Nginx on Docker

Github에서 소스부터 보기

Nginx와 같은 Reverse proxy server를 둠으로써 얻을 수 있는 효과들에 대해서 오래 전 쓴 글에서 살펴 볼 수 있다. 사실 이 글은 Nginx에 대한 이해가 쌓여갈수록 (조금씩) 업데이트 하고 있다.(https://kimsup10.com/2016/11/25/why-nginx/) Docker를 통해 서비스를 컨테이너화 하는 것의 효과 또한 이전 글에서 살펴 보았다. (https://kimsup10.com/2017/09/02/why-docker/)

이번 글에서는 Docker에 컨테이너화 시킨 웹 서버들 앞에 Nginx를 Reverse proxy server로 두는 것에 대해 다룬다. 내가 떠올릴 수 있는 방법은 세 가지가 있다.

  • 서버 머신에 Nginx하고 Docker port로 proxy_pass 하기
  • 웹 서버 컨테이너 안에 Nginx를 설치하고 컨테이너 안에서 웹 서버에 proxy pass 하기
  • Nginx를 컨테이너화 하기

처음 두 가지 방법이 왠지 모르게 더 쉽게 느껴진다. 하지만 웹 어플리케이션이 한대가 아닌 여러대가 돌아갈 수 있다는 점을 감안하고 Nginx에서 얻을 수 있는 로드 밸런싱의 효과를 생각 했을 때, 그리고 Docker를 통한 쉬운 배포라는 장점을 생각 했을 때, 이는 제대로 된 정답이 될 수 없다. 따라서 Nginx를 컨테이너화 해서 reverse proxy 역할을 하도록 하였다. 특히 Nginx을 이용하여 가장 기본적으로 할 수 있는 proxy와 ssl offloading을 자동으로 할 수 있는 이미지를 만들고 싶었다. 따라서 기본 Nginx 이미지에 기능을 추가하여 새로운 이미지를 위한 Dockerfile을 만들었다.

//Dockerfile.nginx
FROM nginx:latest

ARG domain
ARG port
ARG email

RUN apt-get update && apt-get install software-properties-common -y && add-apt-repository ppa:certbot/certbot
RUN apt-get install -y \
    python-certbot-nginx \
    && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

ADD nginx.conf /root/nginx.conf
ADD ./scripts/setup-reverse-proxy.sh /root/setup-reverse-proxy.sh
ADD ./scripts/setup-ssl.sh /root/setup-ssl.sh

WORKDIR /root

RUN ./setup-reverse-proxy.sh $domain $port
RUN ./setup-ssl.sh $domain $email

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

우선 proxy_pass의 경로가 되는 포트 번호(port)와 Let’s encrypt를 이용하여 무료 인증서를 받기 위해 필요한 domain과 email을 ARG를 통해 입력으로 받을 것을 선언한다. 이 입력은 docker-compose.yml에서 제공할 수 있다. 모든 Docker image는 linux에 기반하므로 apt-get을 이용해 필요한 것을 설치할 수 있다. Nginx 이미지 안에 let’s encrypt 인터페이스 어플리케이션인 certbot을 설치한다.

그리고 다음 명령어를 통해 리버스 프록시 설정을 우리가 입력한 도메인, 포트, 이메일 정보를 이용해 바꾼다.(아래 코드에 대한 링크가 있다!)

RUN ./setup-reverse-proxy.sh $domain $port

그리고 이 스크립트들이 바꾸는 설정 파일 그리고 후에  Nginx 이미지가 적용시킬 conf 파일은 다음과 같다. 위 중 첫번째 스크립트 실행에 따라 PORT는 입력된 port번호로,  DOMAIN_NAME은 입력된 개발자의 도메인 네임으로 바뀐다. (두번째 스크립트는 certbot을 실행시키고 ssl 인증서를 받는다.)

//nginx.conf
upstream docker-webapp {
        server web:PORT;
}

server {
        listen 80;
        server_name DOMAIN_NAME;

        location / {
                client_max_body_size 20M;
                proxy_pass http://docker-webapp;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                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_set_header X-Forwarded-Proto $scheme;
        }
}
server {
        listen 443 ssl http2;
        server_name DOMAIN_NAME;

        location / {
                client_max_body_size 20M;
                proxy_pass http://docker-webapp;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                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_set_header X-Forwarded-Proto $scheme;
        }
}

여기서 주목할 점은 upstream을 docker container의 호스트 네임으로 바꿔 준다는 것이다. upstream을 따로 설정하지 않을 경우 localhost가 기본으로 설정 되어 있는데 Nginx의 이미지를 담고 있는 컨테이너 안에서 로컬호스트로 다른 포트를 아무리 찍어도 웹 어플리케이션을 포함한 다른 서비스들에 연결할 수 없다. 따라서 docker-compose.yml 명시한 서비스 네임 혹은 해당 서비스의 호스트 네임으로 upstream을 설정 해주어야 Nginx container에서 webapp container로 프록시 패스를 할 수 있다. 여기선 web이다.

version: '2'

services:
  # Flask web server
  web:
    build:
      context: .
      dockerfile: Dockerfile
    hostname: web
    command: python3 ./webapp/index.py
      - project_name=myproject
    volumes:
      - .:/root/myproject  # mount current directory inside container
    ports:
      - "5000:5000"

  nginx:
    build:
      context: .
      dockerfile: Dockerfile.nginx
      args:
        - domain= [domain]
        - port= [port]# Same as web port
    command:./setup-ssl.sh [domain] [email]
    hostname: nginx
    ports:
      - "80:80"
      - "443:443"
    links:
      - web

위 docker-compose.yml 파일에서 nignx service의 [domain], [port], [eamil]을 본인들에게 맞는 인자로 채워 넣고 다커 컴포즈 업!을 한다.

$ docker-compose up

그러면 Nignx 컨테이너와 Web 컨테이너가 생성되고 Nginx 컨테이너가 Web 컨테이너의 Reverse proxy server 역할을 하는 것을 볼 수 있다. 이 때 Service-nginx-conmand에 지정해놓은 실행 파일에 의해 해당 컨테이너에 SSL offloading을 한다. 본인이 가지고 있는 도메인이 없으면 localhost를 domain 값으로 주면 되는데, 이 경우엔 SSL offloading을 할 수 없다. 로컬호스트나 aws ec2 도메인 등은 let’s encrypt에서 인증서를 내주지 않기 때문이다.

setup-ssl.sh의 코드를 보면 다음과 같다.

certbot --nginx -n -d "$1" --agree-tos --email "$2"
nginx -g 'daemon off;'

이때 certbot 명령어를 실행시킨 후 바로 스크립트를 탈출하거나 service nginx restart 등을 통하여 nginx를 재실행 시키는 것이 아니라 nginx -g ‘daemon off;’ 를 이용하는 이유는, Dokcerfile 혹은 docker-compose의 command 옵션에 의해 실행되는 명령들은 마지막 명령이 나타내는 터미널의 상태가 곧 Docker 컨테이너의 실행 상태가 된다. certbot혹은 service restart를 할 경우 작업이 끝나면 프로그램을 끝내거나, 백그라운드에서 실행시키고 터미널로 돌아와 버린다. 그렇게 되면 Docker 컨테이너의 동작 역시 멈추는 것이다. 따라서 ‘daemon off’ 옵션을 통해서 nginx을 실행시켜서 띄어 놓은 채로 컨테이너가 유지되도록 한다.

물론 web에 해당하는 Dockerfile은 직접 만드셔야 한다. 이전 글을 참조하여 만들어도 좋고, 당장 테스트 해보고 싶다면 다음 레포지토리를 참고하면 된다!

https://github.com/kimsup10/easy-nginx-on-docker

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중

search previous next tag category expand menu location phone mail time cart zoom edit close