nginx proxy_pass 업스트림 장애 격리하기
proxy_pass에 호스트명을 그대로 쓰면 기동 시 한 번만 해석되고, 실패하면 nginx가 뜨지 않는다. 변수 + resolver로 요청 시점 해석으로 바꾸는 방법과 주의점.
증상
Docker Compose에서 nginx 컨테이너가 여러 서비스의 리버스 프록시를 맡는 구성이다. 서비스 두 개의 배포가 겹치자 nginx가 기동에 실패했고 도메인 전체가 502가 됐다.
[emerg] host not found in upstream "pharmacy-finder" in
/etc/nginx/conf.d/locations/pharmacy-finder.conf:2
nginx: configuration file /etc/nginx/nginx.conf test failed배포 스크립트는 서비스를 올린 뒤 nginx를 재시작한다.
pharmacy-finder-up:
docker compose up -d pharmacy-finder
docker compose restart nginx겹친 순서는 이렇다.
- A 배포가
docker compose stop A로 A 컨테이너를 내린다 - 같은 시점에 B 배포가
docker compose restart nginx를 실행한다 - nginx 설정이 참조하는 A가 없어서 설정 테스트에 실패하고 nginx가 뜨지 않는다
원인
location /pharmacy-finder {
proxy_pass http://pharmacy-finder:3001;
}proxy_pass에 호스트 이름을 문자열로 쓰면 nginx는 기동 시점에 한 번만 그 이름을 IP로 해석한다. 해석에 실패하면 설정 오류로 보고 기동을 거부한다.
- 업스트림 하나가 내려간 상태에서 nginx가 재시작되면 nginx 전체가 못 뜬다
- 평소에는 드러나지 않는다. 컨테이너를 재생성해 IP가 바뀌어도 배포 끝의
restart nginx가 다시 해석해 주기 때문이다
해결: 변수 + resolver
proxy_pass에 변수가 들어가면 기동 시점이 아니라 요청이 올 때 resolver로 이름을 해석한다.
server {
# Docker 내장 DNS. 컴포즈 네트워크 안의 이름을 해석한다
resolver 127.0.0.11 valid=10s;
location /pharmacy-finder {
set $upstream http://pharmacy-finder:3001; # 변수로 받아서
proxy_pass $upstream; # 런타임에 해석
}
}- 업스트림이 죽어 있어도 nginx는 기동한다. 해당 경로만 502가 된다
- 컨테이너가 살아나면 nginx 재시작 없이 복구된다
주의: 변수를 쓰면 URI 치환이 안 된다
끝에 슬래시가 붙은 proxy_pass는 /anki/foo를 업스트림에 /foo로 넘긴다.
location /anki/ {
proxy_pass http://anki-generator:8000/; # 끝의 슬래시
}변수를 쓰면 이 치환이 동작하지 않는다. /anki/foo가 그대로 넘어가서 404가 난다. rewrite로 직접 잘라야 한다.
location /anki/ {
resolver 127.0.0.11 valid=10s;
set $upstream http://anki-generator:8000;
rewrite ^/anki/(.*)$ /$1 break; # 접두사를 직접 제거
proxy_pass $upstream;
}같이 고친 것: 안 쓰는 포트 매핑
nginx를 다시 띄울 때 에러가 하나 더 났다.
failed to bind host port 0.0.0.0:443/tcp: address already in use- compose에
"443:443"매핑이 있었지만 nginx에는 443 리스너가 없었다. TLS는 앞단 CDN에서 종단하고 내부는 80만 쓴다 - nginx가 죽어 443을 놓은 사이 같은 서버의 다른 데몬이 그 포트를 잡았고, nginx 재기동이 막혔다
- 매핑을 지웠다
ports:
- "80:80"
# - "443:443" ← 삭제. 리스너가 없는 매핑검증
# 1. 서비스 하나를 죽인다
docker stop pharmacy-finder
# 2. 그 상태에서 nginx를 재시작한다 (사고 당시와 같은 조건)
docker restart nginx- nginx 정상 기동
- 다른 서비스는 200, 죽인 서비스만 502
docker start pharmacy-finder후 nginx를 건드리지 않아도 해당 경로가 200으로 복구