http에서는 쿠키가 적용되지 않아 SSL을 적용하려 한다. docker-compose, nginx, springboot 환경에서 무료로 SSL 인증서를 적용하고 shell script를 통해 인증서 유효기간이 만료되기 전에 자동으로 기간을 연장해보자.
version: '3'
services:
backend:
container_name: backend
image: {springboot 이미지 이름}
ports:
- "8080:8080"
user: "1000:1000"
networks:
- anifriends
volumes:
- /home/ec2-user/logs:/logs
nginx:
image: {nginx 이미지 이름}
container_name: nginx
volumes:
- ./nginx/:/etc/nginx/
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
ports:
- "80:80"
- "443:443"
depends_on:
- backend
networks:
- anifriends
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
certbot:
container_name: 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;'"
networks:
- anifriends
redis:
container_name: redis
image: redis:latest
depends_on:
- backend
ports:
- "6379:6379"
networks:
- anifriends
networks:
anifriends:
volumes:
logs:
certbot: # 추가
container_name: 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;'"
networks:
- anifriends
certbot 컨테이너
추가nginx
에 certbot
에 대한 volume 2개
추가certbot
이 발급한 인증서를 nginx
가 브라우져의 요청에 따라 반환할 수 있어야 하므로 certbot
컨테이너와 nginx
컨테이너는 같은 폴더를 공유해야 한다.nginx
에 443 포트 열기nginx의 command
, certbot의 entrypoint
는 인증서가 만료되기 전에 자동으로 SSL 인증서를 재발급해준다.events {}
http {
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://backend:8080;
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;
}
}
}
events {
worker_connections 1024;
}
https {
upstream was {
server backend:8080; #서비스명
}
server {
listen 80;
server_name dev.anifriends.site; # 발급한 도메인 주소
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot; # Certbot을 통해 Let's Encrypt 인증서를 발급받을 때 사용하는 경로
}
location / {
return 301 https://$host$request_uri; # 모든 HTTP 요청을 HTTPS로 리다이렉션
}
}
server {
listen 443 ssl;
server_name dev.anifriends.site;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/dev.anifriends.site/fullchain.pem; # SSL/TLS 인증서 경로
ssl_certificate_key /etc/letsencrypt/live/dev.anifriends.site/privkey.pem; # SSL/TLS 개인 키 경로
include /etc/letsencrypt/options-ssl-nginx.conf; # Let's Encrypt에서 제공하는 Nginx SSL 옵션
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://was;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
#!/bin/bash
if ! [ -x "$(command -v docker-compose)" ]; then
echo 'Error: docker-compose is not installed.' >&2
exit 1
fi
domains={발급받은 도메인 주소}
rsa_key_size=4096
data_path="./data/certbot" # certbot 폴더 경로
email="이메일 주소" # Adding a valid address is strongly recommended
staging=0 # Set to 1 if you're testing your setup to avoid hitting request limits
if [ -d "$data_path" ]; then
read -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision
if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then
exit
fi
fi
if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then
echo "### Downloading recommended TLS parameters ..."
mkdir -p "$data_path/conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem"
echo
fi
echo "### Creating dummy certificate for $domains ..."
path="/etc/letsencrypt/live/$domains"
mkdir -p "$data_path/conf/live/$domains"
docker-compose run --rm --entrypoint "\
openssl req -x509 -nodes -newkey rsa:$rsa_key_size -days 1\
-keyout '$path/privkey.pem' \
-out '$path/fullchain.pem' \
-subj '/CN=localhost'" certbot
echo
echo "### Starting nginx ..."
docker-compose up --force-recreate -d nginx
echo
echo "### Deleting dummy certificate for $domains ..."
docker-compose run --rm --entrypoint "\
rm -Rf /etc/letsencrypt/live/$domains && \
rm -Rf /etc/letsencrypt/archive/$domains && \
rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot
echo
echo "### Requesting Let's Encrypt certificate for $domains ..."
#Join $domains to -d args
domain_args=""
for domain in "${domains[@]}"; do
domain_args="$domain_args -d $domain"
done
# Select appropriate email arg
case "$email" in
"") email_arg="--register-unsafely-without-email" ;;
*) email_arg="--email $email" ;;
esac
# Enable staging mode if needed
if [ $staging != "0" ]; then staging_arg="--staging"; fi
docker-compose run --rm --entrypoint "\
certbot certonly --webroot -w /var/www/certbot \
$staging_arg \
$email_arg \
$domain_args \
--rsa-key-size $rsa_key_size \
--agree-tos \
--force-renewal" certbot
echo
echo "### Reloading nginx ..."
docker-compose exec nginx nginx -s reload
신경써야하는 부분 예시
domains=dev.anifriends.site
rsa_key_size=4096
data_path="./data/certbot"
email="test@gmail.com" # Adding a valid address is strongly recommended
staging=0
docker-compose.yml
의 volumes의 data_path
와 일치시켜야 한다.빌드를 반복하면 Let’s Encrypt에서는 Request Limits를 걸고 있는데, 일정 요청을 초과하면 당분간 해당 도메인 주소에 SSL을 발급하지 못하게 된다.
docker-compose up --build
./init-letsencrypt.sh
springboot가 실행 중이어야 Fetching Connection refused
에러가 발생하지 않는다.
build 시 ssl 인증서가 없어서 nginx가 종료된 다음 shell 파일을 실행하면 nginx가 다시 연결된다.
Existing data found for dev.anifriends.site. Continue and replace existing certificate? (y/N) y
### Creating dummy certificate for dev.anifriends.site ...
[+] Building 0.0s (0/0) docker:default
[+] Building 0.0s (0/0) docker:default
Generating a RSA private key
.........................................................................................................................++++
...............................................................................................................................++++
writing new private key to '/etc/letsencrypt/live/dev.anifriends.site/privkey.pem'
-----
### Starting nginx ...
[+] Building 0.0s (0/0) docker:default
[+] Running 2/2
✔ Container backend Running 0.0s
✔ Container nginx Started 0.0s
### Deleting dummy certificate for dev.anifriends.site ...
[+] Building 0.0s (0/0) docker:default
[+] Building 0.0s (0/0) docker:default
### Requesting Let's Encrypt certificate for dev.anifriends.site ...
[+] Building 0.0s (0/0) docker:default
[+] Building 0.0s (0/0) docker:default
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for dev.anifriends.site
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/dev.anifriends.site/fullchain.pem
Key is saved at: /etc/letsencrypt/live/dev.anifriends.site/privkey.pem
This certificate expires on 2024-02-25.
These files will be updated when the certificate renews.
NEXT STEPS:
- The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.
We were unable to subscribe you the EFF mailing list because your e-mail address appears to be invalid. You can try again later by visiting https://act.eff.org.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
* Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
* Donating to EFF: https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
### Reloading nginx ...
2023/11/27 20:10:57 [notice] 11#11: signal process started
인증서를 발급받았으면 docker-compose up --build
만 실행하면 된다. 이후에 shell 파일은 실행하지 않아도 된다.
Certbot failed to authenticate some domains (authenticator: webroot). The Certificate Authority reported these problems:
Domain: dev.anifriends.site
Type: connection
Detail: {ec2 ip}: Fetching http://dev.anifriends.site/.well-known/acme-challenge/GobZNeHSTjHfFAETZGqphDyU6oAZf2gI9Egw56g1NSY: Connection refused
/data/certbot
에 쓰기 권한이 없어서 SSL .pem 파일이 생성이 되지 않았다.
chmod -R 777 data/
-R 옵션을 통해 하위 폴더에도 권한을 부여했다.
docker rmi $(docker images -q)
존재하는 이미지를 삭제하고 다시 빌드해서 변경된 이미지를 적용했다.
staging=0일 때, 기존 경로는 anifriends.kro.kr
이었는데 파일이 여러 번 생성될 경우 자동으로 기존 경로 + -0001
을 붙인다. nginx.conf 파일에서 ssl_certificate
, ssl_certificate_key
부분의 경로가 다르면 에러가 발생하므로 확인하자.
https://zinirun.github.io/2021/03/31/docker-nginx-certbot/
https://velog.io/@zero-black/Docker-compose-certbot-nginx-로-SSL-인증서-발급하기
https://pentacent.medium.com/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71