저는 현재 혼자서 간단한 사이드 프로젝트를 개발하고 있습니다. 이때 배포 과정에 크게 관심을 안 가지고 있었지만, 간단한 사이드 프로젝트가 아니라 실제 세상에서 비즈니스로 운영되는 서비스는 잠깐의 장애도 굉장히 큰 문제가 될 수 있다는 생각이 들어 배포 시 서비스가 멈추지 않고 24시간 실행되는 구조를 만들어야겠다 생각했습니다.
무중단 배포(Zero Downtime Deployment)란?
말 그대로 다운타임(서비스가 일시적으로 중단되는 시간)을 없애는 배포 방식을 의미합니다. 서비스가 단 하나의 프로세스로 실행되고 있다면 해당 소스코드를 업데이트 하기 위해서는 소스코드 수정 후 해당 프로세스를 중단했다 다시 실행해야합니다. 그럼 해당 프로세스가 다시 실행되는데까지 잠깐이긴 하지만 서비스가 멈춰버리는 문제가 생깁니다.
무중단 배포 구현 방법들
무중단 배포에 흔하게 사용되는 방법들 3가지를 정리해보았습니다.
1. 블루 - 그린 배포 (Blue-Green Deployment)
블루는 기존 버전 환경을, 그린은 새로운 버전 환경을 의미합니다. 처음에는 블루 버전의 서비스가 실행되고 있을 것입니다. 업데이트된 서비스를 배포하기 위해서는 그린 버전의 독립적인 환경을 추가로 실행시킵니다. (현재 블루, 그린 서비스 2개가 따로 실행되고 있는 상황) 그린 버전이 성공적으로 실행되면 블루 버전으로 연결되던 트래픽을 모두 그린 버전으로 넘겨주어 사용자들이 온전히 새로운 버전의 서비스를 이용할 수 있도록 합니다.
2. 롤링 배포 (Rolling Deployment)
여러 개의 인스턴스로 운영되는 경우 순차적으로 하나씩 새로운 버전으로 업데이트하며 배포하는 방법입니다. 위에서 설명한 블루 그린 배포 방식과 차이점이라면 업데이트가 되는 순간? 이라고 할 수 있습니다. 롤링 배포는 실제 운영되는 서비스가 여러 인스턴스에서 실행 중이라면 인스턴스 하나 하나를 순차적으로 업데이트하기 때문에 업데이트가 되는 과정에서 어떤 사용자는 기존 서비스에 연결되고 어떤 사용자는 업데이트된 서비스에 연결됩니다. 하지만 블루 그린 배포는 모든 준비가 마무리되었을 때 트래픽을 업데이트된 인스턴스에 한 번에 넘겨주기 때문에 트래픽을 넘겨주기 전까지는 모든 사용자가 기존 서비스에 연결되고 트래픽을 넘겨준 후에는 모든 사용자가 업데이트된 서비스에 연결됩니다.
3. 카나리 배포 (Canary Deployment)
카나리 배포는 예전 광부들이 공기가 안전한지 확인하기 위해 '카나리아'라는 새를 광산에 데리고 들어갔던 일화에서 이름을 따왔습니다. 즉, 업데이트된 버전이 문제가 있을 수도 있으니 조금만 배포하고 소수의 사용자만 이용할 수 있도록하는 방식입니다. 그리고 문제가 없다는 것이 확인되면 점차 업데이트 버전을 늘려 결국 모든 서비스가 업데이트되도록 하는 방식입니다. 구조적으로 롤링 배포와 거의 동일합니다.
내가 선택한 방식: 롤링 배포
저는 '롤링 배포'를 사용해 무중단 배포를 구현하기로 결정했습니다. 우선 실제 사용자가 아직 없었기 때문에 굳이 카나리 배포같이 점진적인 배포를 사용할 이유가 없었습니다. 또한 현재 저는 사이드 프로젝트로 개발하는 과정이었기 때문에 프리티어로 EC2를 이용하고 있는데요. 이때 블루 그린 배포를 위해서는 기본적으로 기존 버전과 업데이트된 버전이 모두 실행되고 있는 순간이 필요한데 제가 사용중인 값 싼 EC2가 감당하지 못할 것 같았습니다. 그래서 최종적으로 롤링 배포를 시도하게 되었습니다.
또한 현재 제가 사용중인 EC2는 t2.micro로 굉장히 열약한 환경이기 때문에 쿠버네티스를 활용하기 어렵다고 판단했습니다. 그래서 직접적으로 컨테이너를 순차적으로 종료 및 실행하도록 설정했고, 트래픽은 nginx upstream을 통해 분배했습니다.
간단한 예제
nginx 설정은 다음과 같습니다.
upstream api {
server localhost:3000;
server localhost:3001;
}
server {
listen 443 ssl;
server_name todayz.org;
location /api/ {
proxy_pass http://api/;
... 생략
}
}
즉, URL 경로가 /api 로 시작하는 요청은 서버 내의 3000번 포트와 3001번 포트로 라우팅해주는 설정입니다. 만약 3000번 포트로 연결해줬는데 응답을 하지 않는다면 알아서 3001번 포트로 다시 요청을 보냅니다. 이를 통해 업데이트 과정 중 컨테이너가 종료되어도 문제가 되지 않을 수 있습니다.
도커 컨테이너는 다음과 같이 실행되고 있습니다.
$ docker ps -a
CONTAINER ID PORTS NAMES
86e7fbd57ecf 0.0.0.0:3001->3001/tcp, :::3001->3001/tcp api-2 api-2
76da26b23177 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp api-1
github actions yml 파일에서는 다음과 같은 코드를 통해 순차적으로 컨테이너가 종료되고 업데이트 후 실행되도록 설정했습니다.
script: |
docker-compose pull backend-main
docker-compose up -d api-1 --build
while ! curl -s http://localhost:3000/health; do
echo "Waiting for API-1 to be ready..."
sleep 5
done
docker-compose up -d api-2 --build
'데브옵스 > CI,CD' 카테고리의 다른 글
[Github] Git 기본 용어 및 명령어 모음 (0) | 2025.02.18 |
---|---|
[Github] Github 사용방법 - 커밋 충돌 (0) | 2025.01.23 |
[Github] Github 사용방법 - 브랜치 전략 (0) | 2025.01.22 |
[Github Actions] 코드 수정,테스트,배포 자동화하는 방법 (0) | 2025.01.18 |
[GitHub] private 레퍼지토리 clone 하기 (0) | 2024.12.31 |