1인 회사 시스템 build

컨테이너를 다시 만들면 앞단이 502가 되는 이유 (nginx DNS 캐시)

nginx가 기동 시점에 딱 한 번만 이름을 풀어 IP를 굳혀 넣는 특성 때문에 백엔드 재생성 후 502가 나는 원인과, resolver와 변수 proxy_pass로 근본 해결하는 법을 정리합니다.


집에서 굴리는 미니PC에 도커 컴포즈로 서비스를 올려 두고 씁니다. 구성은 단순합니다. 백엔드 컨테이너 하나와, 그 앞에 붙은 웹 컨테이너 세 개. 어느 날 백엔드 이미지를 새로 빌드해서 컨테이너 하나만 재생성했는데, 멀쩡하던 앞단 웹 세 개가 전부 502를 뱉기 시작했습니다.

살아 있는데 502가 나는 상황

제일 당황스러웠던 건, 죽은 곳이 없다는 점이었습니다. 백엔드 컨테이너는 정상적으로 떠 있었고, 앞단 nginx 컨테이너 안에 들어가 이름으로 DNS 조회를 해 보면 백엔드 IP가 잘 나왔습니다. 손으로 백엔드에 직접 요청을 넣어 보면 응답도 정상이었습니다. 그런데 nginx를 통해 들어오는 요청만 502로 떨어졌습니다.

건드린 건 백엔드 하나뿐인데, 손 하나 대지 않은 앞단 세 개가 동시에 죽었다는 조합이 이상했습니다. 논리적으로 보면 앞단은 그대로인데 뒷단만 갈아 끼웠으니, 문제의 원인은 ‘갈아 끼우는 행위’ 자체와 앞단이 그걸 어떻게 바라보느냐에 있었습니다.

nginx는 이름을 한 번만 푼다

원인은 nginx 설정에 있었습니다. 저는 이렇게 써 두고 있었습니다.

proxy_pass http://core:8080;

여기서 core는 도커 컴포즈가 붙여 준 백엔드 서비스 이름입니다. 사람 눈에는 매 요청마다 이름으로 백엔드를 찾아갈 것처럼 보이지만, nginx는 그렇게 동작하지 않습니다. 호스트 이름을 리터럴로 쓰면 nginx는 기동 시점에 딱 한 번 이 이름을 풀어 IP를 알아낸 뒤, 그 IP를 설정에 굳혀 넣습니다. 이후로는 다시 조회하지 않습니다.

문제는 도커의 습성과 맞물립니다. 컨테이너를 재생성하면 도커는 새 IP를 배정합니다. nginx는 기동할 때 잡아 둔 옛 IP를 그대로 들고 있으니, 이미 아무도 없는 주소로 계속 요청을 보냅니다. 그 결과가 502였습니다. 컨테이너 안에서 DNS 조회가 정상으로 보였던 이유도 여기 있습니다. DNS는 멀쩡했고, 다만 nginx가 그 DNS를 다시 물어보지 않았을 뿐입니다.

재시작으로 급한 불, resolver로 근본 해결

당장 급하면 방법은 간단합니다. 앞단 nginx 컨테이너를 재시작하면 됩니다. 재시작하는 순간 이름을 새로 풀어 바뀐 IP로 다시 잡으니 502가 사라집니다. 하지만 이건 백엔드를 만질 때마다 되풀이해야 하는 임시 조치라, 근본 해결이 아닙니다.

근본 해결은 nginx가 이름을 주기적으로 다시 풀게 만드는 것입니다. resolver 지시자와 변수 형태의 proxy_pass를 함께 씁니다.

resolver 127.0.0.11 valid=10s;
set $upstream http://core:8080;
proxy_pass $upstream;

127.0.0.11은 도커 내장 DNS 주소입니다. valid=10s는 이름 해석 결과를 캐시할 수명을 지정하는 값으로, 이 주기마다 nginx가 이름을 다시 조회합니다. proxy_pass에 변수를 넣으면 nginx는 이름을 굳혀 두지 않고 런타임에 해석하기 때문에, 컨테이너 IP가 바뀌어도 길어야 캐시 수명만큼 지나면 알아서 새 주소를 잡습니다.

여기엔 함정이 하나 있습니다. 변수 proxy_pass는 리터럴 proxy_pass와 URI 처리 규칙이 다릅니다. 리터럴일 때 자동으로 붙던 location 경로가, 변수를 쓰면 뒤에 붙지 않습니다. 그래서 필요한 경로를 변수 값 뒤에 직접 명시해야 합니다. 이 차이를 모르고 넘어가면, 502는 사라졌는데 이번엔 404가 새로 생겨 다시 한참을 헤매게 됩니다.

한쪽만 만졌는데 관계없는 쪽이 죽으면

이 사건에서 남긴 판단 기준은 하나입니다. ‘한쪽만 재시작했는데 관계없는 쪽이 죽었다’는 증상이 나오면, 죽은 쪽이 이름 해석을 언제 하는지부터 봅니다. 매 요청마다 이름을 푸는지, 아니면 기동할 때 한 번 풀어 굳혀 두는지. 후자라면 뒷단의 주소가 바뀌는 순간 앞단은 아무 잘못 없이 옛 주소를 붙들고 죽습니다.

기동 시점에 이름을 한 번만 해석하는 구성요소는 nginx만이 아닙니다. 커넥션 풀, 캐시 클라이언트, 각종 프록시가 비슷한 방식으로 동작합니다. 그러니 원인을 ‘DNS가 고장 났나’로 좁히지 말고, ‘누가 언제 이름을 푸는가’로 넓혀 보는 편이 빠릅니다. DNS는 대개 멀쩡합니다. 다시 물어보지 않는 쪽이 문제입니다.


이런 자가호스팅 삽질기와 1인 개발 기록을 이어서 받아 보고 싶다면, 뉴스레터를 구독해 주세요. 다음에 또 어딘가에서 502를 만나면, 그 이야기도 담담히 적어 두겠습니다.

자주 묻는 질문

백엔드 컨테이너를 재생성했더니 앞단 nginx가 502를 뱉는 이유는 무엇인가요?

nginx 설정에 proxy_pass http://core:8080; 처럼 호스트 이름을 리터럴로 쓰면 nginx는 기동 시점에 딱 한 번 이름을 풀어 IP를 설정에 굳혀 넣습니다. 이후에는 다시 조회하지 않기 때문에, 컨테이너를 재생성해 IP가 바뀌면 nginx는 옛 IP로 계속 요청을 보내고 그 결과가 502입니다.

당장 급할 때 502를 가장 빨리 없애는 방법은 무엇인가요?

앞단 nginx 컨테이너를 재시작하면 됩니다. 재시작하는 순간 이름을 다시 풀어 새 IP로 잡기 때문에 502가 사라집니다. 다만 이건 임시 조치라, 백엔드를 재생성할 때마다 반복해야 합니다.

resolver와 변수 proxy_pass로 바꿨더니 이번엔 404가 나옵니다. 왜 그런가요?

변수 proxy_pass는 리터럴 proxy_pass와 URI 처리 규칙이 다릅니다. location의 경로가 자동으로 뒤에 붙지 않으므로, 필요한 경로를 변수 값 뒤에 직접 명시해야 합니다. 이 차이를 놓치면 502는 사라지지만 404가 새로 생깁니다.

#nginx#도커#DNS#502#1인 개발