Nginx를 사용한 리버스 프록시 구축 및 무중단 배포 구성하기

리버스 프록시는 프록시와 반대로 외부에서 들어오는 요청(request)들을 받아서 내부의 백엔드 서버로 전달하는 역할을 한다. 즉, 외부에서 80 또는 443 포트로 들어오는 http 요청들을 내부의 8080 또는 다른 포트의 서버에 전달하여, 특정 포트 번호 없이 주소만으로 해당 백엔드 서버에 접속할 수 있도록 해준다. 웹서비스를 구축할 때 리버스 프록시를 사용하여 외부에는 공개된 도메인 주소로 접속할 수 있도록 하면서 내부에 특정 포트를 사용한 웹 애플리케이션을 구축하여 연동하는 것이 일반적이다. 여기서는 이러한 리버스 프록시를 사용하여, 무중단 배포 시스템을 구성하는 방법을 살펴본다.

사실 대부분의 웹 애플리케이션은 그 자체가 하나의 소프트웨어로서 기능 추가, 버그 수정 등을 위해 서비스 중에도 업데이트가 필요하고, 이러한 업데이트는 필연적으로 일시적인 서비스 중단을 유발할 수밖에 없다. 하지만 이러한 서비스 중단 없이 사용자가 거의 눈치채지 못하게 업데이트하는 방법이 있다. 이를 무중단 배포라고 하며, 리버스 프록시를 사용하여 이를 구현할 수 있다. 이 방법은 일종의 Active-Standby 방식의 서버 운영이라고 볼 수 있는데, 일단 두 개의 인스턴스를 각각 다른 포트번호로 띄우고, 리버스 프록시는 그 중에 하나를 바라보게 구성하여 운영하다가 업데이트가 필요하면 나머지 하나를 Stop -> Update -> Restart 시킨다음 리버스 프록시의 설정을 바꾸어 새로 업데이트된 서버를 바라보게 하는 것이다. 리버스 프록시를 재시작(리로드)하는 시간은 매우 짧기 때문에 사용자는 서비스가 중단된다는 느낌이 별로 없고, 즉각적으로 새로운 버전의 웹 애플리케이션으로 접속을 하게 된다.

1. 리버스 프록시 구축

1) nginx 설치

우리는 여기서 리버스 프록시를 구축할 때 nginx를 사용할 것이다. 원래 nginx는 apache와 같이 웹서버를 띄우기 위한 소프트웨어인데 리버스 프록시 서버로도 이용할 수 있다. nginx는 리버스 프록시 뿐만 아니라 로드밸런서로도 활용할 수가 있고 설정을 어떻게 하느냐에 따라 단순 웹서버 이상의 많은 용도로 사용할 수 있으므로 알아 두는 것이 좋다.

먼저, nginx를 준비된 리눅스 서버에 설치한다. (여기서 리눅스 설치는 따로 다루지 않는다. 다른 자료를 참고하여 먼저 VM을 준비하고 여기에 리눅스를 설치한 것을 준비해야 한다. 리눅스 배포판은 ubuntu나 centos면 충분하다.) ubuntu 리눅스의 경우 apt, centos의 경우 yum으로 간단하게 설치할 수 있다.

(ubuntu)

> sudo apt install nginx

(centos)

> sudo yum install nginx

설치 후, 설치된 서버의 아이피로 웹브라우저에서 접속을 했을 때, nginx 페이지가 뜨면 정상적으로 설치된 것이다. 만약 접속이 되지 않을 경우, 서버의 방화벽 또는 보안 그룹에서 80 포트가 열려있는지 확인한다. Hyper-V, VirtualBox, VMware와 같은 로컬의 VM을 사용하는 경우에는 아마 별다른 설정이 필요없을 것이다. 대부분 리눅스를 스스로 설치한 경우 기본적으로 방화벽이 열려 있어 별다른 설정을 하지 않아도 접속이 가능하기 때문이다. 이런 경우 오히려 추가적으로 방화벽을 설정하고 특정 포트만 열어주는 것이 보안적으로 바람직하다.

AWS와 같은 퍼블릭 클라우드를 사용하는 경우에는 해당 서비스에 따라 방화벽 설정을 해주어야 한다. AWS에서는 보안 그룹이라는 것이 존재하여 여기서 각 VM별로 열어 줄 포트를 설정할 수가 있다. 보안 그룹의 인바운드 규칙에 80 포트(HTTPS를 사용하고자 한다면 443 포트도 열어주어야 한다.)를 열어준다. (아웃바운드가 아니라 인바운드 규칙을 설정하는 것이다. 아웃바운드 규칙은 특별히 건드리지 않아도 자동으로 설정되는데 모든 트래픽, 모든 프로토콜, 모든 포트에 대해서 열려 있는 것이 기본이다.)

2) 리버스 프록시 설정

이제 설치된 nginx의 설정을 변경하여 리버스 프록시로 구성해보자. nginx의 설정을 변경하고자 한다면 conf 파일을 수정하면 된다.

다음과 같이 vim 에디터를 사용하여 nginx의 설정 파일을 연다. (ubuntu와 centos에서 설정 파일의 이름과 위치가 다르다.)

(ubuntu)

> sudo vim /etc/nginx/sites-available/default

(centos)

> sudo vim /etc/nginx/nginx.conf

기존의 location / { … } 부분을 모두 주석 처리한 후, 아래의 내용을 추가한다.

location / {
    proxy_pass http://localhost:8080;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Proto $scheme;
 }

위에서 localhost:8080 부분의 8080이 실제로 요청을 전달받을 백엔드 서버의 포트 번호이다. 8080 포트가 아닌 다른 포트를 사용할 경우 그에 맞게 수정한다.

proxy_set_header 부분은 실제의 요청 데이터를 header의 각 항목에 할당하도록 하는 부분이다. 경우에 따라서 위의 4가지 설정이 다 필요없는 경우도 있으나 일반적으로는 이렇게 설정하면 문제없다.

참고로 location 항목의 위치는 http > server의 하위에 있어야 한다. 즉, 아래와 같은 구조이다.

http {
...
    server {
        ...
        location / {
            proxy_pass http://localhost:8080;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
        ...
    }
...
}

3) nginx 재시작

nginx 의 설정을 바꿨으니 재시작한다.

이때, centos 의 경우 먼저 아래 내용을 실행할 필요가 있으니 주의한다.

> sudo setsebool -P httpd_can_network_connect 1

nginx를 재시작하는 것은 service 또는 systemctl 명령을 통해서 수행한다.

> sudo service nginx restart

또는

> sudo systemctl restart nginx

4) 접속 확인

포트 번호 없이 주소만으로 접속하여 해당 포트의 백엔드 서버로 접속되는지 확인한다.

2. 리버스 프록시를 활용한 무중단 배포 구성

리버스 프록시를 구축했으므로 이제는 무중단 배포 시스템을 구성하기 위해 nginx의 설정을 추가로 변경한다.

앞에서 설명한대로 두 개의 백엔드 서버를 스위칭하는 방식으로 무중단 배포를 하는 것이므로 8081 포트와 8082 포트로 2개의 서버 인스턴스를 띄운다. 이때, 리버스 프록시에서 바라보는 포트를 active 포트, 나머지 하나를 idle 포트로 본다. 재배포 시 idle 포트의 서버를 종료하고, 새로운 버전으로 재배포한 다음 재시작하고, 리버스 프록시가 바라보는 서버 포트를 스위칭한 후, nginx 를 리로드하면 된다.

1) active 포트 설정

/etc/nginx/conf.d 경로 밑에 service-url.inc 라는 파일을 만들고 vim으로 아래와 같은 내용을 입력한다.

set $service_url http://127.0.0.1:8081;

2) proxy_pass 설정

nginx 의 conf 파일을 수정하여 리버스 프록시 구성을 하는데, 이때 앞에서와 다른 부분은 proxy_pass를 설정하는 부분이다. 다음과 같이 변경하면 된다.

http {
...
    server {
        ...
        include /etc/nginx/conf.d/service-url.inc;
        location / {
                proxy_pass $service_url;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
                proxy_set_header X-Forwarded-Proto $scheme;
        }
        ...
    }
...
}

3) 재배포 방법

재배포시 service-url.inc 내용을 변경한 후, nginx를 reload (restart 가 아니라 reload 해야 한다. restart 를 하면 일시적으로 접속이 끊어질 수 있다.) 하는 셸 스크립트를 작성하면 된다. 아래는 셸 스크립트 샘플이다.

IDLE_PORT=8082
echo “set \$service_url http://127.0.0.1:${IDLE_PORT};” | sudo tee /etc/nginx/conf.d/service-url.inc
sudo service nginx reload

4) 주의사항

실제 배포 스크립트에서는 위와 같이 IDLE_PORT를 하드코딩하면 안되고, 실제의 IDLE_PORT 를 찾는 코드를 작성해야 한다. 즉, 8081, 8082 중 어떤 것이 active이고 어떤 것이 idle인지를 판단하는 로직을 코딩해서 자동으로 스위칭되도록 하는 것이 바람직하다. 업데이트나 배포 등을 수행할 때, 사람이 일일이 작업하는 것은 필연적으로 실수를 불러올 수 있으므로 가능하면 코드로 구현하고 원클릭 업데이트 또는 원클릭 배포가 이루어지도록 하는 것이 좋다.

가능하다면 젠킨스를 사용하여 빌드 및 배포를 자동화하고 이를 통해 무중단 배포를 수행하는 것이 시스템 운영 측면에서 작업 속도, 효율, 오류 방지 등의 이점이 있다. 젠킨스를 사용한 빌드 및 배포 시스템을 구축할 때, 단위 테스트를 추가하여 배포 전 안정성을 확보하는 것도 중요하다. 단순하게 빌드가 성공한다고 하더라도 단위 테스트 없이 배포를 수행할 경우, 사전에 충분한 테스트를 거치지 않은 경우에는 알지 못하는 버그를 포함한 채 배포가 될 수 있기 때문이다. 단위 테스트는 최소한의 안정성 확보에 불과하며 모든 안정적인 서비스는 테스트 케이스를 구축하고 최종적인 사용자 테스트를 수행하여 안정성을 확보한 후에 배포하는 것이 좋다.

무중단 배포 시스템은 운영 시스템에서도 큰 효과를 볼 수 있지만 개발 시스템에서도 큰 힘을 발휘한다. 어찌되었든 시간을 아낄 수 있고 사람이 할 수 있는 실수를 방지할 수 있다는 것만으로도 개발 리소스를 절약하는데 큰 효과가 있다고 하겠다.


[참고 도서]

이동욱, 스프링 부트와 AWS로 혼자 구현하는 웹 서비스, 프리렉, 2019