inlee's blog

Docker를 이용한 Reverse Proxy 서버 구성 및 자동화

· inlee

개요

이 글은 Docker를 이용해 NginX Reverse Proxy 서버를 구성한 후 2개의 웹 어플리케이션(Node.js App, phpmyadmin)과 MySQL을 설한 방법에 대한 내용이다. 그리고 웹 어플리케이션에 사용한 Let’s Encrypt 인증서 발급 및 갱신, 확인 과정을 추가하였으며 마지막으로 이들을 자동화한 방법에 대해 소개하고자 한다.

배경

기존 위의 그림과 같이 Docker NginX(Reverse Proxy), 어플리케이션들, DB를 설정하여 사용했었다. docker compose를 이용하면 설정파일(.yml)에 정의한 Container 들이 한번에 실행이 가능해 자동화 할 수 있으나 NginX 환경설정 파일(.conf)의 도메인 이름과 인증서 저장 경로는 프로젝트에 의존적인 값이기 때문에 항상 수동으로 직접 입력하여 사용하였다.

그래서 NginX Reverse Proxy 실행 과정을 개선하여 직접 입력하는 불편함을 해결하였다. 또한 Let’s Encrypt 인증서의 발급 과정을 추가함으로써 기본으로 https 서버가 실행이 되도록 하였다. 마지막으로 짧은 인증서 유효기간(3개월)을 자동으로 갱신을 하는 스크립트를 만든 후 crontab 에 등록하여 사용하였다.

이어지는 글은 NginX Reverse Proxy 서버 구성과 개선한 방법에 대한 내용이며 구현 결과는 아래 링크에 업로드 하였다.

Docker 설정

Docker Compose

본 글의 구현은 docker compose를 이용해 동작하며 서버 설정, Let’s Encrypt 설정으로 나누어 진다.

  • 서버 설정: Docker를 이용한 NginX Reverse Proxy, Node.js App, MySQL, phpmyadmin 설정. #링크.
  • Let’s Encrypt 설정: Docker Certbot를 이용한 Let’s Encrypt 인증서 발급/갱신/확인 설정. #링크.

Docker Images

사용한 Docker Image는 공식 Image와 해당 오픈 소스에서 공개하여 배포하는 Image를 이용하였으며 아래와 같다.

항목Docker Image설명
Reverse Proxynginx:1.17.8Reverse Proxy 서버
Appnode:13.12.0테스트 어플리케이션
Database Administratorphpmyadmin/phpmyadmin:latestMySQL 웹 관리 도구
Databasemysql:5.7.29데이터베이스
Let’s Encrypt 인증서certbot/certbot:latest인증서 발급/갱신

Docker Volume

데이터를 공유하기 위해 Docker Volume을 아래와 같이 사용하였으며 docker compose 설정파일(.yml) 들에 정의하였다.

  • Container의 데이터는 Docker에서 제공하는 Volume 사용.
  • Container 내에서 사용할 설정 파일은 Host의 Volume 사용.

Docker Network

Container 이름으로 네트워크 통신을 하기 위해 별도의 Bridge 네트워크를 만든 후 사용하였으며 docker compose 설정파일(.yml) 들에 정의하였다.

Reverse Proxy 환경설정 파일 내의 upstream과 App에서 사용할 MySQL의 host 에 Container의 IP 대신 이름을 사용하였다. 참고로 IP 정보는 고정으로 지정되어 있지만 사용하지 않았다.

환경변수

본 글의 Reverse Proxy 설정과 Let’s Encrypt 인증서의 발급/갱신/확인은 docker compose를 이용해 실행한다. docker compose는 DotEnv(.env) 파일을 읽어들여 실행이 가능1하다. 그래서 서버 구성과 인증서 발급/갱신 과정에서 프로젝트별로 다르게 사용하는 정보들은 .env 에 정의하여 사용하도록 하였다.

사용한 환경변수 항목은 아래와 같다.

환경변수 이름설명
APP_URLapp 컨테이너(Container)에 접속할 URL. Reverse Proxy 설정에서 사용. FQDN 입력.
PHPMYADMIN_URLphpmyadmin 컨테이너(Container)에 접속할 URL. Reverse Proxy 설정에서 사용. FQDN 입력.
MYSQL_ROOT_PASSWORDMYSQL의 Root 계정 비밀번호
CERTBOT_CERT_EMAILLet’s Encrypt 인증서 발급자의 이메일 주소
CERTBOT_CERT_NAMELet’s Encrypt 인증서 이름. App, phpmyadmin 인증서를 하나의 파일로 발급받아 사용(멀티 도메인 참조)하므로 파일을 저장할 디렉터리 이름으로 사용. Default로 mycert 라는 이름을 사용하도록 설정.

Reverse Proxy 설정

서버의 대문 역할을 하는 Reverse Proxy는 NginX를 이용하며 기존에는 NginX 환경설정 파일(.conf) 내 도메인 이름과 인증서 저장 경로는 프로젝트에 의존적인 값이기 때문에 항상 수동으로 직접 입력하여 사용하였다.

그래서 이를 보완하고 자동화하기 위한 방법으로 docker compose를 이용해 실행 시 NginX Docker Image에서 공식으로 제공하는 방법인 envsubst2 명령을 이용해 NginX 설정 템플릿(template) 파일 내 환경변수(도메인 이름, SSL 인증서 저장 경로)들을 .env 파일의 값으로 대치한 후 그 결과를 NginX 설정 파일로 만들어 사용하였다3.

또한 보안을 위해 Let’s Encrypt 인증서를 이용한 https 접속을 기본으로 하였다. http 서버 설정 템플릿은 Let’s Encrypt 인증서 도전4 대응과 http to https Redirect 만을 구현하였다. certbot을 이용한 Let’s Encrypt 인증서 발급은 webroot 모드로 진행하기 때문에 인증서 발급 전 반드시 각 도메인별 http 서버(80 Port)가 실행되어 있어야 한다.

추가로 SSL 통신 암호화를 위해 암호 키 교환 과정에 사용되는 2048bit 디피-헬먼 키 교환 (Diffie-Hellman Key Exchange) 알고리즘을 적용 하였다.

전체 실행 과정은 아래와 같다.

  1. 2048bit dhparam (디피-헬먼 키 교환 알고리즘에 사용되는 문자열)을 파일로 생성.

  2. https 설정 템플릿에 사용하기 위해 정해진 파일 이름으로 생성하도록 구현

  3. http 서버 템플릿을 이용해 Let’s Encrypt 인증서 발급을 위한 http 서버 실행.

  4. 실행 시 envsubst를 이용해 http 설정 템플릿 내 환경변수(도메인 이름)를 .env 파일의 값으로 대치한 결과를 NginX 설정 파일로 만들어 실행.

  5. Let’s Encrypt 인증서 발급.

  6. 실행 시 .env의 이메일, cert name 참조.

  7. 발급받은 인증서를 이용해 https 서버 실행.

  8. https 설정 템플릿에 1의 dhparam 파일 경로 추가(미리 정해진 파일 이름을 사전 입력).

  9. http 설정 템플릿과 https 설정 템플릿을 합친 후 Reverse Proxy 재시작

  10. 재시작 시 2.1 의 과정과 마찬가지로 envsubst를 이용해 환경변수(도메인 이름, 인증서 저장 경로)들을 .env 값으로 대치한 결과를 NginX 설정 파일로 만들어 실행.

  11. https 서버가 실행 완료되어 설정한 도메인 이름으로 접속 가능.

Let’s Encrypt 설정

Let’s Encrypt 인증서 발급/갱신을 담당하는 Docker 이미지인 certbot을 이용해 Let’s Encrypt 인증서의 발급과 갱신, 유효기간 확인을 할 수 있도록 하였다. certbot은 인증서 갱신 및 발급 시에만 동작하며 완료하면 자동으로 삭제되도록 하였으며 .env 파일의 환경변수를 이용한다.

인증서 발급

본 구현에서 인증서의 발급은 서버 실행 스크립트 내 포함되어 있으며 .env 파일 내 도메인, 인증서 발급자 이메일, 인증서의 cert name을 이용해 발급을 진행하도록 하였다.

인증서 갱신

짧은 인증서 유효기간(3개월)을 자동으로 갱신하는 갱신 스크립트를 만들었으며 아래와 같이 crontab에 등록하여 사용할 수 있다.

# 매주 일요일 한국시간 02:30 (UTC 17:30)
30 17 * * 0 /path/to/certbot-renew.sh

인증서 유효기간 확인

추가로 인증서의 유효기간을 확인할 수 있는 확인 스크립트를 별도로 구현하였다.

그 외 설정

App

테스트용 App을 Node.js로 구현하였다. DB 연결 결과와 서버 실행 결과를 출력하는 코드로 구성되어 있다.

phpmyadmin

MySQL을 웹에서 관리하기 위한 관리 도구이다. MySQL에 대한 의존성이 설정되어 있다.

MySQL

가장 많이 사용되는 데이터베이스며 my.cnf에서 MySQL 실행과 관련된 추가 설정이 가능하도록 하였다.

실행

Github 에 업로드된 프로젝트를 Clone 한 후 아래와 같이 실행한다.

  1. 환경변수 설정: .env.example 파일을 .env 파일로 생성.
  2. 프로젝트 내 쉘 스크립트(.sh) 파일들에 실행권한(+x) 부여.
  3. 실행 스크립트 실행.

결론

이 글은 Docker 기반의 NginX를 이용한 Reverse Proxy 설정과 NginX의 설정파일 중 프로젝트에 의존적인 도메인, 인증서 경로를 수동으로 입력한 점을 개선하여 자동화한 방법에 대한 내용이다. 또한 이 과정에서 Let’s Encrypt 인증서를 자동으로 적용하였다.

기존 Nginx Reverse Proxy를 설정할 때에는 http, https 설정이 미리 정의된 1개의 설정파일 템플릿을 이용하였었다. 인증서 발급받기 전 https 설정을 주석처리한 후 http 설정 항목에 도메인을 입력하여 서버를 실행하여 Let’s Encrypt 인증서를 발급받는다. 인증서 발급이 완료되면 주석처리된 https 설정 항목에 도메인과 인증서 저장경로를 입력하고 주석을 해제한 후 재실행하여 https 서버로의 실행을 완료하였었다. 이 과정이 반복되다보니 불편함을 느끼게 되었고 개선을 하게 되었으며 이 글은 개인의 고민에 대한 작은 결과이다.

Docker를 이용한 Reverse Proxy 설정은 이미 많이 알려져 있기 때문에 Docker를 알고 있다면 어렵지 않을 것이라 생각한다.

구현결과(소스 코드)는 아래 Github 링크를 참조 바란다.

Appendix 1: Proxy란

Proxy는 컴퓨터 용어의 의미로 클라이언트-서버의 요청-응답을 중개하여 처리하는 것5을 말한다. Proxy Server는 Proxy를 처리하는 서버를 말하며 크게 Forward Proxy와 Reverse Proxy가 있다6.

  • Forward Proxy: 클라이언트의 목적지와 요청을 대신 받아 목적지로 요청을 전달(Forward) 한 후 그 결과를 클라이언트에게 전달.
  • Reverse Proxy: 클라이언트의 목적지에서 요청을 받아 내부 서버(들)에서 처리한 결과를 클라이언트에게 전달.

본 글에서는 하나의 서버에 2개의 어플리케이션을 구성하므로 입구 역할을 하는 Reverse Proxy 설정을 이용하였다.

Appedix 2: NginX 설정 파일 내 환경변수 사용

NginX는 환경변수 값을 직접 사용하는 것을 지원하지 않지만, envsubst를 이용해 NginX 설정 파일 내 ${환경변수값} 와 같이 정의된 값을 환경변수의 값으로 치환한 후 새로운 파일로 만들어 사용하는 것을 소개하고 있다7.

NginX 환경설정 템플릿(default.template)이 envsubst 명령을 통해 사용되는 예시는 아래와 같다.

  1. NginX 환경설정 템플릿(default.template) 생성
### App (http)
server {
    listen 80;
    server_name ${APP_URL};
    ...
}
  1. .env 파일 생성 및 값 입력
APP_URL=example.com
  1. docker compose 설정파일(.yml)의 environment 항목에 Container에서 사용할 .env의 값과 실행 시 nginx 설정파일(default.template) 내 환경 변수를 치환한 후 새로운 파일을 생성하는(default.conf) envsubst 명령어 입력.
nginx-proxy:
  ...
  environment:
    - APP_URL=${APP_URL}

  command: >
    /bin/bash -c "
        envsubst \"`env | awk -F = '{printf \" $$%s\", $$1}'`\" < /etc/nginx/conf.d/default.template > /etc/nginx/conf.d/default.conf \
        && exec nginx -g 'daemon off;'
    "    
  ...
  1. docker compose 를 실행하면 설정파일(.yml) 내 enviroment의 ${APP_URL}이 .env 에 정의한 example.com 으로 대치되어 Container 내 환경변수로 등록. 이후 command의 envsubst 명령어를 통해 nginx 설정 템플릿(default.template)에 정의한 환경변수 값인 ${APP_URL}이 Container에 등록된 환경변수 값(APP_URL)으로 대치한 결과를 default.conf 파일로 생성. 최종적으로 .env 파일의 APP_URL 값이 default.template의 ${APP_URL} 값으로 대치되는 것과 같음. 대치된 결과인 default.conf는 아래와 같으며 NginX Container는 이를 실행.
### App (http)
server {
    listen 80;
    server_name example.com;
    ...
}

  1. Environment variables in Compose, Docker Docs, https://docs.docker.com/compose/environment-variables/#the-env-file, 2020.03.29 검색. ↩︎

  2. env(ironment) subst(itute). 환경변수에 정의한 값으로 대치하는 명령어. ↩︎

  3. 자세한 동작 과정은 Appendix 2 참조. ↩︎

  4. ACME Challenge↩︎

  5. 프록시 서버, 위키피디아, https://ko.wikipedia.org/wiki/프록시_서버, 2020.03.29 검색. ↩︎

  6. 포워드 프록시(Forward Proxy) 리버스 프록시(Reverse Proxy) 의 차이, 정광섭, https://www.lesstif.com/pages/viewpage.action?pageId=21430345, 2020.03.29 검색. ↩︎

  7. https://hub.docker.com/_/nginx의 Using environment variables in nginx configuration 항목 참조. ↩︎