[Jenkins] 무중단 배포를 위한 파이프라인 구성
무중단 배포 (Blue-Green 배포) 방법
들어가며
지난 포스트에서는 SSL 인증서 발급과 이를 Jenkins 파이프라인에 적용하는 방법을 다뤘습니다. 이번 포스트에서는 이 설정을 바탕으로 무중단 배포를 구현하는 방법을 공유합니다.
무중단 배포란?
무중단 배포는 기존 서비스를 중단하지 않고 새로운 버전을 배포하는 방법입니다. 이렇게 배포를 진행하면 서비스의 가용성을 높일 수 있으며, 사용자에게 끊김 없이 업데이트된 버전을 제공할 수 있습니다.
무중단 배포에는 여러 가지 방법이 있지만, 대표적으로 Blue-Green 배포, Rolling 배포, Canary 배포 방식이 있습니다.
1. Blue-Green 배포
Blue-Green 배포는 두 개의 환경(Blue와 Green)을 유지하여 서비스를 중단 없이 새로운 버전을 배포하는 방법입니다. 하나의 환경(예: Blue)을 운영 중에 두고, 새로운 버전은 다른 환경(예: Green)에서 준비됩니다. 이후 트래픽을 새로운 환경으로 전환하면서 배포를 진행합니다.
2. Rolling 배포
Rolling 배포는 전체 서버를 동시에 업데이트하지 않고, 한 서버씩 점진적으로 새로운 버전을 배포하는 방법입니다. 서비스의 일부만 업데이트되기 때문에 전체 서비스의 다운타임을 최소화할 수 있습니다.
3. Canary 배포
Canary 배포는 새로운 버전을 소수의 사용자에게만 먼저 배포하여 문제가 없는지 확인하는 방법입니다. 문제가 없으면 점차적으로 전체 서비스에 배포를 진행합니다.
이번 포스트에서는 Blue-Green 배포를 중심으로 설명하겠습니다.
배포 준비
백엔드 서버 준비
먼저, 백엔드 서버를 준비합니다. 여기서는 Django 프로젝트를 사용하고, Docker로 서버 환경을 구성할 것입니다. 각 환경(Blue, Green)을 Docker Compose로 설정합니다. 그리고 윈도우 환경에서 배포를 진행하였습니다.
docker-compose.yml
# docker-compose.yml
version: "3.8"
services:
ts-proxy:
image: nginx:1.22.1
container_name: test-proxy
restart: always
volumes:
- ./nginx:/etc/nginx/conf.d
- ./static:/usr/share/nginx/html/static
networks:
- test-network
ts-postgres:
image: postgres:13.10-alpine
container_name: test-db-dev
restart: always
environment:
- POSTGRES_PASSWORD=postgres
- DJANGO_DEBUG=False
networks:
- test-network
volumes:
- ts-db:/var/lib/postgresql/data
ts-redis:
image: redis:7.0-alpine
container_name: test-redis-dev
restart: always
networks:
- test-network
volumes:
ts-db:
networks:
test-network:
name: test-network
driver: bridge
위 파일은 Django 서버에서 사용하는 DB, Redis, Nginx 설정을 포함하고 있습니다. docker-compose.yml
파일은 공통으로 사용되며, Blue와 Green 배포 환경에서도 공유됩니다.
Blue/Green 배포 환경 설정
- Blue 환경 (docker-compose-blue.yml)
# docker-compose-blue.yml
version: "3.8"
services:
ts-django-blue:
build:
context: .
container_name: test-dev-blue
restart: always
env_file:
- .env
ports:
- 8000:8000
environment:
- PORTS=8000
- DJANGO_CONFIGURATION=production
command:
- /bin/sh
- -c
- |
dockerize -wait tcp://ts-postgres:5432 -timeout 20s
poetry run python manage.py makemigrations
poetry run python manage.py migrate
poetry run gunicorn -c gunicorn.conf.py -b 0.0.0.0:8000 app.core.asgi:application
networks:
- test-network
volumes:
- ./save:/workdir/save
networks:
test-network:
external: true
name: test-network
driver: bridge
- Green 환경 (docker-compose-green.yml)
# docker-compose-green.yml
version: "3.8"
services:
ts-django-green:
build:
context: .
container_name: test-dev-green
restart: always
ports:
- 8001:8001
env_file:
- .env
environment:
- PORTS=8001
- DJANGO_CONFIGURATION=production
command:
- /bin/sh
- -c
- |
dockerize -wait tcp://ts-postgres:5432 -timeout 20s
poetry run python manage.py makemigrations
poetry run python manage.py migrate
poetry run gunicorn -c gunicorn.conf.py -b 0.0.0.0:8001 app.core.asgi:application
networks:
- test-network
volumes:
- ./save:/workdir/save
networks:
test-network:
external: true
name: test-network
driver: bridge
Nginx 설정
- default.conf (공통 설정)
server {
listen 80;
server_name localhost;
include service-url.inc;
location /static/ {
alias /usr/share/nginx/html/static/;
}
location / {
proxy_pass http://app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
- service-url.inc (공통 설정)
upstream app {
server localhost:8000;
}
위 파일들은 공통적으로 사용되는 Nginx 설정 파일들로, 두 환경에서 동일하게 사용할 수 있습니다.
무중단 배포 Jenkins 파이프라인
이제 Jenkins 파이프라인을 작성하여 배포 자동화 과정을 설정합니다.
pipeline {
agent any
environment {
DOCKER_COMPOSE_VERSION = '3.8'
WORKSPACE = "${env.WORKSPACE}"
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Determine Deploy Target') {
steps {
script {
def blueContainerOutput = powershell(
script: '(docker ps -q -f name=test-dev-blue) -ne $null',
returnStdout: true
).trim()
echo "blueContainerOutput: ${blueContainerOutput}"
def blueContainerExists = blueContainerOutput.toLowerCase() == 'true'
echo "blueContainerExists: ${blueContainerExists}"
env.CURRENT_COLOR = blueContainerExists ? 'blue' : 'green'
env.DEPLOY_COLOR = blueContainerExists ? 'green' : 'blue'
env.CURRENT_PORT = blueContainerExists ? '8000' : '8001'
env.DEPLOY_PORT = blueContainerExists ? '8001' : '8000'
echo "Current running on ${env.CURRENT_COLOR} with port ${env.CURRENT_PORT}"
echo "Deploying to ${env.DEPLOY_COLOR} with port ${env.DEPLOY_PORT}"
}
}
}
stage('Deploy New Version') {
steps {
script {
// 새로운 버전 배포
bat "docker-compose -f docker-compose.yml -f docker-compose.${env.DEPLOY_COLOR}.yml up -d --build"
}
}
}
stage('Health Check') {
steps {
script {
def maxAttempts = 10
def attempts = 0
def healthy = false
while (!healthy && attempts < maxAttempts) {
attempts++
try {
def response = bat(
script: "curl -s http://localhost:${env.DEPLOY_PORT}/api/v1/test",
returnStdout: true
).trim()
if (response) {
healthy = true
echo "New version is healthy!"
}
} catch (Exception e) {
echo "Attempt ${attempts}/${maxAttempts} failed"
if (attempts < maxAttempts) {
sleep 10
}
}
}
if (!healthy) {
error "New version failed health check after ${maxAttempts} attempts"
}
}
}
}
stage('Switch Traffic') {
steps {
script {
// Nginx 설정 업데이트
bat """
echo upstream app { > ${WORKSPACE}/nginx/service-url.inc
echo server localhost:${env.DEPLOY_PORT}; >> ${WORKSPACE}/nginx/service-url.inc
echo } >> ${WORKSPACE}/nginx/service-url.inc
"""
// Nginx 재시작
bat 'docker exec test-proxy nginx -s reload'
// nginx-nginx-1 재시작
bat '''
(echo set $service_url http://[서버의 아이피]:%DEPLOY_PORT%;) > temp_service_url.inc
docker cp temp_service_url.inc nginx-nginx-1:/etc/nginx/conf.d/test-backend/service-url.inc
del temp_service_url.inc
'''
bat 'docker exec nginx-nginx-1 nginx -s reload'
}
}
}
stage('Cleanup Old Version') {
steps {
script {
if (env.CURRENT_COLOR != null) {
// 이전 버전 종료
bat "docker-compose -f docker-compose.${env.CURRENT_COLOR}.yml down"
}
}
}
}
}
post {
failure {
script {
// 배포 실패 시 로그 확인 및 롤백
bat "docker-compose -f docker-compose.yml -f docker-compose.${env.DEPLOY_COLOR}.yml logs"
bat "docker-compose -f docker-compose.yml -f docker-compose.${env.DEPLOY_COLOR}.yml down"
bat """
echo upstream app { > ${WORKSPACE}/nginx/service-url.inc
echo server localhost:${env.CURRENT_PORT}; >> ${WORKSPACE}/nginx/service-url.inc
echo } >> ${WORKSPACE}/nginx/service-url.inc
"""
bat "docker exec test-proxy nginx -s reload"
bat '''
(echo set $service_url http://[서버의 아이피]:%CURRENT_PORT%;) > temp_service_url.inc
docker cp temp_service_url.inc nginx-nginx-1:/etc/nginx/conf.d/test-backend/service-url.inc
del temp_service_url.inc
'''
bat "docker exec nginx-nginx-1 nginx -s reload"
}
}
}
}
파이프라인 설명
- Checkout: 소스 코드를 불러옵니다.
- Determine Deploy Target: 현재 실행 중인 환경(Blue/Green)을 확인하고, 배포할 환경을 설정합니다.
- Deploy New Version: 새로운 버전을 배포합니다.
- Health Check: 새로 배포한 서버가 정상인지 확인합니다.
- Switch Traffic: 트래픽을 새로운 환경으로 전환합니다.
- Cleanup Old Version: 이전 버전의 환경을 종료하여 자원을 정리합니다.
마치며
이번 포스트에서는 Blue-Green 배포를 통한 무중단 배포 방법을 설명했습니다. 이를 통해 서비스의 가용성을 높일 수 있으며, Jenkins 파이프라인을 활용한 배포 자동화도 가능해졌습니다.
Comments