Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: dev 서버 무중단 배포(blue-green) 적용 #598

Open
wants to merge 18 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 22 additions & 14 deletions .github/workflows/backend-dev-ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ on:
- "backend/**"
- ".github/workflows/backend-dev-ci-cd.yml"
- "Dockerfile"
# pull_request:
# branches: [ "develop" ]
# paths:
# - "backend/**"
# - ".github/workflows/backend-dev-ci-cd.yml"
# - "Dockerfile"
# pull_request:
# branches: [ "develop" ]
# paths:
# - "backend/**"
# - ".github/workflows/backend-dev-ci-cd.yml"
# - "Dockerfile"

jobs:

Expand Down Expand Up @@ -57,16 +57,24 @@ jobs:
docker tag ${{ secrets.BE_DOCKER_IMAGE_NAME_DEV }} ${{ secrets.BE_DOCKERHUB_USERNAME }}/${{ secrets.BE_DOCKER_IMAGE_NAME_DEV }}:${GITHUB_SHA::7}
docker push ${{ secrets.BE_DOCKERHUB_USERNAME }}/${{ secrets.BE_DOCKER_IMAGE_NAME_DEV }}:${GITHUB_SHA::7}

deploy:
deploy-new-container:
needs: build-and-test
runs-on: [ self-hosted, dev ]
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Deploy new container
run: |
bash launch_next_container.sh ${GITHUB_SHA::7} dev ${{ secrets.BE_DOCKERHUB_USERNAME }} ${{ secrets.BE_DOCKER_IMAGE_NAME_DEV }}
working-directory: backend/deploy

switch-new-container:
needs: deploy-new-container
runs-on: [ self-hosted, dev ]

steps:
- name: Pull Image And Restart Container
- name: Switch from old to new container
run: |
docker login -u ${{ secrets.BE_DOCKERHUB_USERNAME }} -p ${{ secrets.BE_DOCKERHUB_PASSWORD }}
docker stop ${{ secrets.BE_DOCKER_CONTAINER_NAME }} | true
docker rm ${{ secrets.BE_DOCKER_CONTAINER_NAME }} | true
docker image prune -a -f
docker pull ${{ secrets.BE_DOCKERHUB_USERNAME }}/${{ secrets.BE_DOCKER_IMAGE_NAME_DEV }}:${GITHUB_SHA::7}
docker run --name ${{ secrets.BE_DOCKER_CONTAINER_NAME }} -d -p 80:8080 -v /logs:/logs -e SPRING_PROFILES_ACTIVE=dev ${{ secrets.BE_DOCKERHUB_USERNAME }}/${{ secrets.BE_DOCKER_IMAGE_NAME_DEV }}:${GITHUB_SHA::7}
bash switch_blue_green_container.sh
working-directory: backend/deploy
33 changes: 33 additions & 0 deletions backend/deploy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# blue-green 스크립트

무중단 배포를 하기 위한 blue green 스크립트 입니다.

## 초기 설정

blue green을 처음 구축할 때 사용됩니다.

스크립트 실행 시 nginx 컨테이너와 총대마켓 컨테이너가 각각 80, 8080 포트로 구동되고, nginx는 총대마켓 컨테이너로 proxy_pass가 설정되도록 구축이됩니다.
외부의 요청을 받기 위해 nginx는 외부 80포트가 오픈됩니다.

### 스크립트 실행 방법

``` bash
./setup.sh <최근 배포 성공한 commit 해시> <적용 프로파일> <컨테이너 계정 명> <컨테이너 이미지 명>
```

## 이후 전략

CI에서 배포가 끝나면 배포 단계에서 `launch_next_container.sh`을 실행합니다.
실행 시 최신 빌드 성공한 commit이 반영 된 컨테이너가 실행됩니다.
컨테이너가 올라가면, `sleep second` 동안 대기 후 성공적으로 컨테이너가 실행됬는지 확인합니다.

이후 성공적으로 컨테이너가 구동 완료 되면 `switch_blue_green_container.sh`를 실행시켜 nginx가 새로운 컨테이너로 proxy_pass하도록 설정을 변경합니다.

### 스크립트 실행 과정

``` bash
./launch_next_container.sh <PR commit hash> <적용 프로파일> <컨테이너 계정 명> <컨테이너 이미지 명>

# blue -> green , green -> blue 전환
./switch_blue_green_container.sh
```
14 changes: 14 additions & 0 deletions backend/deploy/default.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
upstream active_server {
server chongdae_backend:8080;
}

server {
listen 80;
location / {
proxy_pass http://active_server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

84 changes: 84 additions & 0 deletions backend/deploy/launch_next_container.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/bin/bash

GITHUB_SHA=$1
PROFILE_ACTIVE=$2
DOCKERHUB_USER_NAME=$3
DOCKER_IMAGE_NAME=$4

INITIAL_BLUE_GREEN_NETWORK_NAME="blue_green_network"
BLUE_CONTAINER="chongdae_backend_blue"
GREEN_CONTAINER="chongdae_backend_green"
NGINX_CONTAINER="nginx"

# parameter check
if [ -z "$GITHUB_SHA" ] || [ -z "$PROFILE_ACTIVE" ] || [ -z "$DOCKERHUB_USER_NAME" ] || [ -z "$DOCKER_IMAGE_NAME" ]; then
echo "사용법: $0 <GITHUB_SHA> <PROFILE_ACTIVE> <DOCKERHUB_USER_NAME> <DOCKER_IMAGE_NAME>"
exit 1
fi

get_active_container() {
local active_container_name=$(docker exec ${NGINX_CONTAINER} cat /etc/nginx/conf.d/default.conf | \
grep -P '^\s*server\s+\S+:' | \
grep -oP '(?<=server\s)[^:;]+' | \
sed 's/^[[:space:]]*//;s/[[:space:]]*$//')

echo "$active_container_name"
}

get_next_container() {
local active_container=$1
if [[ "$active_container" == "$GREEN_CONTAINER" ]]; then
echo "$BLUE_CONTAINER"
return 0
fi

if [[ "$active_container" == "$BLUE_CONTAINER" ]]; then
echo "$GREEN_CONTAINER"
return 0
fi

return 1
}

health_check() {
local next_container=$1
local max_tries=$2
local sleep_seconds=$3

for ((i=1; i<=max_tries; i++)); do
echo "[INFO] Attempt $i: Checking health of $next_container"
local status=$(docker exec nginx curl -H "Host: localhost" -o /dev/null -s -w "%{http_code}\n" http://$next_container:8080/health-check)

if [[ "$status" -eq "200" ]]; then
echo "[INFO] Health check successful."
return 0
fi

echo "[WARNING] Health check failed. Attempt $i of $max_tries."
sleep $sleep_seconds
done

echo "[ERROR] All $max_tries health check attempts failed for $next_container. Exiting..."
return 1
}

ACTIVE_CONTAINER=$(get_active_container)
NEXT_CONTAINER=$(get_next_container $ACTIVE_CONTAINER)

echo "[+] launch $NEXT_CONTAINER"

docker rm -f ${NEXT_CONTAINER} >/dev/null 2>&1

docker run -d \
--network ${INITIAL_BLUE_GREEN_NETWORK_NAME} \
--name ${NEXT_CONTAINER} \
-v /logs:/logs \
-e SPRING_PROFILES_ACTIVE=${PROFILE_ACTIVE} \
${DOCKERHUB_USER_NAME}/${DOCKER_IMAGE_NAME}:${GITHUB_SHA:0:7}

MAX_TRIES=5
SLEEP_SECONDS=10
health_check "$NEXT_CONTAINER" "$MAX_TRIES" "$SLEEP_SECONDS"
if [ $? -ne 0 ]; then
exit 1
fi
62 changes: 62 additions & 0 deletions backend/deploy/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/bin/bash
GITHUB_SHA=$1
PROFILE_ACTIVE=$2
DOCKERHUB_USER_NAME=$3
DOCKER_IMAGE_NAME=$4

# parameter check
if [ -z "$GITHUB_SHA" ] || [ -z "$PROFILE_ACTIVE" ] || [ -z "$DOCKERHUB_USER_NAME" ] || [ -z "$DOCKER_IMAGE_NAME" ]; then
echo "사용법: $0 <GITHUB_SHA> <PROFILE_ACTIVE> <DOCKERHUB_USER_NAME> <DOCKER_IMAGE_NAME>"
exit 1
fi

# intialize blue & gren
INITIAL_INSTANCE_NAME="chongdae_backend_green"
INITIAL_BLUE_GREEN_NETWORK_NAME="blue_green_network"
NGINX_DEFAULT_CONF_PATH="default.conf"
NGINX_NEW_CONF_PATH="new-default.conf"

# 1. ADD DOCKER NETWORK
if ! docker network ls | grep -q ${INITIAL_BLUE_GREEN_NETWORK_NAME}; then
echo "[+] Create Docker Netowrk ${INITIAL_BLUE_GREEN_NETWORK_NAME}"
docker network create --driver bridge "${INITIAL_BLUE_GREEN_NETWORK_NAME}"
fi

# 2. RUN INITIAL_CONTAINER
echo "[+] stop and remove intial container"
docker rm -f ${INITIAL_INSTANCE_NAME} >/dev/null 2>&1

echo "[+] add chongdae_backend container"
docker run -d \
--network ${INITIAL_BLUE_GREEN_NETWORK_NAME} \
--name ${INITIAL_INSTANCE_NAME} \
-v /logs:/logs \
-e SPRING_PROFILES_ACTIVE=${PROFILE_ACTIVE} \
${DOCKERHUB_USER_NAME}/${DOCKER_IMAGE_NAME}:${GITHUB_SHA:0:7}

if [ $? -ne 0 ]; then
exit 1
fi

# 3. CHANGE INITIAL UP STREAM SERVER
cp ${NGINX_DEFAULT_CONF_PATH} ${NGINX_NEW_CONF_PATH}

if grep -q "server chongdae_backend:" "${NGINX_NEW_CONF_PATH}"; then
sed -i "s/server chongdae_backend:/server ${INITIAL_INSTANCE_NAME}:/" "${NGINX_NEW_CONF_PATH}"
fi

# 4. RUN DOCKER NGINX
echo "[+] RUN nginx server"
docker run -d \
--network ${INITIAL_BLUE_GREEN_NETWORK_NAME} \
--name nginx \
-p 80:80 \
nginx:latest

# 5. setup proxy in nginx container
docker cp ${NGINX_NEW_CONF_PATH} nginx:/etc/nginx/conf.d/${NGINX_NEW_CONF_PATH}
docker exec nginx mv /etc/nginx/conf.d/${NGINX_NEW_CONF_PATH} /etc/nginx/conf.d/default.conf
docker exec nginx nginx -s reload

# 6. clean up
rm -rf ${NGINX_NEW_CONF_PATH}
83 changes: 83 additions & 0 deletions backend/deploy/switch_blue_green_container.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/bin/bash
NGINX_CONTAINER_NAME="nginx"
BLUE_CONTAINER="chongdae_backend_blue"
GREEN_CONTAINER="chongdae_backend_green"
NGINX_DEFAULT_CONF="default.conf"
NGINX_CHANGED_DEFAULT_CONF="new-default.conf"

get_active_container() {
local active_container_name=$(docker exec nginx cat /etc/nginx/conf.d/default.conf | \
grep -P '^\s*server\s+\S+:' | \
grep -oP '(?<=server\s)[^:;]+' | \
sed 's/^[[:space:]]*//;s/[[:space:]]*$//')

echo "$active_container_name"
}

get_next_container() {
local active_container=$1
if [[ "$active_container" == "$GREEN_CONTAINER" ]]; then
echo "$BLUE_CONTAINER"
return 0
fi

if [[ "$active_container" == "$BLUE_CONTAINER" ]]; then
echo "$GREEN_CONTAINER"
return 0
fi

return 1
}

change_blue_green_container() {
local next_container=$1
cp ${NGINX_DEFAULT_CONF} ${NGINX_CHANGED_DEFAULT_CONF}

if grep -q "server chongdae_backend:" "${NGINX_CHANGED_DEFAULT_CONF}"; then
sed -i "s/server chongdae_backend:/server ${next_container}:/" "${NGINX_CHANGED_DEFAULT_CONF}"
fi
echo "[+] change container $next_container"
docker cp ./${NGINX_CHANGED_DEFAULT_CONF} ${NGINX_CONTAINER_NAME}:/etc/nginx/conf.d/${NGINX_CHANGED_DEFAULT_CONF}
docker exec ${NGINX_CONTAINER_NAME} cp /etc/nginx/conf.d/${NGINX_CHANGED_DEFAULT_CONF} /etc/nginx/conf.d/default.conf
docker exec ${NGINX_CONTAINER_NAME} rm -rf /etc/nginx/conf.d/${NGINX_CHANGED_DEFAULT_CONF}
docker exec ${NGINX_CONTAINER_NAME} nginx -s reload

# clean up
rm -rf ${NGINX_CHANGED_DEFAULT_CONF}
}


health_check() {
local status=$(curl -o /dev/null -s -w "%{http_code}\n" http://localhost/health-check)
if [[ "$status" -eq "200" ]]; then
return 0
fi
return 1
}

remove_container() {
local removed_container_name=$1

docker rm -f $removed_container_name
if [ $? -eq 0 ]; then
echo "[-] stop $removed_container_name"
return 0
fi
return 1
}

ACTIVE_CONTAINER=$(get_active_container)
NEXT_CONTAINER=$(get_next_container $ACTIVE_CONTAINER)

if [ $? -eq 0 ]; then
change_blue_green_container $NEXT_CONTAINER
fi

health_check

if [ $? -eq 0 ]; then
remove_container $ACTIVE_CONTAINER
fi

echo "[-] clean docker image"
docker image prune -a -f