프로젝트/뉴스타
[Infra] Blue-Green 배포 전략을 활용한 무중단 서비스
cks._.hong
2024. 8. 9. 12:40
Blue-Green 배포 전략을 활용한 무중단 서비스 왜 궁금했을까❓
뉴스타 프로젝트에 Blue-Green 배포 전략을 도입하여 무중단 서비스를 구축한 과정에 대해 작성해보려고 한다. 또한, 배포 전략 중에 Blue-Green을 선택한 이유도 작성해보겠다.
[Infra] 무중단 배포 전략 (Rolling / Blue-Green / Canary)
무중단 배포 전략 (Rolling / Blue-Green / Canary) 왜 궁금했을까❓뉴스타 서비스 CI/CD 과정에서 서비스가 중단되는 문제점으로 인해 사용자가 일정 시간동안 서비스를 이용하지 못하는 문제점이 있었
pslog.co.kr
위 포스팅을 통해 무중단 배포 전략에 대한 개념을 학습할 수 있다.
1. 왜 Blue-Green 배포 전략인가?
- 1개의 서버와 어플리케이션당 하나씩 Docker를 이용해서 띄우기 때문에 롤링과 카나리 방식은 적합하지 않았다.
- 프로젝트 기간이 짧아 기능의 품질을 보장하기 쉽지 않았고 이로 인해 잦은 백업이 필요했다. 보다 쉽게 롤백을 이뤄낼 수 있기 때문에 Blue-Green을 선택했다.
- Blue-Green의 경우 리소스가 많이 사용된다는 점이 있지만 어플리케이션을 3개 밖에 구동중이지 않아 전환되는 과정에서 부하가 많지 않을 것이라 예상되었다.
2. 프로젝트 구축 과정
2.1. Blue-Green 무중단 서비스 Flow
- 개발자가 코드 변경 사항을 GitLab에 PUSH
- Webhook을 통해서 신버전 코드 빌드 및 이미지 배포
- Blue-Green 전환 배포 스크립트 Deploy.sh 실행
- Nginx 라우팅을 통해서 구버전 -> 신버전 전환
[CI/CD] Jenkins / GitLab / Docker / EC2 연동 (1/2)
SSAFY 2학기 프로젝트 "Archiview"를 Jenkins, GitLab, Docker, EC2를 이용해서 CI/CD를 구축해보겠다.처음에는 Jenkins를 Docker에 올렸는데 Jenkins에서 docker를 실행하기 위해서는 docker를 또 설치해야 하는 문제점
pslog.co.kr
위 포스팅을 통해서 Jenkins, GitLab을 활용한 CI/CD 과정을 볼 수 있다.
2.2. Nginx
service-url.inc
set $spring_url http://172.17.0.1:8080;
set $react_url http://172.17.0.1:3000;
set $fastapi_url http://172.17.0.1:8000;
- 서버 IP를 각 변수에 저장하고 해당 IP를 변경하여 Blue-Green 전환이 이뤄지게 된다.
default.conf
server {
...
location /api {
proxy_pass $spring_url;
proxy_set_header X-Forwarded-Host $server_name;
}
location /api/data/ {
proxy_pass $fastapi_url;
proxy_set_header X-Forwarded-Host $server_name;
}
location / {
proxy_pass $react_url;
}
}
- proxy_pass에 각 서버에 해당되는 변수를 입력하여 서버 IP를 지정하여 라우팅한다.
2.3. Docker
docker-compose.blue.yaml
version: '3'
services:
spring:
container_name: spring-blue
image: newstar_back
ports:
- 8080:8080
restart: always
env_file:
- /env/.env
environment:
TZ: Asia/Seoul
react:
container_name: react-blue
image: newstar_front
ports:
- 3000:3000
fastapi:
container_name: fastapi-blue
image: fastapi_back
ports:
- 8000:8000
restart: always
env_file:
- /env/.env
environment:
TZ: Asia/Seoul
networks:
default:
name: app-net
external: true
docker-compose.green.yaml
version: '3'
services:
spring:
container_name: spring-green
image: newstar_back
ports:
- 8081:8080
restart: always
env_file:
- /env/.env
environment:
TZ: Asia/Seoul
react:
container_name: react-green
image: newstar_front
ports:
- 3001:3000
fastapi:
container_name: fastapi-green
image: fastapi_back
ports:
- 8001:8000
restart: always
env_file:
- /env/.env
environment:
TZ: Asia/Seoul
networks:
default:
name: app-net
external: true
2.4. Jenkins Pipeline Script
pipeline {
agent any
stages {
stage('clone') {
steps {
echo 'git clone'
git branch: 'master',
credentialsId: '[비밀키]',
url: 'https://lab.ssafy.com/s10-bigdata-recom-sub2/S10P22B302.git'
}
}
stage('API Server build') {
steps {
dir('back') {
sh 'docker build -t newstar_back .'
}
}
}
stage('GPU Server build') {
steps {
dir('pydata') {
sh 'docker build -t fastapi_back .'
}
}
}
stage('Web Server build') {
steps {
dir('front/newstar') {
sh 'docker build -t newstar_front .'
}
}
}
stage('Blue-Green Deploy'){
steps{
dir('exec/deploy'){
sh 'chmod +x deploy.sh'
sh './deploy.sh'
}
}
}
}
}
- WebHook에 의해 파이프라인이 실행되는데 GitLab에 있는 Repo로부터 코드를 Clone 받아온다.
- API, GPU, Web Server를 순으로 신버전의 도커 이미지를 빌드한다.
- deploy.sh를 통해 Blue-Green 배포를 실시한다.
2.5. deploy.sh
#!/bin/bash
echo 'CI/CD Deploy Start'
cd ../exec/deploy
# Working container check
EXIST_BLUE=$(docker compose -p deploy-blue -f docker-compose.blue.yaml ps | grep Up)
if [ -z "$EXIST_BLUE" ]; then
# blue
docker compose -p deploy-blue -f docker-compose.blue.yaml up -d
BEFORE_COLOR="green"
AFTER_COLOR="blue"
BEFORE_SPRING_PORT=8081
BEFORE_REACT_PORT=3001
BEFORE_FASTAPI_PORT=8001
AFTER_SPRING_PORT=8080
AFTER_REACT_PORT=3000
AFTER_FASTAPI_PORT=8000
else
# green
docker compose -p deploy-green -f docker-compose.green.yaml up -d
BEFORE_COLOR="blue"
AFTER_COLOR="green"
BEFORE_SPRING_PORT=8080
BEFORE_REACT_PORT=3000
BEFORE_FASTAPI_PORT=8000
AFTER_SPRING_PORT=8081
AFTER_REACT_PORT=3001
AFTER_FASTAPI_PORT=8001
fi
# Spring Server health checking
for retry_count in {1..60}
do
response=$(curl -s http://172.17.0.1:${AFTER_SPRING_PORT}/api/actuator/health)
up_count=$(echo $response | grep 'UP' | wc -l)
if [ $up_count -ge 1 ]
then
echo "=========================="
echo "> Spring Server is working"
echo "=========================="
break
else
echo "> Spring Health is not working: ${response}"
fi
# about 10 minuetes
if [ $retry_count -eq 60 ]
then
echo "> Spring Server working failed"
docker compose -p deploy-${AFTER_COLOR} -f docker-compose.${AFTER_COLOR}.yaml down
exit 1;
fi
# wating 10 seconds
sleep 10
done
# Fastapi Server health checking
for retry_count in {1..60}
do
response=$(curl -s http://172.17.0.1:${AFTER_FASTAPI_PORT}/api/data/health)
up_count=$(echo $response | grep 'UP' | wc -l)
if [ $up_count -ge 1 ]
then
echo "=========================="
echo "> Fastapi Server is working"
echo "=========================="
break
else
echo "> Fastapi Health is not working: ${response}"
fi
# about 10 minuetes
if [ $retry_count -eq 60 ]
then
echo "> Fastapi Server working failed"
docker compose -p deploy-${AFTER_COLOR} -f docker-compose.${AFTER_COLOR}.yaml down
exit 1;
fi
# wating 10 seconds
sleep 20
done
echo "${AFTER_COLOR} server up(spring_port:${AFTER_SPRING_PORT}, react_port:${AFTER_REACT_PORT}, fastapi_port:${AFTER_FASTAPI_PORT})"
EXIST_AFTER=$(docker compose -p deploy-${AFTER_COLOR} -f docker-compose.${AFTER_COLOR}.yaml ps | grep Up)
if [ -n "$EXIST_AFTER" ]; then
echo "nginx Setting"
docker exec -i nginx /bin/bash -c "echo -e 'set \$spring_url http://172.17.0.1:${AFTER_SPRING_PORT};\nset \$react_url http://172.17.0.1:${AFTER_REACT_PORT};\nset \$fastapi_url http://172.17.0.1:${AFTER_FASTAPI_PORT};' | tee /etc/nginx/conf.d/service-url.inc && nginx -s reload"
echo "Completed Deploy!"
echo "$BEFORE_COLOR server down(spring_port:${BEFORE_SPRING_PORT}, react_port:${BEFORE_REACT_PORT}, fastapi_port:${BEFORE_FASTAPI_PORT})"
docker compose -p deploy-${BEFORE_COLOR} -f docker-compose.${BEFORE_COLOR}.yaml down
fi
- 현재 실행중인 Docker-compose의 색깔을 확인(Blue or Green)
- 만약, Blue일 경우 Green의 Docker-compose를 띄우고 port번호를 Green에 맞게 저장
- Green일 경우 Blue의 Docker-compose를 띄우고 port번호를 Blue에 맞게 저장
- 각 어플리케이션 API, GPU Server의 Health Check API를 통해 요청을 날려보며 신버전의 코드가 정상적으로 운영되고 있는지 확인
- 만약, 정상적으로 운영되지 않으면 신버전의 서버 다운 후 스크립트 종료
- 신버전의 Docker-compose가 정상적으로 띄워져 있다면 service-url.inc에 있는 각 어플리케이션 port를 신버전으로 변경 후 Nginx 재시작
- 기존에 운영되던 구버전의 docker compose 다운