[Troubleshoot][Nginx] DNS Resolving이 정상적으로 되어지지 않는 이슈

1. 이슈
CloudFront를 통해 캐싱된 정보를 응답하는 로직에 connection 이슈가 발생하였으며 504 에러를 반환하였다.
Nginx 에러로그
아래 에러 로그 패턴이 반복적으로 확인되어졌다.
2024/09/19 11:24:45 [info] 11#11: *28417007 epoll_wait() reported that client prematurely closed connection, so upstream connection is closed too while connecting to upstream, client: 10.xxx.xxx.xx, server: <<issued-domain-address>>, request: "GET /xxx.css HTTP/1.1", upstream: "http://54.230.174.188:80/xxx.css ", host: " <<issued-domain-address>>", referrer: "https://<<issued-domain-address>>/loginredirectURL=/xxxx/xxxx"
2. 원인 및 해결
이슈 원인은 Nginx 가 참조하고 있는 CloudFront의 엣지 로케이션에 이슈로 인해 발생한 문제이다. Nginx는 DNS 캐싱된 정보로 인해 이슈 대상의 엣지 로케이션을 참조하고 있었으며 사용 불가능한 서버에 연결을 시도하여 504 Gateway Timeout 오류가 발생하였다.
Nginx 서버 재기동 후에는 Nginx가 가용가능한 CloudFront Pop IP list를 참조하여 이슈는 해결되었다.
이슈 원인을 풀어서 정리하면 다음과 같다.
1. CloudFront는 DNS 기반 라우팅을 사용하며 트래픽을 가장 가까운 적합한 엣지 로케이션(POPs)으로 전달한다.

2. Nginx 로그에 기록된 IP 주소 54.230.174.188는 CloudFront 엣지 로케이션(PoP, NRT57-C2)에 대한 IP이며 서비스에 이슈가 확인되어 해당 IP는 PoP에서 제외되었다.
3. Nginx 서버는 CloudFront 도메인에 대하여 과거 DNS 캐시 엔트리(54.230.174.188)를 참조하고 있었으며, 사용할 수 없는 서버에 계속해서 연결을 시도하였다.
4. Nginx가 사용할 수 없는 서버에 연결을 시도하였기 때문에, 업스트림 서버로부터 적시에 응답을 받지 못했다는 504 Gateway Timeout 오류가 발생하였다.
3. 이슈 원인 가능성 및 재발방지 대안
Nginx 프록시 설정 내 직접적인 문자열을 사용하여 오래된 IP 주소가 캐싱되었을 가능성이다.
resolver 10.xxx.0.2 valid=5s;
location /static/ {
proxy_pass http://xxxxxxx.cloudfront.net/xxxxx/;
...
}
AWS 리소스 경우 ELB와 같은 자원들은 자주 IP가 바뀌어지게 된다. DNS flush가 올바르게 되어지기 위해 proxy_pass 구문에 변수를 지정함으로써 동적으로 DNS resolution을 진행할 수 있도록 한다.
resolver 10.xxx.0.2 valid=5s;
location /static/ {
set $cf_domain "xxxxxxx.cloudfront.net";
proxy_pass http://$cf_domain/xxxxxxx/;
...
}
고려사항 1. 성능저하 이슈
이 설정은 동적 DNS 해석 과정이 추가되기 때문에 요청 처리 속도가 느려질 수 있다는 trade-off가 존재한다. 그렇기에 성능이 최우선시되는 로직이라면 도입에 대하여 검토가 필요하다.
(참조 문서: Configuring dynamic DNS resolution in NGINX)
고려사항 2. 변수 설정하더라도 DNS가 재해석 되지 않는다는 사례 존재
Nginx 포럼에 유사한 이슈에 대하여 공개된 글이 존재한다. (참고: https://trac.nginx.org/nginx/ticket/2625)
여기서 질문자는 테스트 환경을 구성하여 Nginx 설정에 변수를 사용하더라도 DNS 해석이 일어나지 않는 부분을 언급하였다. 여기에 달린 답변은 "만약 Nginx 설정에서 특정 proxy_pass에 대해 upstream이 구성 시점에 이미 존재한다면, 변수를 사용하더라도 Nginx는 그 upstream을 사용하고 DNS 해석을 다시 하지 않는다." 라고 언급하였다.
즉, 변수를 사용한다고 해서 매번 DNS 해석이 일어나는 것은 아니라는 것이다.
여기서 우리가 겪은 사례를 대입해보면 가령 CloudFront PoP에 이슈가 생기더라도 Nginx는 이미 설정된 upstream 정보를 계속해서 참조하기 때문에, CloudFront의 PoP(Points of Presence)에 문제가 생기더라도 그 IP에 대한 upstream 정보가 탐색이 되어지는 한 Nginx는 지속해서 그 IP를 참조할 수 있다는 가능성이다.
만약 설정 변경으로 이슈가 해결되지 않는다면 504 에러 탐지 -> Nginx 서버 재기동 부분으로 검토해야 한다.
4. 문제 해결 과정
(1) 기존까지 동작하던 로직이였으며 Nginx, CloudFront 설정에 어떠한 수정이 가해지지 않았음에도 발생한 이슈이다. 그렇기에 1차적으로 AWS 인프라에 이슈가 있는지 1차적으로 의심해볼 수 있다.
(2) 에러 문구를 보면 upstream 서버에서 불완전하게 통신이 종료됨을 시사한다.(epoll_wait() reported that client prematurely closed connection).
여기서, epoll_wait()에 대해 짚고 넘어가 보자.
epoll_wait
epoll 아키텍처는 Nginx가 이벤트 기반 아키텍처에서 효율적인 이벤트 처리를 위해 사용하는 시스템 호출이다.
이를 바탕으로 Nginx는 과도한 CPU 리소스를 소비하지 않고 I/O 이벤트가 발생하기를 기다리면서 여러 파일 디스크립터(예: 네트워크 소켓)를 동시에 모니터링할 수 있게 된다.
참조: https://darkcoding.net/software/epoll-the-api-that-powers-the-modern-internet/

(3) 그렇다면 "upstream: http://54.230.174.188:80/xxx.css" 부분에서 표시된 Public IP는 어떤 정보를 포함하고 있는지 확인해보자.
dig -x 54.230.174.188 +short
>> server-54-230-174-188.nrt57.r.cloudfront.net. #CloudFront 관련 서버 IP임을 짐작할 수 있다.
여기서 nrt57은 일본에 위치한 CloudFront의 엣지로케이션 정보를 의미한다.
CloudFront는 전 세계의 엣지 로케이션(POP)을 식별하기 위해 IATA(국제항공운송협회) 공항 코드를 사용한다. 여기서 NRT는 일본 도쿄에 위치한 나리타 국제 공항을 의미한다.

(3) netcat(참고)을 이용하여 해당 cloudfront 서버와 80/443 통신이 가능한지 확인해보면 통신이 불가능하다는 것을 확인할 수 있다.
# 80/443 Connection 확인 -> 통신실패
nc -v -z 54.230.174.188 80
nc: connect to 54.230.174.188 port 80 (tcp) failed: Connection refused
nc -v -z 54.230.174.188 443
nc: connect to 54.230.174.188 port 443 (tcp) failed: Connection refused
아래 도식표에서 2번 로직에 이슈가 있는 것으로 짐작할 수 있다.

(4) AWS 측에 확인해본 결과 IPs는 이슈가 확인되어 엣지 로케이션(PoP) 대상에서 제외되었다. Nginx는 DNS 캐싱된 정보로 인해 이슈 대상의 엣지 로케이션을 참조하고 있었으며 사용 불가능한 서버에 연결을 시도하여 발생한 이슈이다.
(5) Nginx 재기동 결과 정상적으로 응답하는 부분을 확인되었으며 이슈는 종료되었다.
참조
- https://cyuu.tistory.com/172
- https://docs.wallarm.com/admin-en/configure-dynamic-dns-resolution-nginx/
- https://ercanermis.com/prevent-nginx-from-caching-dns-for-proxy/
댓글