tech.kkung.net

Nginx를 ELB Reverse Proxy로 사용할때 주의 점

Nginx를 ELB의 Reverse Proxy로 사용할때 주의 점

(이 글은 Nginx with dynamic upstreams 내용을 참고하여 작성되었습니다)

비트는 서버 구성요소를 크게 목적에 따라 몇가지 분류로 구분하여 사용하고 있으며, 해당 서버 그룹의 Load balacing을 위하여 ELB를 이용하고 있다.

BEAT-DG

최근, 급격한 트래픽 증가시점이나, 비정기적으로 ELB상의 HTTP Error가 증가하는 경우가 발견하여 문제를 추적하던 도중, Nginx에서 upstream timeout 혹은 upstream connection failed 같은 오류가 관측되는 경우를 확인하였다.

ELB는 외부 용도(Internet facing)뿐 아니라, 내부 서비스들의 Load balacner로 쓰기에도 매우 훌륭하다. 특히 서비스 인스턴스군을 Autoscaling Group으로 관리할 경우, 적절한 Healthy check를 통한 가용성 관리와 서비스 등록&해제는 무척 편리한다. 무엇보다, 이런점들과 함께 '알아서 스케일 되는' ELB는 무척 편리한 서비스임에 틀림 없다.

물론 이렇게 무척 편리한 ELB이지만, 특히 Internal Load Balancer로 쓸 때에는 몇가지 고려 사항이 있다.

예를들어,

ubuntu@ip-10-167-5-68:~$ dig foo.beatpacking.com

; <<>> DiG 9.9.5-3ubuntu0.2-Ubuntu <<>> foo.beatpacking.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 15618
;; flags: qr rd ra; QUERY: 1, ANSWER: 8, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;foo.beatpacking.com.      IN      A

;; ANSWER SECTION:
foo.beatpacking.com. 60    IN      A       54.249.0.0
foo.beatpacking.com. 60    IN      A       54.249.0.0
foo.beatpacking.com. 60    IN      A       176.32.0.0
foo.beatpacking.com. 60    IN      A       46.51.20.0
foo.beatpacking.com. 60    IN      A       46.51.20.0
foo.beatpacking.com. 60    IN      A       54.248.0.0
foo.beatpacking.com. 60    IN      A       54.248.0.0
foo.beatpacking.com. 60    IN      A       54.249.0.0

;; Query time: 3 msec
;; SERVER: 172.16.0.23#53(172.16.0.23)
;; WHEN: Fri Feb 19 12:35:50 UTC 2016
;; MSG SIZE  rcvd: 181

위의 결과와 같이 ELB는 사용량에 따라 여러개의 IP를 반환할 수 있다. 1 사실, ELB는 EC2 인스턴스의 집합이며, 내부적으로 ELB 사용량에 따라서 자동으로 Scale-In/Out 되는 관리형 서비스 이기 때문에, 위와 같이 IP가 계속 변동된다.

비트에서 격었던 문제는, 이런 ELB의 특성과 Nginx의 작동 특성에 기인하여 발생하였다. 보통 Nginx 설정은, upstream 을 선언하여 설정하거나,

upstream backend {
  server  unix:/tmp/gunicorn.sock max_fails=0 fail_timeout=0;}

location @beat-api {
    proxy_pass http://backend;
    proxy_set_header   Host $host;
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;}
location @beat-api {

   proxy_pass http://elb-test.ap-northeast-1.elb.amazonaws.com;
    proxy_set_header   Host $host;
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
}

혹은 proxy_pass 에 직접 서버 주소를 지정함으로써 처리하게 되는 것이 보통이다. 크게 문제가 될 것이 없어 보이는 이런 설정에서 왜 문제가 발생하게 될까? 위의 dns query결과를 다시 살펴보면, foo.beatpacking.com. 60 IN A 54.249.0.0 위와 같이 TTL 이 60초로 짧게 설정되어 있어, IP가 바뀔 경우 빠르게 대응하도록 요구하고 있는데, Nginx의 경우 설정 파일을 읽는 시점에 해당 DNS에 대한 IP 변환(resolve)를 수행한다는 점에 있다. 이럴 경우 ELB에 트래픽이 많아져 ELB 스스로 Scale-Up 한 경우에 그 효과를 기대할 수 없을뿐 아니라, 최악의 경우에는 IP가 교체되었음에도 불구하고 여전히 옛날 IP로 요청이 전달되어 오류가 나는 경우까지도 발생한다.

이 문제를 어떻게 해결해야 할까? nginx의 proxy_module 문서를 살펴 보면, 다음과 같은 부분을 확인해 볼 수 있다.

A server name, its port and the passed URI can also be specified using variables:

proxy_pass http://$host$uri;

or even like this:

proxy_pass $request;

In this case, the server name is searched among the described server groups, and, if not found, is determined using a resolver.

즉, 변수를 통해서 server 이름이 지정된 경우 항상 resolver를 통한 IP변환을 수행하게 되어 항상 올바른 ELB IP를 사용할 수 있게 된다. 이에 따라, 이런 부분을 반영하면 설정은 다음과 같이 변경된다.

# 172.16.0.23은 AWS의 내부 DNS
# ELB의 TTL은 60초로 설정되기 때문에, 이보다 짧거나 같은 유효시간을 설정해서  최신의 IP를 사용토록 한다.
resolver 172.16.0.23 valid=5s;

set $ep "http://elb-test.ap-northeast-1.elb.amazonaws.com";
location @beat-api {
    proxy_pass http://$ep;
    proxy_set_header   Host $host;
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
}

위와 같이 설정한 후, 대량 Push발송등의 이벤트시에 종종 관찰되던 upstream connection failed/timeout 관련 문제가 해결된 것이 확인되었다.


  1. 트래픽 사용량이 없더라도 AZ(Availability Zone)에 따라 최소 1개의 IP가 할당되는 모습을 확인할 수 있다.