Nginx를 통한 PROXY 통신시 간혈적인 502 오류 발생
구조
API 서버를 ECS로 배포시 간혈적으로 502 오류가 발생하였습니다. 원인으로 NGINX의 로그를 확인해본 결과 아래와 같은 로그를 확인하였습니다.
2022/09/22 17:24:42 [error] 2650#2650: *1202397 connect() failed (110: Connection timed out) while connecting to upstream, client: 172.10.6.7, server: , request: "GET /mobilecheck/ HTTP/1.1", upstream: "https://172.10.6.175:443/", host: lahuman.github.io"
2022/09/22 17:24:52 [error] 2650#2650: *1202419 connect() failed (110: Connection timed out) while connecting to upstream, client: 172.10.6.7, server: , request: "GET /mobilecheck/ HTTP/1.1", upstream: "https://172.10.6.175:443/", host: lahuman.github.io"
2022/09/22 17:26:53 [error] 2650#2650: *1202397 connect() failed (110: Connection timed out) while connecting to upstream, client: 172.10.6.7, server: , request: "GET /mobilecheck/ HTTP/1.1", upstream: "https://172.10.6.133:443/", host: lahuman.github.io"
2022/09/22 17:27:03 [error] 2650#2650: *1202419 connect() failed (110: Connection timed out) while connecting to upstream, client: 172.10.6.7, server: , request: "GET /mobilecheck/ HTTP/1.1", upstream: "https://172.10.6.133:443/", host: lahuman.github.io"
timeout이 발생하고, 제가 설정한 ALB의 주소가 아닌, 172.10.6.133, 172.10.6.175를 바라보고 있습니다.
실제 ALB의 주소를 확인해보면, 아래와 같이 다른 주소가 나옵니다.
sh-4.2$ nslookup lahuman.github.io
Server: 172.10.6.2
Address: 172.10.6.2#53
Non-authoritative answer:
lahuman.github.io canonical name = internal-lahuman-alb-825567739.ap-northeast-2.elb.amazonaws.com.
Name: internal-lahuman-alb-825567739.ap-northeast-2.elb.amazonaws.com
Address: 172.10.6.135
Name: internal-lahuman-alb-825567739.ap-northeast-2.elb.amazonaws.com
Address: 172.10.6.173
Setting the Domain Name in a Variable를 확인해보면,
NGINX caches the DNS records until the next restart or configuration reload, ignoring the records’ TTL values.
NGINX는 레코드의 TTL 값을 무시하고 다음에 다시 시작하거나 구성을 다시 로드할 때까지 DNS 레코드를 캐시합니다.
라고 되어 있습니다.
따라서 ALB의 IP가 변경되거나, scale-out으로 서버가 늘어날 경우에 Nginx에서는 처음 캐시된 DNS 레코드를 사용하여 502
와 같은 오류가 발생합니다.
문제 해결
해결하는 방식은 Nginx의 설정에 도메인 이름을 재확인하는 빈도를 추가 하는 방법입니다.
# 10초에 한번씩 도메인 재확인
resolver 10.0.0.2 valid=10s;
server {
location / {
# 변수를 사용하여 proxy_pass에 도메인을 설정하면 TTL이 만료될 때 도메인 이름을 다시 확인
set $backend_servers backends.example.com;
proxy_pass http://$backend_servers:8080;
}
}
다만, 이렇게만 처리할 경우
request uri
가 전달되지 않습니다. 최종 코드는 아래와 같이 처리 하였습니다.
server {
listen 8000;
client_max_body_size 150m;
error_page 500 502 503 504 /50x.html;
# /etc/resolv.conf 에서 nameserver 값으로 치환 처리
resolver V_DNS_IP valid=30s;
location /v1 {
set $elb "lahuman.io:443/v1";
if ($request_uri ~* "/v1(.*$)") {
set $path_remainder $1;
}
proxy_pass https://$elb$path_remainder;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_send_timeout 300;
access_log /var/log/nginx/app.api.log json_combined;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
}
location / {
root /app/build;
index index.html index.htm;
try_files $uri $uri/ /index.html;
access_log /var/log/nginx/app.user.log;
}
}
resolver에서 사용되는 IP는 /etc/resolv.conf
에 nameserver
로 존재 합니다.
따라서, 이를 아래와 같이 치환해서 사용합니다.
DNS_IP="$(cat /etc/resolv.conf | grep -o -P 'nameserver \K.*')"
cat ./nginx/server.conf | sed \
-e "s|V_DNS_IP|${DNS_IP}|g" \
> /etc/nginx/conf.d/default.conf
마치며
502오류가 간혈적으로 발생하여 원인을 찾지 못하고 있었는데, Nginx에서 위와 같이 동작하는지 알게되는 시간이었네요.
특히 IDC에서 사용할때는 IP가 항상 고정이라 502 오류의 이유로는 api 주소를 잘못 입력한 경우였었습니다.
클라우드에서는 ip
가 아닌 domain
기반으로 설정되고 ip
는 유동적으로 변경된다는 사실을 꼭 기억해야겠네요.
추가로 ALB
가 아닌 NLB
를 사용하여 ip
를 고정할 수 있습니다.
ip
가 고정되어야하고, ALB
가 꼭 필요한 상황이면, 애플리케이션 로드 밸런서(ALB)에 고정 IP 주소 설정 및 사용하기를 참조해서 NLB=>ALB
구조로 생성합니다.