이번에 프로젝트를 진행하면서 기능 구현이 빈번하게 일어나면서 사용중에 서버가 다운되는 현상이 자주 발생하였습니다. 이러한 사용중 불편함을 해소하고자 무중단 배포를 적용하여 사용 중에 서비스를 사용하지 못하는 상황을 막기 위해 도입하기로 결정했습니다.
무중단 배포란?
먼저, 무중단 배포 이전에 배포란 무엇일까요?
배포란 새로 개발된 코드를 패키징하여 서버에 새로운 버전의 애플리케이션을 실행시키는 행위를 의미합니다.
배포시에 새로운 버전의 애플리케이션을 실행하기 위해 기존에 존재하던 애플리케이션을 종료해야 하는데 이 과정에서 사용자에게 서비스가 되지 않는다면 사용자들은 불편함을 느끼게 될 것입니다.
이런 상황을 피하기 위해 서비스를 중단하지 않고 배포하는 행위인 무중단 배포를 하게 되었습니다.
무중단 배포는 어떻게 이루어지나요?
배포중에 서비스가 내려가고 다시 올라가는 동안 서비스가 불가능한 것은 어쩔 수 없습니다.
이 때동안 서비스를 할 수 있는 방법은 없을까요?
가장 간단한 해결책으로 서버를 두대 이상 준비하고 서버를 한대씩 업데이트 하면 해결될 것입니다.
하지만 이 해결책도 문제점이 존재합니다.
- 사용자가 각 각 서버의 IP를 알아야 합니다.
- 어떤 서버가 실제 운영중인지 알 수 없습니다.
결국 사용자는 각각 서버에 대한 정보를 알아야 하고 어떤 서버가 현재 운영중인지 알기 위해 하나씩 다 접근해야 하는 것입니다.
리버스 프록시
이러한 문제를 해결하기 위해서는 클라이언트와 서버 사이의 중계자를 두어 해당 중계자가 요청을 서버에 전달시키도록 할 수 있습니다.
이렇게 클라이언트의 요청을 대신 받아 내부 서버로 전달해주는 것을 리버스 프록시라고 부릅니다.
리버스 프록시를 사용하면 다음과 같은 장점을 얻을 수 있습니다.
- 로드 밸런싱 : 클라이언트의 요청이 특정 서버에 몰리지 않도록 부하를 분산함으로써 성능 및 확장성을 향상시킬 수 있습니다.
- 캐싱 : 미리 렌더링 된 페이지를 캐시하여 페이지 로드 시간을 단축할 수 있습니다.
- 보안 : 클라이언트는 서버의 정보(IP와 같은)를 알 수 없기 때문에 보안측면에서 더 안전합니다.
nginx를 사용하면 이러한 리버스 프록시 서버를 구축할 수 있습니다.
무중단 배포 방식
이제 중간에서 트래픽을 어디로 보낼지 정해주는 리버스 프록시를 두어 현재 운영중인 서버에게 보낼 수 있다는 사실을 알게 되었습니다.
그렇다면 실제 무중단 배포를 위해 어떤 방법들이 이용되는지 알아봅시다.
가장 대표적인 배포 전략으로 롤링, 블루/그린, 카나리 배포 방법이 사용됩니다.
롤링(Rolling)
롤링 배포는 점진적으로 이전 버전의 애플리케이션을 신규 버전의 애플리케이션으로 교체해나가는 전략입니다.
서비스 중인 인스턴스 하나를 로드밸런서에서 라우팅하지 않도록 한 뒤, 새 버전을 적용한 후 다시 라우팅하도록 하는 방식입니다.
- 장점
- 인스턴스마다 차례대로 배포를 진행하기 때문에 손쉽게 롤백이 가능합니다.
- 서비스에 사용하는 만큼만 서버를 사용할 수 있습니다.
- 단점
- 새 버전을 배포할때 서비스 중인 서버의 수가 감소하기 때문에 사용중인 인스턴스에 일시적으로 트래픽이 과하게 몰릴 수 있습니다. 이 때문에, 배포시에 서비스 처리가 문제 없이 될 수 있도록 서비스 처리 용량을 고려해야 합니다.
- 배포가 진행될 때 이전 버전과 새로운 버전이 공존할 수 있기 때문에 사용 중 호환성 문제가 발생할 수 있습니다. 위의 그림에서 2번의 경우 사용자는 v1으로도 서비스를 제공받을 수 있고 v2로도 서비스를 제공받을 수 있기 때문에 사용자들은 같은 서비스를 받는 것을 보장받지 못합니다.
블루/그린(Blue/Green)
블루/그린 배포는 두 가지 운영환경을 나란히 운영하여 로드 밸런서를 통해 한번에 신규 버전으로 트래픽을 전환하여 배포하는 전략입니다.
- 장점
- 구 버전의 인스턴스가 그대로 남아있기 때문에 빠르고 손쉬운 롤백이 가능합니다.
- 운영환경에 영향을 주지 않고 새 버전을 쉽게 테스트할 수 있습니다.
- 단점
- 시스템 자원이 두배로 필요합니다.
카나리(Canary)
카나리 배포는 가동 중인 서버 일부에만 새로운 버전을 배포하여 일부 트래픽만 새로운 버전으로 보낸 후 이를 점차적으로 늘려 최종적으로 전체 애플리케이션을 새 버전으로 적용시키는 방법입니다.
- 장점
- A/B 테스트로 활용 가능합니다. (기존서비스와 새로 적용하고 싶은 서비스를 제공하여 사용자에게 어떤 서비스가 더 선호되는지 확인할 수 있습니다.)
- 일부에게 먼저 제공함으로써 오류가 발생시 빠르게 감지할 수 있습니다.
- 단점
- 네트워크 트래픽을 직접 제어해야하므로 난이도가 있습니다.
Nginx를 이용한 무중단 배포
블루 - 그린 배포 방식을 사용하여 무중단 배포를 진행하겠습니다.
이전에 HTTPS 설정을 했던 내용을 기반으로 무중단 배포를 할 수 있도록 컨테이너를 하나 더 추가해주겠습니다.
이전 HTTPS 환경 구축은 다음 글을 참고하시길 바랍니다.
이번에 구축할 환경의 구성은 다음과 같습니다.
서버는 사용자로부터 HTTP(HTTPS) 요청을 받으면 nginx에 지정한 애플리케이션 서버로(현재 운영중인 서버) 요청을 전달합니다.
만약 배포가 이루어질시 green에 해당 하는 컨테이너를 업데이트하고 nginx가 green을 가르키도록 변경합니다.
일단 이전에 작성했던 도커 컴포즈 파일을 수정합시다.
현재는 애플리케이션이 app이 하나밖에 존재하지 않는데 다음과 같이 blue와 green으로 컨테이너를 추가합시다.
그리고 추가적으로 nginx 설정 파일을 외부에서 관리하기 위해 {내 설정파일 위치}:{nginx 컨테이너의 설정 파일 위치}를 volumes로 매핑해줍니다.
version: "3"
services:
web-server:
image: nginx
container_name: nginx
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/:/etc/nginx/conf.d/
blue:
image: ${YOUR_APPLICATION_IMAGE}
container_name: ${YOUR_APPLICATION_CONTAINER_NAME}
restart: always
expose:
- "8080"
volumes:
- ./:/home/ubuntu
- /etc/localtime:/etc/localtime
green:
image: ${YOUR_APPLICATION_IMAGE}
container_name: ${YOUR_APPLICATION_CONTAINER_NAME}
restart: always
expose:
- "8080"
volumes:
- ./:/home/ubuntu
- /etc/localtime:/etc/localtime
이제 nginx가 blue를 가르키게 하도록 nginx 설정파일을 만들어줍시다 (./nginx/default.conf)
server {
listen 80;
listen [::]:80;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /etc/letsencrypt/live/{도메인주소}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{도메인주소}/privkey.pem;
server_name {내도메인 주소};
location / {
proxy_pass http://blue:8080; # 이부분이 수정되는 부분
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
이제 배포시에 nginx가 가르키는 애플리케이션을 변경하기 위해 쉘 스크립트를 작성합시다.
# !/bin/bash
RUNNING_APPLICATION=$(docker ps | grep blue)
DEFAULT_CONF="nginx/default.conf"
if [ -n "$RUNNING_APPLICATION" ];then
echo "green Deploy..."
docker-compose pull green
docker-compose up -d green
while [ 1 == 1 ]; do
echo "green health check...."
REQUEST=$(docker exec nginx curl http://green:8080)
echo $REQUEST
if [ -n "$REQUEST" ]; then
break ;
fi
sleep 3
done;
sed -i 's/blue/green/g' $DEFAULT_CONF
docker exec nginx service nginx reload
docker-compose stop blue
else
echo "blue Deploy..."
docker-compose pull blue
docker-compose up -d blue
while [ 1 == 1 ]; do
echo "blue health check...."
REQUEST=$(docker exec nginx curl http://blue:8080)
echo $REQUEST
if [ -n "$REQUEST" ]; then
break ;
fi
sleep 3
done;
sed -i 's/green/blue/g' $DEFAULT_CONF
docker exec nginx service nginx reload
docker-compose stop green
fi
RUNNING_APPLICATION=$(docker ps | grep blue)
- 현재 실행중인 애플리케이션이 blue인지 확인하기 위해 사용하는 명령입니다. docker ps | grep blue의 결과가 RUNNING_APPLICATION에 들어갑니다.
DEFAULT_CONF="nginx/default.conf"
- 현재 nginx 컨테이너와 연결한 nginx 설정파일의 위치를 지정합니다.
if [ -n "$RUNNING_APPLICATION" ];then
- RUNNING_APPLICATION의 결과가 빈문자열인지 확인합니다. 만약 blue에 해당하는 Application이 없는지 확인합니다. 있다면 blue가 실행중이기 때문에 green을 배포할 준비를 해야 합니다.
- green이 실행중이라면 green을 포함하는 컨테이너가 떠있지 않는 이상 빈문자열이 들어가게 될 것입니다.
echo "green Deploy..."
docker-compose pull green
docker-compose up -d green
- green Deploy...라는 문자열을 표준 출력에 출력하고 컨테이너 이미지를 다운받고(pull) 해당 이미지로 컨테이너를 실행하게 됩니다(up). 실행은 백그라운드로 이루어지게 됩니다.(-d)
while [ 1 == 1 ]; do
echo "green health check...."
REQUEST=$(docker exec nginx curl http://green:8080)
if [ -n "$REQUEST" ]; then
break ;
fi
sleep 3
done;
- nginx 컨테이너만 현재 애플리케이션에 접근가능하므로 nginx 컨테이너에서 green 애플리케이션에 요청을 보냅니다. 요청 결과값으로 데이터가 넘어온다면 현재 서비스 가능한 상태라는 것을 알 수 있으므로 무한루프에서 빠져나오고 아니면 sleep을 통해 잠깐 쉬었다가 다시 요청을 보냅니다
즉, 서비스가 사용가능한 상태까지 기다리는 용도의 구문입니다.
sed -i 's/blue/green/g' $DEFAULT_CONF
- nginx가 가르키고 있는 애플리케이션을 수정하기 위해 sed 명령어를 사용하여 blue를 green으로 변경하도록 합니다.
해당 명령이 실행되면 default.conf가 다음과 같이 변경됩니다.
server {
listen 80;
listen [::]:80;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /etc/letsencrypt/live/{도메인주소}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{도메인주소}/privkey.pem;
server_name {내도메인 주소};
location / {
proxy_pass http://green:8080; # 이부분이 수정되는 부분
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
docker exec nginx service nginx reload
- 이제 nginx 설정 파일의 변경사항을 적용하기 위해 nginx를 재시작 합니다.
docker-compose stop blue
- 이제 이전 버전이었던 blue를 종료합니다.
else
echo "blue Deploy..."
docker-compose pull blue
docker-compose up -d blue
while [ 1 == 1 ]; do
echo "blue health check...."
REQUEST=$(docker exec nginx curl http://blue:8080)
echo $REQUEST
if [ -n "$REQUEST" ]; then
break ;
fi
sleep 3
done;
sed -i 's/green/blue/g' $DEFAULT_CONF
docker exec nginx service nginx reload
docker-compose stop green
fi
- green이 켜져 있는 경우에는 blue를 최신버전으로 변경하고 가리키게 하는 부분을 추가합니다.
이제 배포시에 해당 shell script만 실행시키면 무중단 배포를 할 수 있게 만들 수 있습니다.
bash deploy.sh
여기까지 블루/그린 배포로 무중단 배포환경을 구축하는 것을 직접 만들어보았습니다.
참고자료
https://velog.io/@znftm97/무중단-배포를-위한-환경-이해하기
https://velog.io/@woodonggyu/Deployment-Strategies
'DevOps > CICD' 카테고리의 다른 글
Github actions + Jenkins를 통한 자동 배포 구현 (0) | 2022.08.14 |
---|---|
JIB을 이용한 컨테이너 배포 (0) | 2022.08.02 |
sonarQube를 이용한 CI 코드 정적 분석 기능 추가 (0) | 2022.07.30 |
Github actions에서 jacoco를 통해 테스트 커버리지 확인하기 (0) | 2022.07.29 |