무중단 배포가 무엇일까?
- 무중단 배포는 재배포하는 도중에도 서비스를 종료하지 않고 배포한것을 적용하는 것을 의미합니다.
- 그럼 어떻게 무중단 배포를 구현할까요?
- A라는 서비스를 사용자들이 사용하고 있다고 해보겠습니다.
- 이때 A 서비스에 기능을 추가한 B 서비스가 등장합니다.
- A 서비스를 제공하고 있는 도중에 B 서비스를 배포합니다.
- 이 후 B 서비스가 안정화가 되면 A 서비스로 가는 트랙픽을 B 서비스로 바꿔줍니다.
- 이러면 사용자는 재배포에서 발생하는 다운타임을 느끼지 못한채 새로운 기능이 추가된 서비스를 사용할 수 있는 것입니다!
무중단 배포에는 무엇이 있을까요?
- 블루그린 배포 / 카나리 배포 / 롤링 업데이트 배포가 있습니다.
- 블루 그린 배포가 위에서 언급한 방식대로 동작합니다. 기존 서비스는 블루 서비스라고 하면 새로 대체되는 서비스가 그린 서비스인 것이지요.
- 그럼 카나리 배포와 롤링 업데이트가 무엇일까요?
카나리 배포
- 카나리 배포는 새 버전을 소수의 사용자에게 선 배포합니다.
- 이 후 안정성이 검토되면 점차적으로 더 많은 사용자의 트래픽을 배포된 서버로 옮기고 기존 서버를 종료시킵니다.
롤링 업데이트 배포
- 기존 인프라 위에서 점진적으로 업데이트 하는 방식으로 서버의 총 숫자가 변하지 않습니다.
- 예를 들어 기존 3개의 서버가 존재하면 서버 하나씩 점진적으로 업데이트 하는 방식입니다.
그렇다면 저희는 왜 블루그린 배포를 선택했을까요?
- PM에서 자체적으로 무중단 배포를 제공합니다.
- 하지만 저희는 서비스를 도커로 배포하기로 결정했고 이 과정에서 PM2의 무중단 배포의 장점이 사라집니다.
- 따라서 무중단 배포의 장점을 그대로 가져가기 위해 무중단 배포를 선택했습니다.
- 그중 블루 그린 배포가 도커 서비스 환경에서 가장 구현하기 적합할 것 같아서 적용했습니다.
도커로 배포를 한 이유가 무엇인가요?
- 가장 간편하게 기존 서비스를 삭제하기 재시작할 수 있기 때문입니다.
- 서비스의 수정 사항이 발생했을때 로컬에서 이미지를 빌드한 후 인스턴스에 접속한후 해당 이미지로 재배포만 이루어지면 되기 때문에 가장 간단합니다.
- 즉 깃허브에 코드를 올리기 전에 실제 테스트가 가능하기 때문입니다.
블루 그린 배포는 어떻게 구현할까?
- 일단 저희는 모든 서비스를 도커로 운영하고 있습니다. 즉 실제 서버 뿐 아니라 Nginx도 도커입니다.
- 또한 클라이언트의 요청이 올때 Nginx 리버스 프록시를 통해 서버로 라우팅하고 있습니다.
- 크게 2가지 처리를 해야합니다.
- 블루 컨테이너와 그린 컨테이너를 적절한 시기에 띄우고 종료하기
- Nginx 리버스 프록시를 통해 트래픽을 기존 서버에서 새로운 서버로 바꾸기
Nginx는 블루 컨테이너로 트래픽을 보냅니다
- Nginx는 블루 컨테이너(기존 서버)와 연결되어 트래픽을 전달합니다.
새롭게 업데이트된 서버의 이미지로 그린 컨테이너를 띄웁니다.
- 서버의 업데이트를 반영한 이미지를 통해 그린 컨테이너 서비스를 띄웁니다.
그린 컨테이너가 안정화되면 트래픽을 그린 컨테이너로 변경합니다.
- 그린 컨테이너가 요청을 처리할 수 있는 상태가 되면 Nginx 설정을 리로드하여 트래픽을 변경합니다.
블루 컨테이너를 종료합니다.
- 리소스를 잡아먹는 기존 블루 컨테이너를 종료합니다.
1. 도커 컴포즈 파일 작성하기
- 먼저 블루 컨테이너와 그린 컨테이너 그리고 Nginx 서비스를 정의하는 컴포즈 파일을 작성합니다.
- Nginx 리버스 프록시를 통해 443 포트로 접근하면 3001번 포트 또는 3002번 포트로 라우팅 되는 것입니다.
- 여기서 3000 포트는 각 도커 내부에서 서비스 포트입니다.
version: '3.8'
services:
blue:
container_name: blue
image: 이미지 이름
ports:
- 3001:3000
env_file:
- .env
green:
container_name: green
image: 이미지 이름
ports:
- 3002:3000
env_file:
- .env
nginx:
container_name: nginx
image: 이미지 이름
ports:
- 80:80
- 443:443
depends_on:
- blue
- green
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- /etc/letsencrypt:/etc/letsencrypt
2. Nginx 리버스 프록시 설정하기
- Nginx 리버스 프록시를 통해 블루 또는 그린 서비스로 라우팅합니다.
- 443 포트로 요청이 왔을때 로케이션을 통해 리버스 프록시를 진행합니다.
- proxy_pass에 어떤 경로로 라우팅 할지 결정합니다.
upstream blue_backend {
server blue:3000;
}
upstream green_backend {
server green:3000;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name 도메인;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name 도메인;
ssl_certificate /etc/letsencrypt/live/도메인/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/도메인/privkey.pem;
location / {
proxy_pass http://blue_backend;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
3. 새로운 서버 배포 후 Nginx 설정을 리로드합니다.
- 기존 서버로 라우팅을 했다면 이제 새로운 서버를 띄웁니다.
- 이제 새로운 서버로 라우팅을 수정하고 Nginx 설정을 리로드합니다.
- 도커에 로그인한 후 새로운 서버 이미지를 받아올 준비를 합니다.
- 현재 서비스가 블루 서비스인지 그린 서비스인지 체크한 후 상응하는 서비스로 재배포를 준비합니다.
- Nginx 설정을 리로드하고 재배포가 일어납니다.
docker login -u ID -p PASSWORD
DOCKER_REPO=REGISTRY
if docker ps | grep -q blue; then
echo "Blue service is active."
CURRENT_SERVICE="blue"
NEW_SERVICE="green"
else
echo "Green service is active."
CURRENT_SERVICE="green"
NEW_SERVICE="blue"
fi
NGINX_UPSTREAM="${NEW_SERVICE}_backend"
sudo sed -i "s/proxy_pass http:\/\/.*_backend;/proxy_pass http:\/\/$NGINX_UPSTREAM;/" ./nginx/conf.d/nginx.conf
docker build -f dockerfile-nginx -t $DOCKER_REPO/Nginx 이미지 .
docker push $DOCKER_REPO/Nginx 이미지
sudo docker pull $DOCKER_REPO/서버 이미지
sudo docker pull $DOCKER_REPO/Nginx 이미지
sudo docker-compose up -d $NEW_SERVICE
if ! docker ps | grep -q nginx; then
sudo docker-compose up -d nginx
fi
sleep 60
NGINX_CONTAINER=$(docker ps | grep "nginx" | awk '{print $1}')
docker exec $NGINX_CONTAINER nginx -s reload
sudo docker-compose stop $CURRENT_SERVICE
docker image prune -f