무중단 배포란?
말그대로 서비스를 중단하지 않고 배포하는 것을 의미한다.
관련 단어들
- 컴파일 : 프로그래머가 작성한 소스코드를 기계어로 변환
- 빌드 : 소스 코드 파일을 컴퓨터에서 실행할 수 있는 소프트웨어 산출물로 변환
- 배포 : 빌드의 결과물을 사용자가 접근할 수 있는 환경에 배치
무중단 배포가 필요한 이유
같은 포트에서 v1 서비스를 제공하다가 이를 중지하고 새로운 v2 버전을 시작하기까지 서비스는 자연스럽게 중단되게 된다. 이렇게 서비스가 중단되는 시간을 다운타임(Down time)이라고 한다.
물론 유저가 없는 시간에 진행하는 등의 방법으로 회피할 수는 있겠으나, 유저에게 최상의 경험을 제공하기 위해서는 이러한 다운 타임이 없는 무중단 배포를 지원하는 것이 좋다.
리버스 프록시와 로드밸런싱
리버스 프록시
이때 무중단 배포를 지원하기 위해서는 최소한 서버가 띄워지는 곳을 두 곳 이상으로 분리해야 하는데, 이를 위해서는 클라이언트와 서버 사이에 중계하는 누군가가 필요하게 된다.
이를 책임지는 것이 바로 리버스 프록시이다.
- 리버스 프록시란?
- 인터넷과 서버 사이에 위치한 중계 서버
- 클라이언트가 요청한 내용을 캐싱
- 서버 정보를 클라이언트로부터 숨길 수 있어 보안에 용이
로드밸런싱
- 서버에 가해지는 부하를 분산시켜주는 역할
- 하나의 서버가 멈추더라도 서비스 중단 없이 다른 서버가 서비스를 유지할 수 있도록 하여 무중단 배포를 할 수 있도록 해준다.
적용 방법
- AWS에서 Blue-Green 무중단 배포
- 도커를 이용한 무중단 배포
- L4, L7 스위치를 이용한 무중단 배포
- Nginx를 이용한 무중단 배포
Nginx를 이용한 방법은 가장 저렴하고 단순한 편이어서 많은 사람들이 사용한다.
무중단 배포 방식
롤링(Rolling)
구 버전에서 신 버전으로 트래픽을 점진적으로 전환하는 배포
장점
- 인스턴스를 추가하지 않아도 돼서 관리가 간편
단점
- 배포 중 한쪽 인스턴스에 트래픽이 몰릴 수 있음
- 구버전과 신버전의 공존으로 인한 호환성 문제
블루 그린(Blue Green)
구 버전을 블루, 신 버전을 그린 이라고 해서 붙여진 이름이다.
운영 환경에서 구버전과 동일하게 신 버전을 배포하고 일제히 전환해 모든 트래픽을 신버전으로 변경하는 배포 방식이다.
장점
- 배포하는 속도가 빠르다
- 신속하게 롤백이 가능하다
- 남아있는 기존 버전의 환경을 다음 배포에 재사용할 수 있다.
단점
- 시스템 자원이 2배로 필요하다.
카나리(Canary)
카나리 배포 방식은 광산에서 유독가스 누출의 위험을 알리는 용도로 사용됐던 ‘카나리아’에서 기원한 방식이다.
따라서 위험을 빠르게 감지할 수 있는 특징을 가지고 있으며, 신 버전을 특정 서버 혹은 유저에게만 제공하였다가 점점 넓혀갈 수 있다.
장점
- 문제 상황을 빠르게 감지 가능
- A/B 테스트로 활용 가능
단점
- 모니터링 관리 비용
- 구버전과 신버전의 공존으로 인한 호환성 문제
프로젝트 적용 방식
무중단 배포의 경우에는 회고덕 프로젝트에서도 적용되었는데, 우리 서버의 경우 기본적으로 Nginx + BlueGreen 방식을 사용하였다. 하지만, 서버를 두대 띄울 정도의 리소스가 필요하지는 않다고 생각하였기 때문에 위에서 설명한 BlueGreen과는 조금 다른 방식으로 진행되었다. 간단히 정리하자면 다음과 같다.
- 기존 서버의 포트를 확인한다.
- 다른 포트에 서버를 띄운다
- health check를 통해 서버가 잘 띄워졌는지 확인한다.
- 성공 → 기존에 띄워져 있던 포트를 내리고, 새로 띄운 포트로 포트를 변환한다.
- 실패 → 기존에 띄워져있던 서버를 유지한다.
자세한 적용 내용은 하단에 정리해두었다.
## Deploy.sh
#!/bin/bash
# find IDLE PROFILE
echo "> 현재 구동중인 profile 확인"
CURRENT_PROFILE=$(curl -s http://{profile_uri})
echo "> $CURRENT_PROFILE"
if [ $CURRENT_PROFILE == was1 ]
then
IDLE_PROFILE=was2
IDLE_PORT=포트2
elif [ $CURRENT_PROFILE == was2 ]
then
IDLE_PROFILE=was1
IDLE_PORT=포트1
else
IDLE_PROFILE=was1
IDLE_PORT=포트1
echo "> 일치하는 Profile이 없습니다. Profile: $CURRENT_PROFILE"
echo "> was1을 할당합니다. IDLE_PROFILE: was1"
fi
# deploy in IDLE_PORT
IDLE_PID=$(pgrep -f $IDLE_PROFILE)
CURRENT_PID=$(pgrep -f $CURRENT_PROFILE)
echo "> $IDLE_PORT 가 사용 중인 PID 확인 : $IDLE_PID"
if [ -z $IDLE_PID ]; then
echo "> 현재 $IDLE_PORT 가 사용 중인 애플리케이션이 없으므로 종료하지 않습니다."
else
echo "> kill -9 $IDLE_PID"
kill -9 $IDLE_PID
sleep 5
fi
echo "chmod +x jar 변경"
chmod +x /home/ubuntu/deploy/{projectName}-0.0.1-SNAPSHOT.jar
echo "> $IDLE_PROFILE 를 $IDLE_PORT 포트로 실행합니다."
nohup java -jar /home/ubuntu/deploy/{projectName}-0.0.1-SNAPSHOT.jar --spring.profiles.active=$IDLE_PROFILE 1> /dev/null 2>&1 &
echo "> $IDLE_PROFILE 10초 후 Health check 시작"
echo "> curl -s http://localhost:$IDLE_PORT/{healthCheck용 url}"
sleep 10
for retry_count in {1..10}
do
response=$(curl -s http://localhost:$IDLE_PORT/{healthCheck용 url})
up_count=$(echo $response | grep 'UP' | wc -l)
if [ $up_count -ge 1 ]
then
echo "> Health check 성공"
break
else
echo "> Health check의 응답을 알 수 없거나 혹은 status가 UP이 아닙니다."
echo "> Health check: ${response}"
fi
if [ $retry_count -eq 10 ]
then
echo "> Health check 실패. "
echo "> Nginx에 연결하지 않고 배포를 종료합니다."
exit 1
fi
echo "> Health check 연결 실패. 재시도..."
sleep 10
done
# nginx $service_url switching
bash /home/ubuntu/deploy/switch-serve.sh ${IDLE_PORT}
sudo service nginx reload
echo "> kill -9 $CURRENT_PID"
kill -9 $CURRENT_PID
sleep 5
curl -k http://localhost/profile
PROXY_PORT=$(curl -k http://localhost/{profile 확인용url})
echo "> Nginx Current Proxy Port: $PROXY_PORT"
sleep 1
이때 active한 서비스의 포트를 변환하기 위한 파일은 다음과 같다.
## switch-serve.sh
echo "set \$service_url http://127.0.0.1:$1;" | sudo tee /etc/nginx/conf.d/service-url.inc
echo를 통해서 입력값을 받고 ($1부분), tee를 통해서 파일에 출력을 진행한다. (>>를 통해서도 출력할 수 있으나, 관리자 권한이 필요했기 때문에 tee를 사용하였다.)
이렇게 변경한 service_url을 nginx에 연결시키면 nginx를 통한 무중단 배포가 가능하다.
참고자료
'프로그래밍 공부' 카테고리의 다른 글
InnoDB 스토리지 아키텍처 (7) | 2022.10.17 |
---|---|
테스트 코드 시간 줄이기 (2) | 2022.10.07 |
VPC란 무엇인가? (2) | 2022.09.20 |
[TDD] 테스트 코드 작성 순서와 종류 (0) | 2022.05.19 |
도메인이란 무엇인가? (1) | 2022.05.15 |