일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 블로그 병행
- 주간회고
- 클래스
- Blue-Green
- 메서드명
- 도커
- mysql
- Jenkins
- InnoDB 버퍼 풀
- 카카오 2차 코딩테스트
- 기능별 구조
- java
- B+TREE
- SQL 실행순서
- db
- 멀티쓰레드 프로그래밍
- 회고
- BalancedTree
- 어댑티브 해시 인덱스
- 우테코
- 계층별 구조
- ci/cd
- 프리코스
- jacoco
- useQuery
- DeleteAll
- useMutation
- 백기선 스터디
- N+1
- 월간회고
- Today
- Total
Haneul's Blog
[스케줄 관리 프로젝트 - 일치(INFRA)] Blue-Green 무중단 배포 본문
무중단 배포란?
말 그대로 배포 중에 서비스 기능은 멈추지 않고, 실제 사용자들이 정상적으로 서비스를 사용할 수 있는 편의를 제공하는 배포 방법입니다.
2개 이상의 서버
그렇다면 이를 위해서는 어떻게 해야 할지를 생각해볼 때 서비스를 다시 배포하기 위해서는 서비스를 중단했다가 다시 실행할 필요성이 있습니다.
이걸 다르게 생각해보면 하나의 서버로는 무중단 배포가 불가능 하다는 것을 알 수 있고, 최소 2개 이상의 서버가 필요합니다.
로드 밸런서의 필요성
만약 로드 밸런서가 없이 무중단 배포를 한다고 생각하면 프론트 측에서는 백엔드의 서버 주소를 모두 알고 있어야 되고, 새로 빌드된 파일을 배포하려고 할 때면 유동적으로 연결된 백엔드 주소를 변경해줘야 하는데 이는 상당히 번거로울 수 있는 작업입니다.
또한 보안, 유지 보수, 안정성의 측면에서도 프론트와 백엔드가 직접 연결되지 않고 로드 밸런서를 두는 편이 좋습니다.
그렇기에 무중단 배포를 진행할 때 로드 밸런서를 통해 프론트 측에서는 하나의 서버 주소만 알고 로드 밸런서 측에서 들어오는 요청을 처리하는 환경을 구현해야 합니다.
무중단 배포 종류
롤링(Rolling) 배포
롤링 배포는 사용 중인 인스턴스 내에서 새 버전을 점진적으로 교체하는 방법으로, 서비스 중인 인스턴스 하나 이상을 로드 밸런서에서 라우팅하지 않게 하고, 새 버전을 적용하여 다시 라우팅하는 방법입니다.
롤링 배포는 구 버전 신 버전이 같이 동작을 하는 순간이 있기 때문에 호환성 문제가 발생할 수 있습니다.
블루-그린(Blue-Green) 배포
기존 버전(블루)에 연결되어 있던 트래픽을 일괄적으로 신 버전(그린)으로 전환하는 배포 방법으로, 이를 위해서는 신 버전을 미리 배포하고 배포한 신 버전이 정상적으로 동작하는지 확인하는 과정이 필요하며, 신 버전만 라우팅하기 때문에 호환성 문제가 발생하지는 않는다는 장점이 있습니다.
하지만 롤링 배포와 달리 구 버전의 서버는 아예 라우팅을 하지 않기 때문에 호환성 문제는 없지만 서버 자원이 2배로 들어가는 단점이 있습니다.
카나리(Canary) 배포
카나리 배포는 롤링 배포와 블루-그린 배포를 합친 배포 방식으로, 신 버전의 대상, 비율등을 관리자 입장에서 직접 조절해가며 배포해가는 방식입니다.
신 버전의 비율을 유동적으로 늘려간다는 부분에서 롤링 배포와 유사하며, 실제 환경에서 미리 테스팅을 진행한다는 점에서 블루-그린 배포와 비슷합니다.
그리고 카나리 배포도 롤링 배포와 마찬가지로 신, 구 버전의 호환성을 검증해야 한다는 단점이 있습니다.
내 프로젝트에 적용할 무중단 배포 방식
제 개인적으로는 호환성 문제로 인해서 롤링 배포와, 카나리 배포를 선호하지 않기 때문에 호환성을 생각할 필요가 없는 Blue-Green을 이번 프로젝트에 적용하기로 하였습니다.
Blue-Green 동작 방법
1. Nginx 설치
// 설치
sudo apt update
sudo apt install nginx
2. Nginx 리버스 프록시 설정
리버스 프록시 설정을 위해서 /etc/nginx/conf.d 위치로 이동을 하고, default.conf 파일을 생성합니다.
cd /etc/nginx/conf.d
vim default.conf
생성한 default.conf 파일에 아래의 내용을 넣어줍니다.
server {
listen 80;
server_name 도메인주소;
location / {
proxy_pass http://localhost:포트번호;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
}
}
3. Certbot 설치 및 Let's Encrypt에서 SSL 인증서 발급
Cerbot을 통해서 SSL인증서를 발급 받을 수 있으며 이를 발급 Cerbot은 snap 패키지 매니저를 통해 설치하는 것이 권장되어서 아래와 같이 snap을 통해 설치합니다.
// snap 패키지 매니저 설치
sudo snap install certbot --classic
// SSL 인증서 발급
sudo certbot --nginx
위의 과정을 거치고 다시 default.conf를 확인해본다면 HTTPS를 위한 여러 설정이 자동으로 추가되어 있습니다.
4. Nginx default.conf 파일 수정
무중단 배포를 위한 변수를 위해 사용할 $service_url 변수를 service-url.inc 파일에 설정해둡니다.
vi /etc/nginx/conf.d/service-url.inc
# 추후에 사용할 애플리케이션 변수 작성
set $service_url http://127.0.0.1:8081;
/etc/nginx/conf.d/default.conf 파일에서 위에서 만든 변수를 활용하여 추후에 무중단 배포를 할 수 있도록 설정합니다.
// sercice-url.inc 파일에서 $service_url 변수 가져오기
include /etc/nginx/conf.d/service-url.inc;
location / {
// service_url 변수로 리버스 프록시 설정하기.
proxy_pass $service_url;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
}
5. 무중단 배포 스크립트 작성
Nginx
profile.sh
현재 nginx와 연결되지 않은 port 찾을 수 있도록 도움을 주는 함수를 모아둔 스크립트입니다.
#!/usr/bin/env bash
function find_idle_profile()
{
RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://{도메인명}/api/profile)
if [ ${RESPONSE_CODE} -ge 400 ]
then
CURRENT_PROFILE=port2
else
CURRENT_PROFILE=$(curl -s https://{domain명}/api/profile)
fi
if [ ${CURRENT_PROFILE} == port1 ]
then
IDLE_PROFILE=port2
else
IDLE_PROFILE=port1
fi
echo "${IDLE_PROFILE}"
}
function find_idle_port()
{
IDLE_PROFILE=$(find_idle_profile)
if [ ${IDLE_PROFILE} == port1 ]
then
echo "8081"
else
echo "8082"
fi
}
stop.sh
profile.sh를 활용하여 쉬고 있는 포트의 작동을 멈추게 합니다.
#!/usr/bin/env bash
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
IDLE_PORT=$(find_idle_port)
echo "> $IDLE_PORT에서 구동중인 애플리케이션 PID 확인"
IDLE_PID=$(sudo lsof -ti tcp:${IDLE_PORT})
if [ -z ${IDLE_PID} ]
then
echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
else
echo "> kill -15 $IDLE_PID"
kill -15 ${IDLE_PID}
sleep 5
fi
start.sh
nginx와 연결되지 않은 port로 springboot 애플리케이션을 시작합니다.
#!/usr/bin/env bash
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
REPOSITORY=/home/ubuntu/app
PROFJECT_NAME={프로젝트명}
IDLE_PORT=$(find_idle_port)
echo "> 새 애플리케이션 배포"
JAR_NAME=$(ls -tr $REPOSITORY/*.jar | tail -n 1)
echo "> JAR Name: $JAR_NAME"
echo "> $JAR_NAME에 실행권한 추가"
chmod +x $JAR_NAME
echo "> $JAR_NAME 실행"
IDLE_PROFILE=$(find_idle_profile)
echo "> $JAR_NAME을 porfile=$IDLE_PROFILE로 실행"
nohup java -jar -Dspring.config.location:$REPOSITORY/application.yml -Dspring.profiles.active=$IDLE_PROFILE $JAR_NAME > $REPOSITORY/nohup.out 2>&1 &
switch.sh
현재 nginx와 연결되지 않은 port를 nginx와 연결하도록 변경합니다.
#!/usr/bin/env bash
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
function switch_proxy()
{
IDLE_PORT=$(find_idle_port)
echo "> 전환할 Port: $IDLE_PORT"
echo "> Port 전환"
echo "set \$service_url http://127.0.0.1:${IDLE_PORT};" | sudo tee /etc/nginx/conf.d/service-url.inc
echo "> 엔진엑스 Reload"
sudo service nginx reload
}
health.sh
nginx와 연결되지 않은 Port가 현재 작동중인지 보고 작동이 되어있다면 위의 Switch.sh를 활용해서 Nginx와 연결된 Port를 바꿔주고, 10번까지 시도해보고 실패한다면 기존에 nginx에 연결되어 있던 port를 그대로 둡니다.
#!/usr/bin/env bash
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
source ${ABSDIR}/switch.sh
IDLE_PORT=$(find_idle_port)
echo "> Health Check Start!"
echo "> IDLE_PORT: $IDLE_PORT"
echo "> curl -s http://localhost:$IDLE_PORT/api/profile "
sleep 10
for RETRY_COUNT in {1..10}
do
RESPONSE=$(curl -s http://localhost:${IDLE_PORT}/api/profile)
UP_COUNT=$(echo ${RESPONSE} | grep 'port' | wc -l)
if [ ${UP_COUNT} -ge 1 ]
then
echo "> Health check 성공"
switch_proxy
break
else
echo "> Health check의 응답을 알 수 없거나 혹은 실행 상태가 아닙니다"
echo "> Health check: ${RESPONSE}"
fi
if [ ${RETRY_COUNT} -eq 10 ]
then
echo "> Health check 실패 "
echo "> 엔진엑스에 연결하지 않고 배포를 종료합니다."
exit 1
fi
echo "> Health check 연결 실패. 재시도..."
sleep 10
done
deploy.sh
위의 스크립트들의 활용하여 무중단 배포를 실질적으로 이루어주는 스크립트입니다.
# nginx와 연결되지 않은 PORT 죽이기
sh stop.sh
# 새로 빌드한 jar 파일을 방금 위에서 죽인 Port로 시작
sh start.sh
# start.sh 과정을 통해 실행된 Port가 실행된다면 nginx의 연결을 바꿈
sh health.sh
Reference
'일정 관리 프로젝트(일치)' 카테고리의 다른 글
[스케줄 관리 프로젝트 - 일치(BE)] Jacoco 적용기 (0) | 2023.05.08 |
---|---|
[스케줄 관리 프로젝트 - 일치(BE)] 성능 개선(1) - JPA의 N + 1 문제 해결 (0) | 2023.05.07 |
[스케줄 관리 프로젝트 - 일치(INFRA)] Jenkins를 활용한 CI/CD(2) - Backend CI/CD 환경 구축 및 사용 (0) | 2023.05.03 |
[스케줄 관리 프로젝트 - 일치(INFRA)] Jenkins를 활용한 CI/CD(1) - 설치 과정 (0) | 2023.05.03 |
[스케줄 관리 프로젝트 - 일치(INFRA)] 성능 테스트 준비 과정 (0) | 2023.05.01 |