Docker + Spring Boot + Nginx + React + EC2 배포 (feat.SSL,Let's encrypt)-(2)

Dave.kim·2023년 7월 20일

프로젝트

목록 보기
1/8

도메인 적용 및 Let's Encrypt를 통한 SSL인증(http->https)

시작하기에 앞서..

물론 Route53,ACM 같이 도메인관련 좋은 서비스는 많다. 하지만 나는 과금을 더 이상은 하기가 싫었다.
그래서 Let's encrypt를 사용해서 ssl인증을 진행했다. 그런데 이 과정이 너무나 빡셌다. 도커를 처음했던 내가 얕은지식으로 했던 것이 사고였다.

1. 도메인 매핑

이 부분은 엄청 쉽다.
나 같은 경우에는 가비아에서 www.jaetteoli.shop으로 도메인을 구입했다.

도메인을 ec2에 적용하는 방법은 간단하다.
1. ec2에 탄력적IP적용
2. ec2의 IP를 내 도메인의 A타입과 매핑해두기
끝이다.

이렇게하면 도메인과 인스턴스의 IP와 연결은 끝났다. 이제 nginx가 이 정보를 알 수 있도록 해야한다.

server {
    listen 80;
    server_name www.jaetteoli.shop;
    access_log off;

    location / {
        proxy_pass http://server:8081;
        proxy_set_header Host $host:$server_port;
        proxy_set_header X-Forwarded-Host $server_name;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
  • 여기서 server_name에 원래는 퍼블릭DNS가 적용되어 있었지만, www.jaetteoli.shop으로 그 내용을 바꿔주면 된다.

이렇게 하면 도메인 적용은 끝났다

  • 하지만 https가 적용되지 않은 상황이다.

https - Let's encrypt

순서는 다음과 같다

1. EC2 인바운드 규칙 - 443포트를 열어둔다.

2. docker-compose.yaml 파일을 수정한다.

version: '3'
services:
  server:
    container_name: server
    image: kimjiseop/jaetteoli-server
    expose:
      - 8081
    ports:
      - 8081:8081
    restart: "always"
    env_file:
      - .env
  nginx:
    container_name: nginx
    image: kimjiseop/jaetteoli-nginx
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    depends_on:
      - "server"
   
  certbot:
    image: certbot/certbot
    restart: unless-stopped
    volumes:
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    

여기서 중요하게 봐야할 부분은

ports:
      - 80:80
      - 443:443
  • 443포트에 대한 바인딩을 해줘야 한다는 점
volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot

여기서 볼륨 마운트 설정이 나오는데, 내가 이것 때문에 엄청 고생을 했다.. 왜냐하면, 볼륨 마운트 설정이 무엇인지도 모르고 추가를 했었기 때문이다.

꼭 볼륨마운트 설정하는 부분은 어느정도 개념을 숙지하고 와야한다. 무조건!

  • restart: unless-stopped 이 설정은 컨테이너가 수동으로 중지되지 않는 한 항상 자동으로 재시작되도록 하는 설정이다. 이전에는 always 옵션이였지만, 이번에는 수정을 했다.

  • volumes: 호스트와 컨테이너 사이에 볼륨을 설정한다. 일전에 말했던 볼륨마운트 설정이다. 볼륨은 데이터를 컨테이너가 아닌 호스트 시스템(여기서는 EC2)에 저장하게 해서, 컨테이너가 삭제되거나 재시작될 때도 데이터가 유지되도록 하는 설정이다.

    • 중요한 점은, EC2와 nginx컨테이너의 디렉토리를 공유한다는 점이다. 예를 들어서, ./nginx/conf.d:/etc/nginx/conf.d라고 설정을 해놨다면, EC2 시스템 내의 .(작업 디렉토리)/nginx/conf.d 디렉토리를 Nginx 컨테이너 내부의 /etc/nginx/conf.d 디렉토리와 연결이 지어진다고 생각하면 된다.
    • 그리고 중요하게 생각해야 할 것은, 이렇게 볼륨마운트 설정을 하고나면, 로컬에서 nginx.conf를 편집해서 배포를 아무리해도, 배포된 Nginx 컨테이너 내부의 conf.d에는 방금 로컬에서 배포한 nginx.conf가 위치하는 것이 아니라, 무조건 EC2의 ./nginx/conf.d 환경으로 덮어지게 되는 것이다. -> 내가 이것 때문에 고생을 많이 한 것 같다.. 정말 처음에는 이해하기 어려웠다.
    • 쉽게 말하면, 그냥 로컬에서 편집하는게 아니라 EC2에서 편집을 해야하는 상황이 온 것이다~ 라고 할 수 있다.
  • image: certbot/certbot certbot/certbot 이미지를 사용하는데, 이 이미지는 Let's Encrypt의 Certbot을 포함하고 있다.

  • volumes: ... Let's Encrypt의 설정 및 인증서 정보를 저장할 볼륨을 설정한다. 이렇게 하면 인증서 정보가 컨테이너 외부에 유지되므로, 컨테이너가 재시작되거나 삭제될 때도 인증서 정보가 보존된다고 보면 된다.

    • 그리고 이는 nginx 컨테이너에도 똑같이 적용해두어야 ssl이 적용될 수 있다.

3. 로컬에서 nginx.conf 수정 후 배포

server {
     listen 80;
     listen [::]:80;

     server_name www.jaetteoli.shop;

     location /.well-known/acme-challenge/ {
             allow all;
             root /var/www/certbot;
     } 
}

그리고나서,

docker-compose -f docker-compose.yaml up -d
  • 이 명령어를 수행해야 하지만, 나는 github actions를 이용하여 먼저 git push를 진행했다.
  • 이 과정이 끝나고나면, 이제 EC2에서 작업을 하면된다.

4. 더미인증서 발급 후 처분 & 실 인증서 발급

curl -L https://raw.githubusercontent.com/wmnnd/nginx-certbot/master/init-letsencrypt.sh > init-letsencrypt.sh
chmod +x init-letsencrypt.sh
vi init-letsencrypt.sh
sudo ./init-letsencrypt.sh
  • 인증서 발급받는 스크립트를 다운로드하고 도메인, 이메일 주소, 디렉터리를 변경해야한다.
  • 특히나 디렉터리 구조를 본인의 환경에 맞게 설정해야 한다.
  • 이 과정이 성공하면.. 솔직히 다왔다.
  • 나는 이 과정에서 계속해서 허가를 받지못했는데 그 이유가 바로! 볼륨마운트 설정을 이상하게 해놨어서다..

5. nginx.conf파일 수정

이제 인증서를 발급받았으니 HTTPS를 적용할 수 있다.EC2내에서 nginx.conf를 수정해야 한다.
vi 혹은 vim을 쓰면되지만, 혹시라도 read-only 경고가 뜨면서 수정이 안될 때를 대비하여 sudo로 nginx.conf를 수정하면 된다.

server {
    listen 80;
    listen [::]:80;
    server_name www.jaetteoli.shop;
    access_log off;

    location /.well-known/acme-challenge/ {
        allow all;
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    server_name www.jaetteoli.shop;

    ssl_certificate /etc/letsencrypt/live/www.jaetteoli.shop/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.jaetteoli.shop/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_pass http://server:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Host $server_name;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
  • location /.well-known/acme-challenge/

    • 이 블록은 Let's Encrypt에서 SSL 인증서를 자동으로 발급받기 위한 설정인데, Let's Encrypt는 ACME(Automatic Certificate Management Environment) 프로토콜을 이용해서 도메인의 소유권을 확인한다.
    • 이 과정에서 /.well-known/acme-challenge/ 경로에 특정한 정보를 배치하게 되는데, 이 설정은 그 요청을 처리하는 부분이라고 보면된다.
  • return 301 https://$host$request_uri; 80포트로 들어오는 HTTP 요청을 HTTPS로 리다이렉트하는 부분이다. 이를 통해 모든 트래픽이 암호화되도록 강제하는 것이다. 301은 HTTP 상태 코드로 "Moved Permanently"를 나타내며, $host는 요청이 도착한 서버의 이름, $request_uri는 요청의 URI를 의미한다.

6. 인증서 자동 갱신

  • 인증서를 자동으로 갱신하도록 설정하지 않으면 만료될 때마다 다시 발급을 해주어야 하기 때문에, 자동으로 인증서가 갱신되도록 docker-compose.yaml 파일을 수정해야 한다.
version: '3'
services:
  server:
    container_name: server
    image: kimjiseop/jaetteoli-server
    expose:
      - 8081
    ports:
      - 8081:8081
    restart: "always"
    env_file:
      - .env
  nginx:
    container_name: nginx
    image: kimjiseop/jaetteoli-nginx
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    depends_on:
      - "server"
    command : "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"

  certbot:
    image: certbot/certbot
    restart: unless-stopped
    volumes:
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
  • 여기서 어떤 부분을 잘 봐야하냐면..
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;'"
  • 이 두개를 잘 봐야한다

  • command : "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"

    • 이 명령은 무한루프를 실행하면서 6시간마다 Nginx를 재시작하는 작업을 수행한다.

    • sleep 6h & wait $${!} 현재 스크립트를 6시간동안 멈추는 명령.

    • nginx -s reload Nginx 서버를 재시작.

    • nginx -g \"daemon off;\" Nginx를 데몬 모드가 아닌 포어그라운드에서 실행한다. 이는 Docker 컨테이너의 특성상 백그라운드에서 실행되는 데몬 프로세스가 컨테이너의 생명주기를 제어하면 안 되기 때문이라고 한다.

  • entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

    • 이 명령은 무한루프를 실행하면서 12시간마다 Certbot를 이용해 SSL 인증서를 갱신하는 작업을 수행한다.

    • trap exit TERM TERM 시그널을 받으면 스크립트를 종료한다. Docker가 컨테이너를 종료할 때는 보통 TERM 시그널을 보내므로, 이 명령을 사용하면 Docker가 컨테이너를 정상적으로 종료할 수 있다.

    • certbot renew SSL 인증서를 갱신한다.

    • sleep 12h & wait $${!} 현재 스크립트를 12시간동안 멈춘다.


  • 성공적으로 https설정이 된 것을 확인할 수 있다.

  • 이 내용은 내가 참고했던 블로그에서 써주신 글인데, 처음에는 이걸 보면서 쉽지않다는 생각을 했었다.
  • nginx환경을 ec2에서 직접 세팅한 것이 아니라, 컨테이너를 ec2에 배포해둔 상태이기 때문에 당연한 모순인 것 같다는 생각이 든다.

내가 참고했던 블로그

다음 글 : React 컨테이너 배포 및 프록시

1개의 댓글

comment-user-thumbnail
2023년 7월 20일

잘 읽었습니다. 좋은 정보 감사드립니다.

답글 달기