#3 웹 프로필 - Docker를 활용하여 nginx reverse proxy와 nginx 웹서버 구성

2021. 10. 19. 20:49Projects/Web Profile

개요

배포하기에 앞서 도커를 활용하여 컨테이너 형태로 reverse proxy와 nginx 웹서버를 구성한다. 

만일 도커를 사용하지 않는다면 reverse proxy 서버와 웹서버를 각각의 서버를 두고 구현해야 하기 때문에 각 서버의 운영체제에 맞는 명령어를 사용하여 각 서버를 세팅하고, AWS를 사용한다면 서버 비용도 증가할 것이다. 하지만 도커를 사용하면 간단하게 여러 컨테이너를  빌드하여 같은 네트워크에 위치시킴으로 통신이 가능하고, dockerfile과 docker-compose 파일을 사용하여 관리와 유지보수에도 더 편리하고 유연함을 가질 수 있다.


토폴로지

전체 토폴로지
AWS EC2 인스턴스 안의 도커 컨테이너 토폴로지

Reverse Proxy는 EC2 인스턴스로 연결되어있는 

/ : 시작 애니메이션 페이지가 동작중인 웹서버로 연결
/main-kr : 한글로 작성된 메인 페이지가 동작중인 웹서버로 연결
/main-en : 영어로 작성된 메인 페이지가 동작중인 웹서버로 연결

파일 트리

제일 먼저 도커 컨테이너를 빌드하기 전 필요 항목을 나열한다. 

Home Directory

현재 다이렉토리 안에 있는 파일이다. 

[certs]

certs Directory

 SSL 인증을 위한 인증서가 위치해있다.


[docker-compose.yml]

docker-compose.yml

도커 컴포즈 파일, 해당 파일로 도커 컨테이너의 설정을 정의하고 여러 컨테이너를 묶어서 함께 동작시킬 수 있다. 아래에서 도커 컴포즈 파일을 자세히 정리한다.


[html]

html Directory

웹서버 컨테이너에서 동작할 웹사이트 소스 코드가 위치해 있다. 

maintenance.html 파일은 사이트 개발 중 아직 준비되지 않은 링크에 접근할 수 없다는 문구를 출력해주기 위해 생성한 html 파일이다.


[nginx]

nginx Directory

reverse proxy와 각 웹서버들의 설정(configuration) 파일이 저장되어있다. 


Reverse Proxy와 웹서버를 위한 Nginx 설정

[nginx.conf] - reverse proxy

nginx.conf for Reverse Proxy Server

Reverse proxy 설정을 위한 nginx.conf 파일이다. 

nginx.conf 파일은 nginx의 메인 설정 파일로 nginx가 실행되면서 제일 먼저 적용되는 설정들이 정의되어있다. 그 후 해당 파일 내의 41번 줄에 정의된 "include /etc/nginx/conf.d/*.conf;" 명령으로 인해 conf.d/ 디렉터리 안에 있는 추가 설정 파일들이 불려 오게 된다. 

해당 설정 파일에서 보라색 테두리 부분을 제외한 부분은 기본값이다. 리버스 프록시 설정을 위해 upstream 영역만 추가해주었다. upstream 이란 리버스 프록시 서버 하단에서 동작하는 웹서버를 뜻한다. upstream의 명칭(upstream main-kr)은 자유롭게 지정할 수 있지만, server 명칭(server mainkr) 같은 경우에는 서버를 가리켜야 하기 때문에 도커 컴포즈 파일에서 정의한 서버 이름과 일치시켜주어야 한다. 이후 도커 컴포즈 파일을 보며 한 번 더 정리한다.

[default.conf] - reverse proxy 

default.conf for Reverse Proxy Server #1

default.conf 파일은 conf.d/ 디렉터리 하위에 위치해있고, 위에서 설명한 nginx.conf 파일 안에 "include /etc/nginx/conf.d/*.conf" 명령으로 인해 불러와진다. 해당 파일에서는 추가적인 설정을 정의한다.

listen : 통신할 포트를 정의
server_name : 통신할 도메인을 정의
location : 통신할 path 위치를 정의

위 설정은 정의한 도메인을 통해 80번 포트(HTTP)로 요청이 들어오면 해당 통신을 HTTPS 통신으로 리다이렉트(redirect) 하겠다는 의미의 설정이다. HTTP 통신은 HTTPS 통신에 비해 모든 사용자의 정보가 암호화되지 않고 그대로 노출되어 MITM(Man In The Middle)등의 공격에 매우 취약하다. 나의 웹사이트는 SSL 인증서를 통해 HTTPS가 적용되어있어 위와 같이 HTTP 프로토콜을 통한 통신은 모두 HTTPS 프로토콜로 전환되도록 설정해놓았다. 

default.conf for Reverse Proxy Server #2

HTTP를 HTTPS로 리다리렉트(redirect) 했다면, HTTPS 프로토콜로 통신이 이루어질 때 동작 방법을 따로 정의해주어야 한다. 

처음 보라색 테두리 안의 내용은 위에서 HTTP 프로토콜에 대해 정의할 때와 같다. 정의한 도메인을 통해 443번 포트(HTTPS)로 통신 요청이 들어왔을 때 어떤 식으로 동작할지 정의한다는 것이다.  

두 번째 파란색 테두리 안의 내용은 SSL 인증서의 위치를 정의해준 것이다. HTTPS 프로토콜을 통해 통신을 하기 위해서는 SSL 인증서가 필요한데, 서버 시스템 안에 위치해있는 인증서의 위치를 절대 경로(absolute path)로 작성하여 정의해주었다.

마지막 노란색 테두리 안의 내용은 프록시 모듈을 정의해준 것이다. 

proxy_intercepts_errors : off의 경우 리버스 프록시 서버 하단에 위치한 서버에서 발생한 에러코드 300번 이상의 에러를 클라이언트에게 바로 전달하고, on의 경우 미리 정의된 error_page 지시어에 맞춰서 에러를 처리한다.
기본값은 off로 설정되어있다.

proxy_buffers : 프록시 서버 하단에 위치한 서버로부터 응답을 받아올 때 사용될 버퍼의 수와 크기를 정의한다.
기본값은 8k 16k로 설정되어있다. 

proxy_set_header : 필드를 정의하고 해당 필드에 값을 정의하는 지시자이다. 값은 텍스트, 변수 또는 텍스트와 변수의 혼합 형식으로 정의할 수 있다. 

proxy_read_timeout : 프록시 서버 하단에 위치한 웹서버로부터 응답을 받아올 시간을 정의한다. 해당 시간 동안 응답을 받지 못하면 연결이 종료된다. 기본값은 60초이다.

proxy_send_timeout : 리버스 프록시 서버가 하위에 웹서버로 요청을 전송하는 시간을 정의한다. 해당 시간 동안 요청을 전송하지 못하면 연결이 종료된다. 기본값은 60초이다.

http://nginx.org/en/docs/http/ngx_http_proxy_module.html
위 nginx 공식 문서를 참고하여 작성하였다.

 

default.conf for Reverse Proxy Server #3

마지막 막으로 reverse proxy에서 어떤 요청을 어느 서버로 전송할지 정의해주는 설정이다.

nginx.conf / default.conf

여기서 중요한 점은 proxy_pass 지시자에 정의되어있는 서버 명칭은 upstream을 지정할 때 사용한 명칭과 동일해야 한다. 

30 ~ 32번 라인 
domain/으로 요청이 들어오면 해당 요청은 http 프로토콜을 사용하여 coverpage 서버로 전송한다.
35 ~ 36번 라인
domain/main-kr로 요청이 들어오면 해당 요청은 http 프로토콜을 사용하여 main-kr 서버로 전송한다.
이때 domain/main-kr/some/uri 와 같은 요청이 들어올 시 웹서버로 전송되는 url을 domain/some/uri 와 같이 변경하여 전송한다.
39 ~ 41번 라인
domain/main-en으로 요청이 들어오면 해당 요청은 http 프로토콜을 사용하여 main-en 서버로 전송한다. 
위 main-kr과 같이 rewrite을 적용시켜주었다.

*리다리렉트(redirect)가 되는 것에 대한 로그를 쌓고 디버깅을 하고 싶다면 location {} 블록 안에 아래와 같이 작성하여 로그를 쌓아볼 수 있다.

location /main-kr {
    proxy_pass        http://main-kr;
    rewrite             ^/main-kr(.*)$ $1 break;
    error_log           /var/log/main-kr.error debug;
}

HTTPS 프로토콜 설정을 마쳤지만 여기에서 HTTP 통신을 사용하는 이유는 웹서버는 내부 서버이고 오로지 리버스 프록시 서버 하고만 통신이 이루어진다. 즉, 웹서버로 들어오는 웹 페이지 요청은 모두 리버스 프록시 서버를 거쳐서 들어오게 되는 것이다. 그러므로 외부 노출이 없는 리버스 프록시와 하단의 웹서버 간의 통신에서는 HTTP 프로토콜을 사용하였다. 결국 외부(클라이언트)와 통신하는 것은 리버스 프록시 서버이기 때문에 HTTP 프로토콜을 통해 응답받은 내용이 다시 리버스 프록시를 통해 클라이언트로 보내질 때는 HTTPS 프로토콜을 통해 전송된다.

[default.conf] - web server

default.conf for Web Server

나의 경우는 기본 nginx의 경로, "/usr/share/nginx/html/"을 그대로 사용하여 서버 이름을 제외한 나머지 설정값을 기본값으로 두었다. 하지만 기본 경로가 아닌 새로운 경로를 사용하고 싶다면 "root" 옆에 새로운 경로를 지정해 줄 수 있다. 


docker-compose.yml

도커 컴포즈 파일을 작성하면 도커의 긴 명령어를 매번 입력할 필요가 없이 docker-compose up 명령어 하나로 도커 컴포즈 파일 안에 정의되어있는 모든 컨테이너를 실행하고, docker-compose down 명령어로 컨테이너 전부를 한 번에 종료할 수 있다. docker run을 사용할 시 필요한 -p, -v 등의 옵션을 port, volumes 등으로 미리 정의할 수 있어 명령어가 간결해지고 추후 컨테이너 관리, 유지보수에도 큰 편리함을 안겨준다.

docker-compose.yml, version

도커 컴포즈 파일 가장 첫 줄에는 버전을 정의해준다. 버전에 따라 명령어가 조금씩 다르다. 

docker-compose.yml, services

버전 이후 정의해주는 것은 services이다. services 블록 안에는 각 컨테이너를 직접 정의해준다. 

docker-compose.yml, revproxy

리버스 프록시 컨테이너를 빌드하기 위한 컴포즈 명령어이다. "- (hyphen)" 은 리스트를 만들 때 사용된다. 

container_name : 컨테이너 이름을 정의 (--name 옵션)
image : 사용할 기본 이미지를 정의
ports : 호스트와 컨테이너 시스템 간 연결할 포트를 정의 (-p 옵션)
volumes : 호스트와 컨테이너 시스템 간 마운트 하여 연동할 디렉터리 정의 (-v 옵션)
networks : 컨테이너를 생성할 도커 네트워크 정의 
depends_on : 도커 파일은 기본적으로 위에서부터 아래로 먼저 정의되어있는 컨테이너 순서대로 컨테이너를 생성한다. 하지만 간혹 특정 컨테이너가 먼저 실행되어야 할 상황이라면 depends_on 명령어로 실행 순서를 정의해줄 수 있다. 

리버스 프록시 서버를 모든 웹서버 컨테이너가 생성된 이후 생성하는 이유는 리버스 프록시가 컨테이너가 제일 먼저 생성되는 경우 제대로 실행되지 못하는 에러가 발생하여 위와 같이 설정하였다.

docker-compose.yml, coverpage
docker-compose.yml, mainkr
docker-compose.yml, mainen

각각의 서버는 위와 같이 설정해주었다. 기본 nginx 컨테이너에 호스트 시스템에 있는 파일을 링크시켜주고 컨테이너를 모두 같은 네트워크에 배치해주었다. 

docker-compose.yml / nginx.conf for Reverse Proxy Server

위 docker-compose 파일에서 지칭한 서비스 이름은 nginx 설정에 중요한 역할을 한다. 리버스 프록시 서버 설정에서 upstream 설정을 할 때 서버 명칭과 docker-compose.yml 에서 정의한 서비스 명칭은 동일해야 한다.

docker-compose.yml, networks

마지막으로 네트워크를 정의해준다. networks 블락 안에 모든 앞에서 정의한 네트워크의 이름을 넣어준다.

external : 네트워크를 도커 컴포즈 파일 외부에서 생성할지, 내부에서 생성할지 설정한다. 
true 설정일 시 네트워크를 외부에서 만들어주어야 하고, 이 경우 docker network create <network_name> 명령어 실행이 선행되어야 한다. 해당 네트워크를 찾지 못한다면 도커 컴포즈는 실행되지 않는다.

docker-compose.yml

도커 컴포즈 파일의 전체이다. 도커 컴포즈 파일은 들여 쓰기(indentation)에 민감하여 정확한 포맷을 갖춰주는 게 중요하다. 


도커 실행

도커 파일이 완성된 후 docker-compose up이라는 명령어를 사용하여 도커 컴포즈 파일을 실행해 그 안에 정의되어있는 컨테이너들을 동작시킬 수 있다. "docker run -it -d --name <> -p " 등을 사용하며 도커 컨테이너 하나하나를 실행할 때에 비해 매우 간편하다.

하지만 도커 컴포즈 파일을 처음 작성하는 도중에는 많은 오류가 생기고, 컨테이너 빌드에 성공한 후에도 컨테이너가 제대로 동작하지 않는 등 많은 에러가 발생한다. 이럴 때는 docker-compose up 또는 docker-compose down이라는 명령어를 치기마저 귀찮을 수 있다. 그래서 나는 .bashrc 파일에 alias를 적용하여 해당 명령어를 더 간편하게 설정해주었다. 

.bashrc

"docker-compose"라는 명령어를 "doc"이라는 세 글자의 짧은 명령어로 설정해주었다. 

이후 "doc up -d" 명령어를 통해 도커를 실행할 수 있고, docker ps -a로 컨테이너 동작 상태를 확인할 수 있다.

doc up -d / docker ps -a


마치며

도커 파일을 이용하여 도커 이미지를 새롭게 빌드하거나 도커 컴포즈 파일에 복잡한 설정은 하지 않아 비교적 쉽게 도커 컴포즈 파일을 작성하였지만 nginx reverse proxy를 설정하는 부분이 힘들었다. 

본래 목적은 이전에 작업했던 django app 또한 reverse proxy 하단에 위치시켜 한 서버에서 모두 동작시킬 예정이었으나 연동하는 과정에서 수많은 에러가 발생했고, reverse proxy 설정 오류인지, django application의 설정 오류인지 아직 해결하지 못하여 따로 서버를 구성하고, 서브 도메인을 활용하여 배포한 상태이다. nginx와 django를 조금 더 공부한 후에 다시 시도해볼 예정이다.