최근 도커를 활용하는 방법(도커, 레지스트리, 도커 파일)에 대한 글을 작성했습니다. 도커를 활용하면 무중단 배포를 쉽게 구현할 수 있어서 간단하게 무중단 배포를 구현하는 방법에 대해 알아보겠습니다.
무중단 배포란?
먼저 무중단 배포란 서비스를 업데이트 할 때 기존 서비스에 영향을 주지 않고 업데이트 하는 것을 말합니다. 기존에서는 서비스를 업데이트 할 때 서비스를 중단하고 업데이트를 해야 했습니다. 왜냐하면 업데이트 되지 않은 서비스가 포트를 점유하고 있기 때문에 서비스를 내린 내리지 않으면 포트를 사용할 수 없기 때문입니다. 그래서 서비스를 내린 후 업데이트를 하고 다시 서비스를 올려야 했습니다.
서비스가 내려가고 올라가는 시간 동안 사용자는 서비스를 이용할 수 없습니다. 이런 시간을 다운타임이라고 합니다. 무중단 배포는 이런 다운타임을 최소화 하기 위해 사용됩니다.
무중단 배포 방식
무중단 배포 방식은 여러가지가 있습니다. 그 중 Blue-Green 배포 방식을 알아보겠습니다.
블루 그린 배포 방식은, 구버전과 동일하게 신버전을 구성을 하고 한 번에 트래픽을 신버전으로 전환하는 방식입니다.
장점은 구버전과 신버전이 존재해서 신버전에 오류가 발생하는걸 파악하면 트래픽을 구버전으로 전환해서 빠른 롤백이 가능합니다. 또한 실제 운영환경과 동일한 환경에서 테스트가 가능합니다.
하지만 구버전과 신버전이 두 개가 존재하므로 자원이 두 배로 필요한 단점이 있습니다.
무중단 배포 아이디어
제가 사용하는 서비스는 서버 한 대에 한 개의 컨테이너를 사용하고 있습니다. 컨테이너를 두 개 유지하기는 부담스러웠습니다. 그래서 Blue-Green 배포 방식에서 살짝 변형 해, 새로운 버전을 테스트 하지 않고 바로 트래픽을 전환하는 방식을 사용했습니다. 그러면 컨테이너가 두 개가 존재하는 시간이 짧아져 두 배의 자원을 사용하는 시간이 짧아집니다.
구체적인 방법은 다음과 같습니다.
blue 컨테이너는 이미 실행 중이고 green 컨테이너는 실행 중이지 않습니다.
green 컨테이너를 최신 버전으로 빌드합니다.
green 컨테이너를 실행합니다.
nginx로 기존 blue 컨테이너 요청을 green 컨테이너로 보냅니다.
blue 컨테이너를 종료합니다.
blue, green의 실행 상황이 반대여도 상관이 없게 구현합니다.
주의 해야할 부분은 컨테이너 실행 명령어를 사용하더라도 컨테이너가 실행되는 시간이 있기 때문에 다운타임이 발생 할 수 있습니다. 그래서 컨테이너가 실행되는 시간을 고려해서 무중단 배포를 구현해야 합니다.
nginx에서 요청을 다른 컨테이너로 바꿀 때 재시작을 하게 되면 다운타임이 발생할 수 있습니다. 그래서 nginx의 설정파일을 바꾼 후에 reload 명령어를 사용해서 무중단 배포를 구현할 수 있습니다.
COMPOSE_FILE_PATH="/home/ubuntu/docker/docker-compose.yml" NGINX_CONF_DIR="/home/ubuntu/docker/nginx/conf.d" SLEEP_TIME=10 # 필요시 조정 NGINX_SERVICE_NAME="nginx"# docker-compose에서 정의된 Nginx 서비스 이름 HEALTH_CHECK_ENDPOINT="http://localhost:8080"# 헬스 체크 엔드포인트
# 현재 실행 중인 환경 확인 (무조건 한 개의 서비스는 실행 중이여야 함) if docker-compose -f $COMPOSE_FILE_PATH ps | grep dang-blue; then CURRENT_ENV=dang-blue NEW_ENV=dang-green CURRENT_CONF=blue.conf NEW_CONF=green.conf.tmp else CURRENT_ENV=dang-green NEW_ENV=dang-blue CURRENT_CONF=green.conf NEW_CONF=blue.conf.tmp fi
# 새로운 환경 이미지 풀링 echo"Pulling the new environment image: $NEW_ENV" docker-compose -f $COMPOSE_FILE_PATH pull $NEW_ENV
# 새로운 환경 이미지 빌드 (캐시 무시) echo"Building the new environment image: $NEW_ENV with no cache" docker-compose -f $COMPOSE_FILE_PATH build --no-cache $NEW_ENV
# 새로운 환경 시작 echo"Starting new environment: $NEW_ENV" docker-compose -f $COMPOSE_FILE_PATH up -d --no-deps $NEW_ENV
# 새로운 환경 시작 대기 sleep $SLEEP_TIME
# 헬스 체크 대기 echo"Waiting for the new environment to be healthy..." for i in {1..30}; do HTTP_STATUS=$(docker-compose -f $COMPOSE_FILE_PATHexec -T $NEW_ENV curl -s -o /dev/null -w "%{http_code}"$HEALTH_CHECK_ENDPOINT) if [ "$HTTP_STATUS" -eq 200 ]; then echo"New environment is healthy!" break else echo"Waiting for new environment to be healthy..." sleep $SLEEP_TIME fi done
if [ "$HTTP_STATUS" -ne 200 ]; then echo"New environment did not become healthy in time." exit 1 fi
# Nginx 설정 업데이트 echo"Updating Nginx configuration..." mv $NGINX_CONF_DIR/$CURRENT_CONF$NGINX_CONF_DIR/$CURRENT_CONF.tmp mv $NGINX_CONF_DIR/$NEW_CONF$NGINX_CONF_DIR/${NEW_CONF%.tmp}
# 이전 환경 중지 및 제거 echo"Stopping and removing the old environment: $CURRENT_ENV" docker-compose -f $COMPOSE_FILE_PATH stop $CURRENT_ENV docker-compose -f $COMPOSE_FILE_PATH rm -f $CURRENT_ENV
echo"Deployment complete."
주석을 달았지만 간단하게 설명하면 다음과 같습니다.
현재 실행 중인 환경이 blue인지 green인지 확인합니다.
새로운 환경 이미지를 풀링하고 빌드합니다.
새로운 환경을 시작합니다.
docker-compose up이 정상 실행될때까지 잠시 대기합니다.
명시한 헬스 체크 엔드포인트가 정상 응답할 때까지 대기합니다.
.tmp로 끝나는 파일을 .conf로 변경하고 .conf로 끝나는 파일을 .conf.tmp로 변경합니다.
Nginx를 리로드합니다.
이전 환경을 중지하고 제거합니다.
작동 확인
blue, green 중 실행중인 컨테이너와 .conf 파일이 일치하는지 확인합니다. 그리고 스크립트를 실행합니다.
1
$ ./deploy.sh
현재 서비스는 서버 ip를 보여주는 간단한 서비스입니다. curl을 계속 실행하면서 중단 없이 서버 ip가 바뀌는지 확인합니다.
1
whiletrue; do curl https://your-domain.com; echo"";sleep 1; done
172.10.0.2에서 오류 없이 172.10.0.5로 변경되는 것을 확인할 수 있습니다.
결론
간단하게 무중단 배포를 구현하는 방법에 대해 알아보았습니다. CI과정은 현재 글에서 설명하지는 않았지만 레지스트리를 사용해서 최신 이미지를 올리고 위 스크립트를 사용하면 CI/CD를 쉽게 구축할 수 있습니다.