전에는 깃허브 액션만을 이용해 원격으로 배포하는 걸 해보았는데, 이번에는 Docker를 살짝 끼얹어보기로 하자.
https://www.seankim.life/aws/1827/
FROM arm64v8/openjdk:17
ARG JAR_FILE=/build/libs/*.jar
COPY ${JAR_FILE} /deploy.jar
ENV AWS_REGION=ap-northeast-2
ENTRYPOINT ["java","-jar","-Dspring.profiles.active=prod", "/deploy.jar"]
./gradlew clean build -x test
docker build -t my-spring-app -f /Users/stella6767/IdeaProjects/free/DockerFile .
# nginx.conf 파일
# 컨테이너 내부에 /etc/nginx/conf.d/nginx.conf 경로에 존재
user nginx;
worker_processes auto; #CPU의 갯수를 알아서 파악
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024; #최대 worker_processes * worker_connections의 갯수로 처리할 수 있다.
}
http {
client_max_body_size 500M; #요청 바디 500MB로 제한
gzip on; #응답 압축
gzip_disable "msie6"; #IE6 지원 안함
gzip_min_length 500; #최소 사이즈를 지정하며 이보다 작은 파일은 압축하지 않음
gzip_buffers 16 8k; #버퍼의 숫자와 크기를 지정
gzip_comp_level 6; #압축 레벨 6
gzip_proxied any; #항상 압축함 https://www.lesstif.com/system-admin/nginx-gzip-59343019.html
gzip_types application/json; #컨텐츠의 유형에 따라 압축 여부를 설정
server_tokens off; #서버 정보를 노출하지 않음 (취약점 방지하기 위해)
#로드밸런싱으로 AWS private ip 대신 클라이언트의 ip를 가져올 수 있도록 함
# set_real_ip_from 172.31.0.0/16;
# real_ip_header X-Forwarded-For;
log_format main '$http_x_forwarded_for $time_local $request_method $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" $request_uri $request_id $request_time "$host"';
# map $http_user_agent $loggable {
# default 1;
# "ELB-HealthChecker/2.0" 0; #ELB health check의 경우 로깅하지 않음
# }
# official docs : https://www.nginx.com/blog/rate-limiting-nginx/
# ip는 binary 저장이 제일 공간적으로 효율적임
# zone={zoneName}:{zoneSize;1m(160,000)}
# (burst 지시자가 없는 경우) 10r/s의 의미는 초당10개를 허용한다는 의미지만 더 정확하게는 이전 요청보다 100ms 낮을 수 없다.
limit_req_zone $binary_remote_addr zone=accountZone:50m rate=20r/s;
server {
listen 80;
listen [::]:80;
server_name www.freeapp.life;
location /.well-known/acme-challenge/ {
allow all;
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
charset utf-8;
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name www.freeapp.life;
keepalive_timeout 65;
#burst 와 noddelay를 적절히 섞어야 함
# limit_req zone=accountZone burst=10 nodelay;
# limit_req_status 429; #too many request 에러
# 일반적으로 많이 사용되는 취약점 스캐너, 공격 TOOL에서 사용하는 USER-AGENT이다.
if ($http_user_agent ~* "Paros|ZmEu|nikto|dirbuster|sqlmap|openvas|w3af|Morfeus|JCE|Zollard|Arachni|Brutus|bsqlbf|Grendel-Scan|Havij|Hydra|N-Stealth|Netsparker|Pangolin|pmafind|webinspect") {
return 444;
}
# 인증서 등록 필요
ssl_certificate /etc/letsencrypt/live/www.freeapp.life/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.freeapp.life/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://web:8081;
proxy_redirect off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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-Host $host:443;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-Forwarded-Proto https;
}
location ~ /.well-known/acme-challenge/ {
root /var/www/certbot;
}
}
}
#!/bin/bash
if ! [ -x "$(command -v docker-compose)" ]; then
echo 'Error: docker-compose is not installed.' >&2
exit 1
fi
domains=www.너의도메인
rsa_key_size=4096
data_path="./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ㅛy
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 -f docker-compose-dev.yml 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 -f docker-compose-dev.yml up --force-recreate -d nginx
echo
echo "### Deleting dummy certificate for $domains ..."
docker-compose -f docker-compose-dev.yml 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 -f docker-compose-dev.yml 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 -f docker-compose-dev.yml exec nginx nginx -s reload
docker-compose -f docker-compose.dev.yml build // 이미지 빌드
docker-image ls // 도커 이미지 확인
docker-compose down : 도커 컨테이너를 중지 시키고 삭제 합니다.
docker-compose stop : 도커 컨테이너를 중지 시킵니다.
docker-compose start : 도커 컨테이너를 실행합니다.
docker-compose ps : 컨테이너 상태를 확인합니다.
docker-compose exec [servicename] [shell cmd] : 도커 컨테이너 접속
접속시 컨테이너 명이 아니고 .yml 파일에 작성한 서비스 명(ubuntu)입니다.
version: "3"
services:
nginx:
container_name: nginx
image: nginx
restart: always
ports:
- "80:80/tcp"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf # nginx.conf를 mount.
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
environment:
- TZ=Asia/Seoul
depends_on:
- web
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
certbot:
container_name: certbot
image: certbot/certbot:arm32v6-latest
restart: unless-stopped
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
web:
container_name: web
restart: always
build:
# context: .
#dockerfile: /Users/stella6767/IdeaProjects/free/DockerFile
dockerfile: /home/ubuntu/cicd/DockerFile
ports:
- "8081:8081"
environment:
- "spring_profiles_active=prod"
https://unix.stackexchange.com/questions/45781/shell-script-fails-syntax-error-unexpected
https://hbesthee.tistory.com/2098
whoami
echo " "
echo "========================"
echo "Path move"
echo "========================"
cd /home/ubuntu/cicd/
echo " "
echo "========================"
echo "Docker compose down"
echo "========================"
# 이미 실행 중인 Docker Compose 중지 및 컨테이너 삭제
sudo docker-compose -f /home/ubuntu/cicd/docker-compose-dev.yml down
#echo " "
#echo "========================"
#echo "Docker compose build"
#echo "========================"
#sudo docker-compose -f /Users/stella6767/IdeaProjects/free/docker-compose-dev.yml build
echo " "
echo "========================"
echo "Docker Compose Up"
echo "========================"
sudo docker-compose -f /home/ubuntu/cicd/docker-compose-dev.yml up -d
name: Java CI with Gradle
on:
workflow_dispatch:
inputs:
BRANCH:
description: 'Branch to use'
required: true
default: 'dev'
type: choice
options:
- dev
- prod
- kt
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
#ref: ${{ github.event.inputs.target-branch }} # 왜 안 먹히냐...
ref: ${{ inputs.branch }}
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Retrieve secrets
env:
MY_SECRETS_ARCHIVE: ${{ secrets.MY_SECRETS_ARCHIVE }}
run: |
echo "$MY_SECRETS_ARCHIVE" | base64 --decode > secrets.tar.gz
tar xzvf secrets.tar.gz -C src/main/resources
- name: Build with Gradle
run: ./gradlew clean build
shell: bash
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: cicdsample
retention-days: 1
path: |
build/libs/
scripts/
nginx/nginx.conf
DockerFile
docker-compose-dev.yml
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v2
with:
name: cicdsample
- name: Setup SSH
uses: webfactory/ssh-agent@v0.5.4
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Add remote server to known hosts
run: |
mkdir -p ~/.ssh
ssh-keyscan ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts
- name: check File List
run: |
pwd
ls -al
- name: SCP transfer
run: |
scp -r scripts build DockerFile docker-compose-dev.yml nginx ${{ secrets.SSH_USER }}@${{ secrets.SERVER_IP }}:~/cicd
- name: Execute remote commands
run: |
if [[ "${{ inputs.branch }}" == "dev" ]]; then
ssh -v ${{ secrets.SSH_USER }}@${{ secrets.SERVER_IP }} "sudo sh ~/cicd/scripts/deploy-docker.sh"
elif [[ "${{ inputs.branch }}" == "prod" ]]; then
ssh -v ${{ secrets.SSH_USER }}@${{ secrets.SERVER_IP }} "sudo sh ~/cicd/scripts/deploy-docker.sh"
else
echo "No specific script found for this"
fi
# delete-artifact
- uses: geekyeggo/delete-artifact@v2
with:
name: cicdsample
먼저 Execute remote commands를 실행하기 전에 init-letsencrypt를 먼저실행
물론 도메인을 구입하고 배포 인스턴스의 ip를 가리키는 도메인의 A 레코드가 있다는 것을 가정.
https://www.daleseo.com/docker-compose-networks/
https://github.com/twer4774/docker-nginx/blob/master/README.md
https://sonnson.tistory.com/45
https://bgpark.tistory.com/132
https://medium.com/geekculture/webapp-nginx-and-ssl-in-docker-compose-6d02bdbe8fa0
https://docs.docker.com/compose/networking/
https://bgpark.tistory.com/132
https://ninano1109.tistory.com/152
https://github.com/twer4774/docker-nginx/blob/master/README.md