nginx 변수 proxy_pass와 resolver — 일부 요청만 실패하는 DNS 조회

네트워킹3분조회

nginx 를 리버스 프록시로 두고 요청 경로의 호스트명으로 백엔드를 동적으로 정하는 설정을 쓰고 있었습니다. 목적지를 변수로 넘기면 nginx 는 매 요청마다 resolver(설정에 지정한 DNS 서버)로 이름을 새로 조회하는데, 이 resolver 목록을 잘못 구성해 생긴 간헐적 실패를 쫓은 기록입니다.

proxy_pass 목적지를 변수로 쓰는 nginx 설정에서, 요청의 일부만 간헐적으로 499 나 응답 없음으로 끝났습니다. 대부분은 정상이었습니다.

변수 proxy_pass — 매 요청 DNS 재조회

proxy_pass http://$host:$port; 처럼 목적지에 변수를 쓰면, nginx 는 시작할 때 한 번 이름을 푸는 게 아니라 매 요청마다 resolver 지시자로 DNS 를 새로 조회합니다.

이게 일반 upstream 블록과 다른 점입니다. upstream 은 재시도와 헬스체크가 있지만, 변수 proxy_pass 의 동적 조회는 재시도도 헬스체크도 없습니다. 한 번의 조회가 실패하거나 이상하면 그 요청 전체가 실패합니다.

resolver에 섞인, 그 존을 모르는 서버

문제가 된 설정입니다.

resolver 192.0.2.1:5300 192.0.2.2:5300 coredns.kube-system.svc.cluster.local valid=10s;

location ~ /app/(.*)/(.*)/socket.io/(.*) {
    rewrite /app/(.*)/(.*)/socket.io/(.*) /socket.io/$3 break;
    proxy_pass http://$1:$2;
}

192.0.2.1/2:5300 은 커스텀 DNS 존을 아는 서버(PowerDNS)입니다. 그런데 같은 목록에 클러스터의 coredns 도 나열해뒀습니다. coredns 는 이 커스텀 존을 전혀 모릅니다.

resolver 에 여러 서버를 나열하면 nginx 가 어느 서버로 조회를 보내고 응답을 어떻게 처리할지가 상황에 따라 갈릴 수 있습니다. 그 결과 이 도메인을 찾는 조회가 간헐적으로 실패했습니다. 커스텀 존을 아는 서버로 조회가 가면 성공, coredns 로 가면 실패. 그래서 요청의 일부만 죽은 겁니다.

resolver엔 해당 존을 아는 서버만

resolver 192.0.2.1:5300 192.0.2.2:5300 valid=10s;

이 위치 블록이 조회하는 도메인은 커스텀 존이니, 그 존을 아는 서버만 남기고 coredns 는 뺐습니다.

클러스터 내부 서비스(*.svc.cluster.local)도 같은 블록에서 조회해야 한다면, resolver 를 하나로 합치지 말고 위치 블록을 분리해서 각각 맞는 resolver 를 쓰는 게 낫습니다. 한 resolver 목록에 서로 다른 존을 아는 서버를 섞지 않는 게 핵심입니다.

비슷하지만 다른 증상 — DNS 캐시 이슈

nginx 의 동적 DNS 조회 문제는 두 가지가 헷갈립니다. 둘 다 resolver 관련이지만 원인이 다릅니다.

DNS 캐시 이슈: resolver 자체는 정상인데 캐싱된 옛날 IP 를 계속 씁니다. 로드밸런서 뒤 서버 IP 가 바뀌면 timeout 이 납니다. 해결은 resolver ... valid=10s 로 캐시 TTL 을 짧게.

이 이슈: resolver 후보에 애초에 그 존을 모르는 서버가 섞여서 조회 자체가 불안정합니다. 해결은 관련 없는 서버를 목록에서 제거.

증상이 비슷해서(둘 다 간헐적 실패) valid=10s 만 만지작거리기 쉬운데, 후자는 목록 구성이 문제라 TTL 을 바꿔도 안 고쳐집니다.

  1. 불러오는 중