-
Notifications
You must be signed in to change notification settings - Fork 0
무중단 배포에 대해 알아보자!
김동철 edited this page Aug 29, 2023
·
27 revisions
서비스가 운영 중일 때, 새로운 버전을 배포하기 위해서는 기존 서비스를 종료하고 새로운 서비스를 시작해야 합니다. 이 과정에서 다운타임(Downtime)이 필연적으로 발생하며, 해당 시간동안 사용자들은 서비스를 이용할 수 없게 됩니다. 이러한 다운타임을 해결해주는 방법이 바로 무중단 배포입니다.
즉, 서비스를 중단하지 않고 새로운 버전의 애플리케이션을 배포하는 방식을 무중단 배포라고 합니다.
- 다운타임(Downtime): 시스템을 이용할 수 없는 시간을 일컫는다. 이용 불가능의 의미는 시스템이 오프라인이거나 사용할 수 없는 상황에 놓이는 상태를 가리킨다.
- 트래픽을 점진적으로 구 버전에서 신 버전으로 옮기는 방식입니다.
- 인스턴스를 하나 추가하여 새로운 버전의 애플리케이션을 실행한다
- 로드 밸런서에 추가된 인스턴스를 연결한다
- 구 버전의 인스턴스를 종료한다.
- 위의 과정을 모든 인스턴스에 대해 반복한다.
- 서버 개수를 유연하게 조절할 수 있는 클라우드 환경에 적합한 방법입니다.
- 구 버전의 인스턴스를 로드 밸런서에서 해제하고
- 새로운 버전의 애플리케이션을 실행한 뒤
- 다시 로드 밸런서에 연결시키는 방법입니다.
- 물리적인 서버로 서비스를 운영하는 온프레미스(On-premise) 환경에 적합한 방식입니다.
- On-premise : 서버를 클라우드와 같은 '가상의 공간'이 아니라, 자체적으로 보유하고 있는 서버에 직접 설치하고 운영하는 방식
- Blue/Green 배포 방식에 비해, 많은 서버 자원을 확보하지 않아도 된다.
- 점진적으로 새로운 버전이 사용자에게 출시되므로, 배포로 인한 위험성을 줄일 수 있다.
- 방법 2의 경우, 서비스 중인 인스턴스의 수가 줄어들면서 각각의 서버가 부담하는 트래픽의 양이 늘어날 수 있습니다. 따라서 전체 트래픽의 양과 단일 서버가 처리할 수 있는 트래픽의 양을 잘 고려하여 배포를 진행해야 합니다.
- 또한, 구버전과 신버전의 어플리케이션이 동시에 서비스되기 때문에 호환성 문제가 발생할 수 있습니다.
-
트래픽을 한번에 구버전에서 신버전으로 옮기는 전략입니다.
-
현재 운영중인 서비스의 환경을 Blue라고 부르고, 새롭게 배포할 환경을 Green이라고 합니다.
-
Blue와 Green의 서버를 동시에 구성해둔 상태로, 배포 시점에 로드 밸런서로 트래픽을 Blue에서 Green으로 일제히 전환시킵니다.
- 롤링 배포와 카나리 배포와 달리, 전체 트래픽을 한번에 새로운 버전으로 옮기기 때문에 호환성 문제가 발생하지 않고 신속한 배포가 가능합니다.
- 실제 서버 운영에 필요한 리소스 대비 2배의 리소스를 확보해야 합니다. 따라서, 온프레미스(On-premise) 환경의 경우 비용 부담이 크다는 단점이 있습니다.
- 서버 트래픽의 일부를 신 버전으로 분산하여 오류 여부를 확인한 뒤, 이상이 없으면 전체를 배포하는 전략입니다.
- 새로운 버전의 배포로 인한 위험을 최소화 할 수 있습니다.
- A/B 테스트가 가능하며, 성능 모니터링에 유용합니다.
- 롤링 배포와 마찬가지로 신/구 버전의 애플리케이션이 동시에 존재하므로 호환성 문제가 발생할 수 있습니다.
#! /bin/bash
DOCKER_COMPOSE_FILE=$1
DOCKER_USERNAME=$2
DOCKER_REPO=$3
ABS_PATH=$(readlink -f "$0")
ABS_DIR=$(dirname "$ABS_PATH")
source "$ABS_DIR"/profile.sh
IDLE_CONTAINER=$(find_idle_profile)
echo "> Nginx, Redis container 실행"
docker compose -f "$DOCKER_COMPOSE_FILE" up nginx redis -d --build
# $IDLE_CONTAINER의 컨테이너 ID를 찾고, 있다면 제거
if [ "$(docker ps -aqf name="^$IDLE_CONTAINER$")" ];
then
echo "> $IDLE_CONTAINER container 제거"
docker stop "$IDLE_CONTAINER" && docker rm "$IDLE_CONTAINER"
else
echo "> 구동 중인 유휴 spring container가 없으므로 종료하지 않습니다."
fi
if [[ "$(docker images -q "$DOCKER_USERNAME"/"$DOCKER_REPO":latest 2> /dev/null)" != "" ]]; then
echo "> latest image tag를 old로 변경"
docker rmi "$DOCKER_USERNAME"/"$DOCKER_REPO":old
docker tag "$DOCKER_USERNAME"/"$DOCKER_REPO":latest "$DOCKER_USERNAME"/"$DOCKER_REPO":old
docker rmi "$DOCKER_USERNAME"/"$DOCKER_REPO":latest
fi
echo "> $IDLE_CONTAINER container 실행"
docker compose -f "$DOCKER_COMPOSE_FILE" up "$IDLE_CONTAINER" -d --build
#! /bin/bash
DOCKER_COMPOSE_FILE=$1
DOCKER_USERNAME=$2
DOCKER_REPO=$3
ABS_PATH=$(readlink -f "$0")
ABS_DIR=$(dirname "$ABS_PATH")
source "$ABS_DIR"/[profile.sh](http://profile.sh/)
source "$ABS_DIR"/[switch.sh](http://switch.sh/)
IDLE_PORT=$(find_idle_port)
echo "> Health check 시작"
echo "> IDLE_CONTAINER: $IDLE_PORT"
echo "> curl -s http://localhost:$IDLE_PORT/profiles"
sleep 10
for RETRY_COUNT in $(seq 1 10)
do
RESPONSE=$(curl -s http://localhost:"$IDLE_PORT"/profiles)
UP_COUNT=$(echo "$RESPONSE" | grep -c "spring") # spring이 들어간 행의 개수
if [ "$UP_COUNT" -ge 1 ]
then
echo "> Health check 성공"
switch_proxy
if [ "$?" -ge 1 ]
then
exit 1
fi
# 성공 시 break
break
else
echo "> 응답 실패"
echo "> Health check: $RESPONSE"
fi
if [ "$RETRY_COUNT" -eq 10 ]
then
echo "> Health 실패"
echo "> Nginx에 연결하지 않고 배포를 종료합니다."
echo "> 배포에 실패한 container 삭제"
IDLE_CONTAINER=$(find_idle_profile)
docker stop "$IDLE_CONTAINER" && docker rm "$IDLE_CONTAINER"
echo "> 실패한 docker latest image 삭제"
docker rmi "$DOCKER_USERNAME"/"$DOCKER_REPO":latest
exit 1
fi
echo "> Health check 실패, 5초 후 재시도..."
sleep 5
done
#! /bin/bash
# nginx와 연결되지 않은 profile 찾기(활성화시킬 profile)
function find_idle_profile() {
RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://api.youngcha.team/profiles)
if [ "$RESPONSE_CODE" -ge 400 ]
then
# nginx와 연결된 컨테이너가 없으면 spring2가 연결되어 있다고 설정
CURRENT_PROFILE=spring2
else
CURRENT_PROFILE=$(curl -s https://api.youngcha.team/profiles)
fi
# 구동할 profile 설정
if [ "$CURRENT_PROFILE" == spring1 ]
then
IDLE_PROFILE=spring2
else
IDLE_PROFILE=spring1
fi
echo "$IDLE_PROFILE"
}
# 쉬고 있는 profile의 port 찾기
function find_idle_port(){
IDLE_PROFILE=$(find_idle_profile)
if [ "$IDLE_PROFILE" == spring1 ]
then
echo "8081"
else
echo "8082"
fi
}
#! /bin/bash
ABS_PATH=$(readlink -f "$0")
ABS_DIR=$(dirname "$ABS_PATH")
source "$ABS_DIR"/profile.sh
function switch_proxy(){
IDLE_CONTAINER=$(find_idle_profile)
echo "> 전환할 컨테이너: $IDLE_CONTAINER"
docker exec nginx /bin/sh -c "echo set '\$service_url http://$IDLE_CONTAINER:8080;' | tee /etc/nginx/conf.d/service-url.inc"
echo "Nginx 재시작"
docker exec -i nginx nginx -s reload
for RETRY_COUNT in $(seq 1 5)
do
INACTIVE_CONTAINER=$(find_idle_profile)
if [ "$IDLE_CONTAINER" != "$INACTIVE_CONTAINER" ]
then
echo "> Nginx에 연결되지 않은 container 삭제"
docker stop "$INACTIVE_CONTAINER" && docker rm "$INACTIVE_CONTAINER"
break
fi
echo "Switch delayed"
sleep 0.2
if [ "$RETRY_COUNT" -eq 5 ]
then
echo "Nginx 전환에 실패했습니다."
exit 1
fi
done
}
- FE - 나도 오픈소스 개발자? (NPM 배포기)
- FE - 합성 컴포넌트에 스토리북 한 스푼 🥄
- FE - Tailwind CSS 찐하게 사용해보기
- AOS - 안드로이드 네트워크 연결
- AOS - API 요청에 따른 동적 탭 생성
- AOS - 나도 오픈소스 개발자? (jitpack 배포기)
- AOS - 폭죽 애니메이션
- AOS - 가이드 모드 애니메이션
- AOS - 뷰모델과 애니메이션을 같이 사용했을때의 ISSUE
- BE - 무중단 배포에 대해 알아보자!
- BE - 더 무중단스러운 배포를 위한 graceful shutdown
- BE - 쿼리 최적화에 대해 알아보자!
- BE - 실전, 쿼리 가속도 업!