[Docker] Nginx 구성과 HTTPS 설정
📕 목차
1. 사전 준비
2. nginx-certbot 설정
3. nginx 설정
4. Docker-compose
1. 사전 준비
📌 Introduce
nginx도 모르는 상태에서 container로 띄워서 https까지 적용해본다고 제법 애먹었다.
docker에 대해서는 지식이 필요하고, 없어도 똑같이 따라하면 어떻게든 되겠지만 원래 블로그 찾아서 적용하면 나는 안 되는 법칙이 있지 않던가.
문제가 발생해도 스스로 해결하길 원한다면 기본적인 docker의 container 원리와 스크립트 작성법 정도는 있어야 한다.
nginx도 막상 써보면 그렇게 어렵진 않은데, 한 번도 안 써보고 이걸 따라하려면 많이 힘들 예정.
나는 http 적용하는 것까지만 로컬 환경의 컨테이너로 띄워서 동작하는 걸 확인해본 후에, 본격적으로 server에서 https 변환 작업을 수행했다.
(근데 nginx 설정 조금만 건들면 툭하면 터져서 좀 화나긴 함)
이제 약 8시간 동안 혼자 죽쒀서 겨우 성공한, 눈물 겨운 https 변환기를 적어보려 한다.
📌 도메인 구매
- https를 적용하려면 도메인을 하나 구매해야 한다
- .com 도메인이 가장 좋긴 하지만 짧은 uri는 죄다 비싸서, co.kr이나 .kr을 사는 걸 권장한다. .org도 낫 밷
📌 Cloud에 Domain 등록
나는 NCP를 사용 중이라서, NCP 기준으로 작성했다.
AWS나 다른 클라우드에서도 DNS 관련 서비스에 구매한 도메인을 연결해주면 된다.
1️⃣ Service > Global DNS 클릭
2️⃣ 도메인 추가
3️⃣ 레코드 추가
- 레코드 값에는 서버 공인 아이피 주소를 적어주면 된다.
- @와 www를 기본 호스트로 넣어주고, 서버를 따로 배포했다면 api도 호스트로 추가해준다.
4️⃣ 확인
5️⃣ 가비아에 네임 서버 등록
- 가비아에서 도메인 관리 > 홈 > 전체 도메인 > 도메인 상세 페이지 > 네임서버 설정
- 기존의 NCP에 등록된 네임 서버를 1, 2차에 넣어주면 된다.
📌 OpenSSL 인증서 발급 (letsencrypt)
sudo apt update
sudo apt upgrade -y
sudo apt-get install letsencrypt -y
sudo apt install certbot python3-certbot-nginx
- Let's Encrypt란 CA 중 하나로, 무료로 SSL 인증서를 발급해주지만 유효기간이 90일이라 재발급을 받아야 하고, 매주 발급받을 수 있는 인증서가 개수 한정되어 있어서 아무 생각없이 무한 발급하면 정지당한다.
- Certbot은 Let's Encrypt에서 SSL 인증서를 발급받는 자동화 툴이다. 오픈소스에, Docker에 공식 Image까지 존재한다.
- 설치하다가 nginx 관련 오류 나면, 기존에 켜져있는 nginx가 있을 수도 있으니 꺼주면 된다.
🟡 참고용
systemctl status nginx.service
systemctl stop nginx.service
ngnix가 아예 로컬에서 돌아가고 있으면, 위 명령어로 확인해보고 종료하면 된다.
기존에 동작하고 있던 서버에서 docker container에 nginx를 띄우고 있었다면, 그것도 종료해주자.
📌 OpenSSL 인증서 발급 (letsencrypt)
sudo letsencrypt certonly -a standalone -d [발급받을 도메인]
- 발급받을 도메인은 `example.com`, `www.example.com` 등 본인의 서버 도메인 주소를 기입하면 된다.
- 아래에서 사용할 cerbot-nginx에서 발급받아도 되긴 한다는데, 나는 이거 했더니 계속 에러 떠서 따로 발급 받았다.
- 실행하면 긴급 통보 혹은 키 복구를 위한 이메일을 적으라고 하는데, 실제로 사용되니 사용하는 이메일을 적으면 된다
- 이후 약관 동의는 읽어보고 Y, N 중에 선택하면 된다.
- 완료하면 `/etc/letsencrypt/live/도메인` 경로에 pem 키가 생긴다.
- 참고로 얘넨 심볼릭 링크라 실제 파일은 `/etc/letsencrypt/archive/도메인`에 있다.
- 반드시 명령어를 실행한 후에 키가 발급되어 있어야 한다.
2. nginx 설정
📌 nginx란?
Spring Boot를 띄워놓은 WAS를 전면에 내놓아도 상관은 없지만, 트래픽이 증가하는 경우 문제가 될 수 있다.
정적 파일을 조회하는 것처럼 굳이 DB를 조회하지 않아도 되는 경우엔 proxy에서 처리해주는 게 이득이다.
또한 log 수집이나 권한 제어같은 기능들을 수행할 수 있다.
📌 nginx.conf vs default.conf
처음에 이 둘 중 어떤 파일을 건드려야 할 지 몰라서 엄청 해멨다.
누구는 nginx.conf를 작성하고 있고, 누구는 default.conf를 수정하고 있어서 둘 다 수정했더니 별의 별 에러가 다 뜸.
짧게 요약하면 default.conf에서 정의한 server 블록이 nginx.conf에 삽입된다.
따라서 nginx 전체 시스템의 설정을 커스텀할 것이 아니라면 default.conf만 작성해주면 된다.
📌 default.conf 작성
upstream docker-server {
server server:8080;
}
server {
listen 80;
server_name {domain 주소};
access_log /var/log/nginx/access.log;
location /.well-known/acme-challenge/ {
allow all;
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name {domain 주소};
ssl_certificate /etc/letsencrypt/live/fitapet.co.kr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/fitapet.co.kr/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
access_log /var/log/nginx/access.log;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html =404;
}
location /api {
proxy_pass http://docker-server;
proxy_http_version 1.1;
proxy_redirect off;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
- upstream
- `/api` 경로로 요청이 들어왔을 때, 요청을 처리할 주소를 명시한다.
- upstream의 대상 주소는 도메인 주소가 아니라, server를 띄운 container의 service 명을 적어야 한다.
- 80 포트
- `/.well-known/acme-challenge/`는 cerbot이 발급한 challenge 파일을 nginx가 서빙하는 경로
- Let's Encrypt가 challenge라는 것을 사용해서 domain 이름을 제어하는지 검증하기 위한 설정이다.
- challenge에 성공하면 `http://도메인명/.well-known/acme-challenge/토큰` 형태로 토큰을 저장하고, 해당 토큰이 있어야 Let's Encrypt에서 올바른 응답을 받고 유효성 검증에 성공할 수 있다.
- 이외의 모든 80 포트의 요청은 443번 포트로 redirect하도록 설정했다.
- 443 포트
- 인증을 위한 fullchain과 privkey를 연결해준다.
- 참고로 해당 경로는 local filesystem 경로가 아니라 docker container 내의 경로에 해당한다.
- 따라서 나중에 key가 저장되어 있는 경로와 docker container의 key 경로의 volume을 설정해주어야 한다.
블로그를 뒤져보면 `listen [::] 443`과 같은 구문을 추가하기도 하는데, 이건 ipv6 주소를 연결할 때 사용한다.
멋도 모르고 썼다가 계속 nginx container가 죽어버렸는데, ipv6를 지원하지 않으면 제거해야 한다.
3. nginx certbot 설정
📌 nginx-certbot 설치
git clone https://github.com/wmnnd/nginx-certbot.git .
cd ./nginx-certbot
vi ./nginx-certbot # email과 domains 수정
./init-letsencrypt.sh
- github에서 확인해보면 init-letsencrypt.sh 내용을 수정하고 실행하면 알아서 key를 발급받아준다고 한다.
- 나는 nginx container를 실행시킬 때, 계속 알 수 없는 에러가 발생해서 그냥 접었다.
- 해당 경로에 들어가보면 docker-compose script가 정의되어 있는데, 이를 참고해서 서버를 띄우기 위한 docker-compose 파일을 작성할 때 참고할 수 있다.
📌 nginx와 cerbot의 command, entrypoint
command: '/bin/sh -c ''while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g "daemon off;"'''
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
- docker-compose 파일을 확인해보면 왜 있는지 모를 두 가지 명령어가 존재한다. 이는 인증서가 만료될 때쯤 자동으로 SSL 인증서 갱신을 위한 커맨드에 해당한다.
- nginx의 background에서 6시간 마다 구성 및 인증서를 다시 로드하고, daemon off로 foreground의 nginx를 실행한다.
- cerbot은 Let's Encrypt 권장 사항에 맞춰 12시간마다 갱신하는 명령어를 사용한다.
- 실제로 cerbot이 12시간마다 인증서를 갱신하진 않는다. (주에 발급 받을 수 있는 횟수가 제한되어 있으므로)
- 갱신하기 전에 인증서가 30일 이내에 만료될 예정인 경우에만 갱신하도록 동작한다.
4. Docker compose
📌 docker-compose script
version: '3.7'
services:
certbot:
container_name: certbot
image: certbot/certbot
restart: unless-stopped
volumes:
- /letsencrypt/certbot/conf:/etc/letsencrypt
- /letsencrypt/certbot/www:/var/www/certbot
depends_on:
- nginx
networks:
- was-net
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
nginx:
image: nginx:1.15-alpine
container_name: nginx-proxy
restart: always
volumes:
- ./proxy/conf.d/default.conf:/etc/nginx/conf.d/default.conf
- /letsencrypt/certbot/conf:/etc/letsencrypt
- /letsencrypt/certbot/www:/var/www/certbot
- /var/log/nginx:/var/log/nginx/
ports:
- "80:80"
- "443:443"
networks:
- was-net
depends_on:
- server
command: '/bin/sh -c ''while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g "daemon off;"'''
server:
image: {server image 경로}
container_name: {server container 이름}
restart: unless-stopped
expose:
- "8080"
env_file:
- .env
environment:
- TZ=Asia/Seoul
networks:
- was-net
networks:
was-net:
external: true
- cerbot과 nginx는 깃헙에서 참고한 docker-compose 기반으로 작성했다.
- 반드시 경로를 맞춰주자.
- 기존에 발급받은 인증서는 `/etc/letsencrypt/live/도메인명`에 위치하는데, `/etc/letsencrypt` 경로의 모든 파일을 `/letsencrypt/certbot/conf`로 복사했다.
- options-ssl-nginx.conf 파일도 따로 정의를 해주어야 한다고 한다. 이것도 깃헙에서 받아온 파일의 conf를 찾아보면 있으니, 위 경로에 복사해주었다.
- nginx 설정을 해둔 default.conf는 직접정인 파일명 경로를 잡아주었다. 디렉토리명으로 지정하면 수정하면 안 되는 파일들의 권한도 건드는 경우가 있어 에러가 날 수 있다.
- server라는 service 명은 반드시 default.conf에서 선택한 upstream의 service명과 일치하도록 해야 한다.
다 쓰고 보니 별 거 아닌 거 같은 내용인데, https 적용해본다고 너무 애먹어서 일단 생각나는 대로 정리했다.