Docker Compose (SpringBoot + Nginx + SSL) + Github Action

공부는 혼자하는 거·2023년 10월 3일
0

Spring Tip

목록 보기
50/52

전에는 깃허브 액션만을 이용해 원격으로 배포하는 걸 해보았는데, 이번에는 Docker를 살짝 끼얹어보기로 하자.

Docker 설치

https://www.seankim.life/aws/1827/

DockerFile 작성

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

# 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;
        }


    }

}

init-letsencrypt.sh

#!/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

기본 명령어 개념

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)입니다. 

Compose Script

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

Deploy Script

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

Github Action

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://velog.io/@lijahong/0%EB%B6%80%ED%84%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-Docker-%EA%B3%B5%EB%B6%80-Docker-Compose-wordpress-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0

https://www.daleseo.com/docker-compose-networks/

https://github.com/twer4774/docker-nginx/blob/master/README.md

https://jforj.tistory.com/226

https://sonnson.tistory.com/45

https://dev.gmarket.com/80

https://bgpark.tistory.com/132

https://medium.com/geekculture/webapp-nginx-and-ssl-in-docker-compose-6d02bdbe8fa0

https://medium.com/@su_bak/docker-compose%EC%97%90%EC%84%9C-%EC%84%9C%EB%A1%9C-%EB%8B%A4%EB%A5%B8-container%EA%B0%80-%EA%B0%99%EC%9D%80-volume%EC%9D%84-%EA%B3%B5%EC%9C%A0%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-5e49430c5282

https://qspblog.com/blog/SSL-%EC%9D%B8%EC%A6%9D-%EB%B0%9B%EA%B8%B0-docker-%EC%82%AC%EC%9A%A9-certbot-%EC%9C%BC%EB%A1%9C-certificates-%EB%B0%9B%EA%B8%B0-https%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-php%EC%99%80-nginx-%EC%9B%B9%EC%84%9C%EB%B2%84

https://docs.docker.com/compose/networking/

https://velog.io/@zero-black/Docker-compose-certbot-nginx-%EB%A1%9C-SSL-%EC%9D%B8%EC%A6%9D%EC%84%9C-%EB%B0%9C%EA%B8%89%ED%95%98%EA%B8%B0

https://bgpark.tistory.com/132

https://ninano1109.tistory.com/152

https://github.com/twer4774/docker-nginx/blob/master/README.md

profile
시간대비효율

0개의 댓글