Setting Up Zero-Downtime Deployment - Back-End Configuration

Server

Language :

Hi, I’m Lovefield.

Following the previous article,”Setting Up Zero-Downtime Deployment - Environment Setup”, this post will describe the configuration for achieving zero-downtime deployment of the back-end server. The back-end framework used is Spring Boot, with Java as the runtime environment. Since building is the first priority, I will create build and upload functionality using Git Actions.

Before writing the Git Action, specify the secrets values required for the upload:

  • HOST_DIR: The base directory location to be used on the server.
  • HOST_IP: The server address.
  • HOST_RUN_DIR: The directory location where scripts are executed on the server.
  • HOST_USER: The server username.
  • PEM_VALUE: The PEM key value for accessing the server.

prod-deploy.yml

YAML

name: Deploy

on:
   push: # Executed when a push is made to the branch
       branches:
           - main
   workflow_dispatch: # Manually executed from the GitHub Actions page
       name:
           description: "print name"

jobs:
   buildAndDeploy:
       runs-on: ubuntu-latest
       permissions:
           contents: read
           packages: write
       steps:
           - uses: actions/checkout@v3
           - uses: actions/setup-java@v4
             with:
                 distribution: "temurin"
                 java-version: "21"

           - name: Build
             run: ./gradlew build

           - name: Upload file
             uses: appleboy/scp-action@v0.1.7
             with:
                 host: ${{ secrets.HOST_IP }}
                 username: ${{ secrets.HOST_USER }}
                 key: ${{ secrets.PEM_VALUE }}
                 port: 22
                 source: "./build/libs/dico-4.0.jar"
                 target: ${{ secrets.HOST_DIR }}/

           - name: Run Deploy Script
             uses: appleboy/ssh-action@v1.1.0
             env:
                 HOST_DIR: ${{ secrets.HOST_DIR }}
                 HOST_RUN_DIR: ${{ secrets.HOST_RUN_DIR }}
             with:
                 host: ${{ secrets.HOST_IP }}
                 username: ${{ secrets.HOST_USER }}
                 key: ${{ secrets.PEM_VALUE }}
                 envs: HOST_DIR,HOST_RUN_DIR
                 port: 22
                 script: |
                     mv $HOST_DIR/build/libs/dico-4.0.jar $HOST_RUN_DIR/back/dico-4.0.jar
                     cd $HOST_RUN_DIR
                     sudo sh $HOST_RUN_DIR/deploy-backend.sh

After performing the build and upload using GitHub Actions, the configuration executes deploy-backend.sh on the server. This deploy-backend.sh script updates the Blue and Green containers.

deploy-backend.sh

Bash

#!/bin/bash

# Check if the Blue container is running
EXIST_BLUE=$(sudo docker compose ps -a | grep "backend-blue")
target="blue";
target_port="9002"
TIMESTAMP=`date +%Y%m`
LOGFILE="./logs/backend-$TIMESTAMP.log"

func_reload()
{
   echo "[$(date +%Y-%m-%d\ %H\:%M\:%S)] $1 Container down" >> $LOGFILE

   sudo docker compose down backend-$1

   echo "[$(date +%Y-%m-%d\ %H\:%M\:%S)] $1 Container up" >> $LOGFILE

   sudo docker compose up backend-$1 -d

   sleep 1.5

   while true; do
       RESPONSE=$(curl -I http://localhost:$2/health-check | grep "200" | awk '{print $2}')

       echo "[$(date +%Y-%m-%d\ %H\:%M\:%S)] $1 Container Health Check" >> $LOGFILE

       if [ "$RESPONSE" -eq "200" ]; then
           break;
       fi
      
       sleep 2
   done
}

touch $LOGFILE

echo "[$(date +%Y-%m-%d\ %H\:%M\:%S)] >>> Start of the back-end deployment script <<<" >> $LOGFILE

# If the Blue container is running
if [ "$EXIST_BLUE" ]; then
   target="green";
   target_port="9001"
else
   target="blue";
   target_port="9002"
fi

func_reload $target $target_port

if [ "$target" -eq "blue" ]; then
   func_reload "green" "9001"
else
   func_reload "blue" "9002"
fi

echo "[$(date +%Y-%m-%d\ %H\:%M\:%S)] >>> End of the back-end deployment script <<<" >> $LOGFILE

After writing the script as described above, it can be executed either through a Git push or by manually creating an action. The process will proceed as follows:
Git Action builds the files based on the main branch.
Git Action transfers the built files to the server.
Git Action executes the deployment script on the server.
The deployment script unzips the received file and updates the Blue and Green containers respectively.
Once the process is complete, log files like the following will be generated in the logs folder

Plain Text

[2024-11-15 07:29:06] >>> Start of the back-end deployment script <<<
[2024-11-15 07:29:06] green Container down
[2024-11-15 07:29:06] green Container up
[2024-11-15 07:29:09] green Container Health Check
[2024-11-15 07:29:11] green Container Health Check
[2024-11-15 07:29:13] green Container Health Check
[2024-11-15 07:29:15] green Container Health Check
[2024-11-15 07:29:17] green Container Health Check
[2024-11-15 07:29:19] green Container Health Check
[2024-11-15 07:29:19] blue Container down
[2024-11-15 07:29:20] blue Container up
[2024-11-15 07:29:22] blue Container Health Check
[2024-11-15 07:29:24] blue Container Health Check
[2024-11-15 07:29:26] blue Container Health Check
[2024-11-15 07:29:28] blue Container Health Check
[2024-11-15 07:29:30] blue Container Health Check
[2024-11-15 07:29:30] >>> End of the back-end deployment script <<<

With this, the back-end deployment configuration is complete.

Lovefield

Web Front-End developer

하고싶은게 많고, 나만의 서비스를 만들고 싶은 변태스러운 개발자입니다.