- [CD] λΈλ£¨/κ·Έλ¦° 무μ€λ¨ λ°°ν¬ κ΅¬ννκΈ° 2νΈ (GitHub Actions/Shell Script)2024λ 01μ 08μΌ 00μ 02λΆ 16μ΄μ μ λ‘λ λ κΈμ λλ€.μμ±μ: @kimyu0218
π [CD] λΈλ£¨/κ·Έλ¦° 무μ€λ¨ λ°°ν¬ κ΅¬ννκΈ° 1νΈ (NGINX/Docker Compose)
μ΄λ² ν¬μ€ν μμλ 1νΈμμ μμ±ν λ컀 μ»΄ν¬μ¦ νμΌμ λ°νμΌλ‘ λΈλ£¨/κ·Έλ¦° 무μ€λ¨ λ°°ν¬λ₯Ό ꡬνν κ²μ΄λ€.
GitHub Actionsλ?
κΉν μ‘μ μ κΉνμμ μ 곡νλ μλν λꡬλ‘, λΉλ, ν μ€νΈ, λ°°ν¬ νμ΄νλΌμΈμ μλνν μ μλ€. κΉν μ‘μ μ νΉμ μ΄λ²€νΈκ° λ°μνλ©΄ μ‘μ μ μλμΌλ‘ μ€ννκ² λλλ°, μ΄λ₯Ό νμ©νμ¬ νΉμ λΈλμΉμ μ½λκ° push λμμ λ μ΄ν리μΌμ΄μ μ λ°°ν¬ν μ μλ€. μ΄μΈμλ λ ν¬μ§ν 리μ μ΄μκ° μμ±λ λλ§λ€ μλμΌλ‘ μ μ ν λΌλ²¨μ λΆμ¬μ£Όλ λ±μ μν¬νλ‘μ°λ₯Ό λ§λ€ μ μλ€.
workflow
μν¬νλ‘μ°λ νλ μ΄μμ μμ μ μ€ννλ μλνλ νλ‘μΈμ€λ€. `.github/workflows` λλ ν 리μ YAML νμΌλ‘ μμ±νλ©° λ ν¬μ§ν 리μ μ΄λ²€νΈκ° λ°μνλ©΄ μ€νλλ€.
runner
λ¬λλ μν¬νλ‘μ°λ₯Ό μ€ννλ μλ²λ‘, ν λ²μ νλμ μμ μ μννλ€.
π¨ λμΌν λ¬λλ₯Ό μ¬μ©νλ μμ λ΄μμλ λ°μ΄ν°λ₯Ό 곡μ ν μ μλ€. νμ§λ§ νκ²½λ³μ λ±μ νμ©νμ¬ μ¬λ¬ μμ κ°μ λ°μ΄ν°λ₯Ό 곡μ ν μλ μλ€.
job
μμ μ μν¬νλ‘μ° λ΄μμ μ€νλλ κ°λ³ μμ μ΄λ€. μμ μ λ³λ ¬μ μΌλ‘ μ€νλλλ° λ€λ₯Έ μμ μ΄ μ νλμ΄μΌ νλ κ²½μ°, `needs` μ΅μ μ νμ©νμ¬ μμ μ΄ μμ°¨μ μΌλ‘ μ€νλλλ‘ κ΅¬νν μ μλ€.
step
λ¨κ³λ μμ λ΄μμ μ€νλλ κ°λ³ μ‘μ μ μλ―Ένλ€.
action
μ‘μ μ μΌμ’ μ μ¬μ©μ μ΄ν리μΌμ΄μ μ΄λ€. μ§μ μ‘μ μ μμ±ν μλ μμ§λ§ κΉνλΈμ λ§μΌ νλ μ΄μ€μμ μ‘μ μ κ°μ Έμ μ¬μ©νλ λ± μΈλΆ μ‘μ μ μ¬μ©ν μλ μλ€.
event
μ΄λ²€νΈλ μν¬νλ‘μ°λ₯Ό μ€ννλ νΈλ¦¬κ±°λ₯Ό μλ―Ένλ€. μ½λ νΈμ, PR μμ± λ±μ΄ μ΄λ²€νΈμ ν΄λΉλλ€.
λΈλ£¨/κ·Έλ¦° λ°°ν¬λ₯Ό μν μν¬νλ‘μ° μμ±νκΈ°
λ‘컬μμ μμ€μ½λλ₯Ό λΉλνκ³ , μ격 μλ²μμ λΉλ κ²°κ³Όλ¬Όμ μ€ννλ λ± μλμΌλ‘ λ°°ν¬νλ λ°©λ²λ μ‘΄μ¬νλ€. νμ§λ§ μ΄λ λ§€μ° λ²κ±°λ‘μ΄ κ³Όμ μΈλ°λ€κ° κ°λ°μμ μ€μκ° λ°μν μ μμ΄ μΌκ΄λ λ°°ν¬λ₯Ό 보μ₯ν μ μλ€. λ°λΌμ κΉν μ‘μ μΌλ‘ λ°°ν¬ κ³Όμ μ μλννμ¬ μ μνκ³ μΌκ΄λ λ°°ν¬λ₯Ό μ 곡ν΄λ³΄μ.
λ°°ν¬ κ³Όμ μ μλννμ λμ μ΄μ
- μ μν λ°°ν¬ : μλνλ λ°°ν¬ νλ‘μΈμ€λ‘ λΉ λ₯΄κ² λ°°ν¬ν μ μλ€.
- μΌκ΄λ λ°°ν¬ νμ§ : κ°λ°μ μ€μλ₯Ό μ€μ΄κ³ μΌκ΄λ λ°°ν¬ νκ²½μ μ¬μ©ν¨μΌλ‘μ¨ μμΈ‘ κ°λ₯νκ³ μμ μ μΈ λ°°ν¬λ₯Ό 보μ₯νλ€.
- μμ μ μ½ : λ°λ³΅μ μ΄κ³ λ²κ±°λ‘μ΄ μμ λ€μ μλννμ¬ κ°λ°μμ μκ°κ³Ό λ Έλ ₯μ μ μ½ν μ μλ€.
λ°°ν¬ μν¬νλ‘μ°
μμ μ΄λ―Έμ§λ λ°°ν¬ μν¬νλ‘μ°λ₯Ό λ€μ΄μ΄κ·Έλ¨μΌλ‘ λνλΈ κ²μ΄λ€. νΉμ λΈλμΉλ‘ push λμμ λ μ€νλλ©°, buildμ deploy μμ μΌλ‘ ꡬμ±λμ΄ μλ€.
name: Blue/Green CD on: push: paths: ["backend/**"] branches: ["dev"] env: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} GITHUB_SHA: ${{ github.sha }} jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Generate .env file and SSL files run: | cd backend echo "${{ secrets.ENV_FILE }}" > .env echo "DOCKER_USERNAME=${{ secrets.DOCKER_USERNAME }}" >> .env echo "GITHUB_SHA=${{ github.sha }}" >> .env mkdir -p config/nginx/ssl/ echo "${{ secrets.SSL_OPTIONS }}" > config/nginx/ssl/options-ssl-nginx.conf echo "${{ secrets.SSL_FULLCHAIN }}" > config/nginx/ssl/fullchain.pem echo "${{ secrets.SSL_PRIVKEY }}" > config/nginx/ssl/privkey.pem echo "${{ secrets.SSL_DHPARAMS }}" > config/nginx/ssl/ssl-dhparams.pem - name: Add Remote Host Key to known_hosts run: | mkdir -p ~/.ssh echo "${{ secrets.SSH_PUBLIC_KEY }}" >> ~/.ssh/known_hosts - name: Copy some files needed for Deployment to Remote Server uses: appleboy/scp-action@master with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USERNAME }} key: ${{ secrets.SSH_PRIVATE_KEY }} port: ${{ secrets.SSH_PORT }} source: "backend/.env,backend/deploy.sh,backend/config,backend/compose.blue-deploy.yml,backend/compose.green-deploy.yml,backend/Dockerfile.nginx" target: "~/app/" overwrite: true - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - name: Build & Push Docker Images (Blue & Green) run: | cd backend docker-compose -f compose.blue-build.yml build docker-compose -f compose.green-build.yml build docker-compose -f compose.blue-build.yml push docker-compose -f compose.green-build.yml push deploy: needs: build runs-on: ubuntu-latest steps: - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - name: Run a New Version of the application on Remote Server uses: appleboy/ssh-action@master with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USERNAME }} key: ${{ secrets.SSH_PRIVATE_KEY }} port: ${{ secrets.SSH_PORT }} script: | cd ~/app/backend chmod +x deploy.sh source deploy.sh
build μμ μμ±νκΈ°
λΉλ μμ μ λ컀 μ΄λ―Έμ§λ₯Ό λΉλνκ³ λ컀 νλΈμ νΈμνλ μν μ΄λ€. λ¨κ³λ³λ‘ νλμ© μ΄ν΄λ³΄μ.
Checkout code
체ν¬μμμ λ ν¬μ§ν 리μ νΉμ λΈλμΉ, μ»€λ° λ±μ κ°μ Έμ€λ μμ μ΄λ€. μ¦, μν¬νλ‘μ°κ° μ€νλ λμ μνλ₯Ό κ°μ Έμ¨λ€. μΌλ°μ μΌλ‘ `actions/checkout` μ‘μ μ μ΄μ©νλ€.
Generate .env file and SSL files
κΉν μν¬λ¦Ώμ μ΄μ©νμ¬ env νμΌκ³Ό ssl νμΌμ μμ±νλ μμ μ΄λ€. μ격 μλ²μ μ΄ν리μΌμ΄μ μ λ°°ν¬νκΈ° μν΄μλ νκ²½λ³μλ€μ μ λ¬ν΄μ€μΌ νλ€. νμ§λ§ env νμΌμ λ ν¬μ§ν 리μ μ¬λΌκ°λ©΄ μλλ λ―Όκ°ν μ 보λ€μ λ΄κ³ μλ€. λ°λΌμ κΉν μν¬λ¦Ώμ μ΄μ©νμ¬ env νμΌμ μμ±ν΄μΌ νλ€.
Add Remote Host Key to known_hosts
sshλ‘ μ격 μλ²μ μ μνκΈ° μν΄μλ `/.ssh/known_hosts` νμΌμ΄ νμνλ€. ν΄λΉ νμΌμ ν΄λΌμ΄μΈνΈκ° μλ²μ μ λ’°μ±μ νμΈνλ λ° μ¬μ©λλ©° μ격 μλ²μ 곡κ°ν€λ₯Ό μ μ₯νλ€.
Copy some files needed for Deployment to Remote Server
νκ²½λ³μ, λ°°ν¬ μ€ν¬λ¦½νΈ λ± λ°°ν¬ν λ νμν νμΌλ€μ μ격 μλ²μ μ λ¬νλ μμ μ΄λ€.
Login to Docker Hub
λ€μ λ¨κ³μμ λ컀 νλΈμ μ΄λ―Έμ§λ₯Ό νΈμν΄μΌ νκΈ° λλ¬Έμ λ컀 νλΈμ λ‘κ·ΈμΈνλ€.
Build & Push Docker Images (Blue & Green)
λ컀 μ»΄ν¬μ¦ νμΌμ λ°νμΌλ‘ λΈλ£¨ λ²μ , κ·Έλ¦° λ²μ μ΄λ―Έμ§λ₯Ό λΉλνλ€. μ΄λ―Έμ§λ₯Ό λΉλν ν, μμ±λ μ΄λ―Έμ§λ₯Ό λ컀 νλΈμ νΈμνλ€. μΆνμ μ격 μλ²μμ νΈμλ μ΄λ―Έμ§λ₯Ό κ°μ Έμ μ€ννκ² λλ€.
deploy μμ μμ±νκΈ°
λ°°ν¬ μμ μ λ컀 νλΈλ‘λΆν° μ΄λ―Έμ§λ₯Ό κ°μ Έμ 컨ν μ΄λλ₯Ό μ€ννλ€.
Login to Docker Hub
λ€μ λ¨κ³μμ λ컀 νλΈμ μ κ·Όνμ¬ μ΄λ―Έμ§λ₯Ό κ°μ ΈμμΌ νλ―λ‘ λ컀 νλΈμ λ‘κ·ΈμΈνλ€.
Run a New Version of the application on Remote Server
μ μ€ν¬λ¦½νΈλ₯Ό μ€ννμ¬ μλ‘μ΄ λ²μ μ μ΄ν리μΌμ΄μ μ λ°°ν¬νλ μμ μ΄λ€. `deploy.sh` νμΌμ μ€ννκΈ° μν΄μλ μ¬μ μ μ€νκΆνμ λΆμ¬ν΄μ€μΌ νλ€.
λΈλ£¨/κ·Έλ¦° λ°°ν¬ μ μ€ν¬λ¦½νΈ μμ±νκΈ°
κΉν μ‘μ μ λ°°ν¬ μ€ν¬λ¦½νΈκ° λ무 κΈΈμ΄μ Έμ `deploy.sh`λΌλ λ³λμ νμΌλ‘ λΆλ¦¬νλ€. μμ deploy μμ μμ ν΄λΉ μ μ€ν¬λ¦½νΈλ₯Ό μ€ννμ¬ λΈλ£¨/κ·Έλ¦° λ°°ν¬λ₯Ό μννλ€.
μλ²μμ μ€νμ€μΈ λ²μ νμΈνκΈ°
λ컀 λͺ λ Ήμ΄λ₯Ό μ΄μ©νμ¬ μλ²μμ μ΄λ€ λ²μ μ΄ μ€ν μ€μΈμ§ νμΈνλ€. μ΄λ¦μ blueλΌλ λ¬Έμμ΄μ΄ ν¬ν¨λ κ²½μ°, λΈλ£¨ λ²μ μ΄ μ€νμ€μ΄λ―λ‘ κ·Έλ¦° λ²μ μμ μ κ· λ²μ μ μ΄ν리μΌμ΄μ μ λμμΌ νλ€. λ°λμ κ²½μ°μ λΈλ£¨ λ²μ μμ μ κ· μ΄ν리μΌμ΄μ μ μ€ννλ€.
if sudo docker ps --filter "name=blue" --format '{{.ID}}' | grep -E .; then RUN_TARGET="green" STOP_TARGET="blue" WAS_RUN_PORT=3002 WAS_STOP_PORT=3000 else RUN_TARGET="blue" STOP_TARGET="green" WAS_RUN_PORT=3000 WAS_STOP_PORT=3002 fi
`RUN_TARGET`μ μ κ· λ²μ μ΄ μ€νλλ λ²μ μ, `STOP_TARGET`μ ꡬλ²μ μ΄ μ€νλλ λ²μ μ μλ―Ένλ€.
μ κ· μ΄ν리μΌμ΄μ λμ°κΈ°
μμμ μ κ· μ΄ν리μΌμ΄μ μ μ΄λμ λμμΌ νλμ§ μμλλ€. μ΄μ λ컀 λͺ λ Ήμ΄λ₯Ό μ΄μ©νμ¬ μ»¨ν μ΄λλ₯Ό μ€νν κ²μ΄λ€.
DOCKER_COMPOSE_FILE="compose.$RUN_TARGET-deploy.yml" sudo docker-compose -f "$DOCKER_COMPOSE_FILE" pull sudo docker-compose -f "$DOCKER_COMPOSE_FILE" up -d
λ컀 μ»΄ν¬μ¦ νμΌμ μ΄μ©νμ¬ λ컀 νλΈλ‘λΆν° μ΄λ―Έμ§λ₯Ό κ°μ Έμ€κ³ 컨ν μ΄λλ₯Ό μ€ννλ€.
NGINXλ₯Ό μ΄μ©νμ¬ νΈλν½ μ ννκΈ°
μ κ· μ΄ν리μΌμ΄μ μ΄ μ€νλκ³ λλ©΄ κΈ°μ‘΄ λ²μ μΌλ‘ κ°λ νΈλν½μ μ κ· μ΄ν리μΌμ΄μ μ ν₯νλλ‘ μμ ν΄μΌ νλ€.
NGINX_ID=$(sudo docker ps --filter "name=nginx" --quiet) NGINX_CONFIG="/etc/nginx/conf.d/default.conf" sudo docker exec $NGINX_ID /bin/bash -c "sed -i 's/was-$STOP_TARGET:$WAS_STOP_PORT/was-$RUN_TARGET:$WAS_RUN_PORT/' $NGINX_CONFIG" sudo docker exec $NGINX_ID /bin/bash -c "sed -i 's/signal-$STOP_TARGET:$((WAS_STOP_PORT + 1))/signal-$RUN_TARGET:$((WAS_RUN_PORT + 1))/' $NGINX_CONFIG" sudo docker exec $NGINX_ID /bin/bash -c "nginx -s reload"
μμ§μμ€λ 컨ν μ΄λμμ μ€ν μ€μ΄κΈ° λλ¬Έμ μ΄λ¦μ nginxκ° ν¬ν¨λ 컨ν μ΄λμ μμ΄λλ₯Ό μ‘°ννλ€. μ΄μ `sudo docker exec`λ₯Ό μ΄μ©νμ¬ μμ§μμ€ μ»¨ν μ΄λμ μ€μ νμΌμ μμ νλ€.
λ§μ½ ꡬλ²μ μ΄ λΈλ£¨ λ²μ μμ μ€νμ€μ΄λΌλ©΄ μμ§μμ€λ `was-blue:3000`κ³Ό `signal-blue:3001`λ‘ νλ‘μνλ€. νΈλν½μ μ ννκΈ° μν΄ `sed -i`λ‘ `was-blue:3000`μ `was-green:3002`, `signal-blue:3001`μ `signal-green:3003`λ‘ μΉννμ¬ κ·Έλ¦° λ²μ μΌλ‘ νλ‘μνλλ‘ λ³κ²½νλ€. λ§μ§λ§μΌλ‘ μμ λ μ€μ νμΌμ λ°μνκΈ° μν΄ μμ§μμ€λ₯Ό 리λ‘λνλ€.ꡬλ²μ μ μ΄ν리μΌμ΄μ μ’ λ£νκΈ°
μ΄μ λ¨κ³λ‘ μΈν΄ νΈλν½μ΄ μ κ· μ΄ν리μΌμ΄μ μ ν₯νλλ‘ λ³κ²½λμλ€. μ΄μ λ μ΄μ μ¬μ©νμ§ μλ ꡬλ²μ μ μ΄ν리μΌμ΄μ μ μ’ λ£νλ μμ λ§ λ¨μλ€.
STOP_CONTAINER_ID=$(sudo docker ps --filter "name=$STOP_TARGET" --quiet) if [ -n "$STOP_CONTAINER_ID" ]; then sudo docker stop $STOP_CONTAINER_ID sudo docker rm $STOP_CONTAINER_ID sudo docker image prune -af fi
λ컀 λͺ λ Ήμ΄λ₯Ό μ΄μ©νμ¬ κ΅¬λ²μ μ 컨ν μ΄λλ₯Ό λ©μΆκ³ μμ νλ€. λλ λμ€ν¬ κ³΅κ° λλΉλ₯Ό λ§κΈ° μν΄ μ¬μ©νμ§ μλ μ΄λ―Έμ§λ μμ ν΄μ€¬λ€.
λ컀 + μμ§μμ€ + κΉν μ‘μ μ νμ©ν λΈλ£¨/κ·Έλ¦° 무μ€λ¨ λ°°ν¬ μμ±μ΄λ€!!π Blue/Green CD μν¬νλ‘μ°
π deploy.sh
μ°Έκ³ μλ£
'backend > ciβ’cd' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
[CD] health checkλ‘ μλ² μν νμΈνκΈ° (Docker Compose/Shell Script) (0) 2024.01.21 [CD] λΈλ£¨/κ·Έλ¦° 무μ€λ¨ λ°°ν¬ κ΅¬ννκΈ° 1νΈ (NGINX/Docker Compose) (1) 2024.01.07 λ€μκΈμ΄ μμ΅λλ€.μ΄μ κΈμ΄ μμ΅λλ€.λκΈ