DevOps/Docker & Kubernetes

[Docker] Nginx 구성과 HTTPS 설정

나죽못고나강뿐 2024. 1. 1. 15:01
📕 목차

1. 사전 준비
2. nginx-certbot 설정
3. nginx 설정
4. Docker-compose

1. 사전 준비

 

📌 Introduce

nginx도 모르는 상태에서 container로 띄워서 https까지 적용해본다고 제법 애먹었다.

docker에 대해서는 지식이 필요하고, 없어도 똑같이 따라하면 어떻게든 되겠지만 원래 블로그 찾아서 적용하면 나는 안 되는 법칙이 있지 않던가.

문제가 발생해도 스스로 해결하길 원한다면 기본적인 docker의 container 원리와 스크립트 작성법 정도는 있어야 한다.

 

nginx도 막상 써보면 그렇게 어렵진 않은데, 한 번도 안 써보고 이걸 따라하려면 많이 힘들 예정.

나는 http 적용하는 것까지만 로컬 환경의 컨테이너로 띄워서 동작하는 걸 확인해본 후에, 본격적으로 server에서 https 변환 작업을 수행했다.

(근데 nginx 설정 조금만 건들면 툭하면 터져서 좀 화나긴 함)

 

이제 약 8시간 동안 혼자 죽쒀서 겨우 성공한, 눈물 겨운 https 변환기를 적어보려 한다.

 

📌 도메인 구매
 

웹을 넘어 클라우드로. 가비아

그룹웨어부터 멀티클라우드까지 하나의 클라우드 허브

www.gabia.com

  • 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 설치
 

GitHub - wmnnd/nginx-certbot: Boilerplate configuration for nginx and certbot with docker-compose

Boilerplate configuration for nginx and certbot with docker-compose - GitHub - wmnnd/nginx-certbot: Boilerplate configuration for nginx and certbot with docker-compose

github.com

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시간마다 갱신하는 명령어를 사용한다.

 


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 적용해본다고 너무 애먹어서 일단 생각나는 대로 정리했다.