서비스 운영 중 배포는 항상 조심스러운 작업입니다. 사용자에게 서비스 중단 없이 새로운 버전을 배포하는 것은 모든 개발팀의 목표이죠. 오늘은 Nginx의 upstream 기능을 활용해서 무중단 배포를 구현하는 방법을 알아보겠습니다! 💪

📋 목차
🤔 Graceful Deployment란?
Graceful Deployment는 서비스 중단 없이 새로운 버전을 배포하는 방식입니다. 기존 요청들은 정상적으로 처리를 완료하고, 새로운 요청들은 업데이트된 서버로 라우팅되도록 하는 것이 핵심이에요.
기본 아이디어 💡
- 서버를 down 상태로 변경 - 새로운 트래픽이 해당 서버로 가지 않도록 설정
- 기존 연결 완료 대기 - Keep-Alive 시간 + 여유시간만큼 대기
- 안전하게 배포 진행 - 모든 요청이 완료된 후 배포 수행
⚙️ Nginx Upstream 설정
먼저 기본적인 Nginx 설정부터 살펴보겠습니다.
nginx.conf 기본 설정
upstream app_servers {
server server1:8080;
server server2:8080;
server server3:8080;
# 헬스체크 설정
keepalive 32;
}
server {
listen 80;
server_name ${SERVER_NAME};
# Keep-Alive 설정
keepalive_timeout 65s;
keepalive_requests 1000;
location / {
proxy_pass http://app_servers;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 타임아웃 설정
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# 헬스체크 엔드포인트
location /health {
access_log off;
add_header Content-Type text/plain;
return 200 "healthy\n";
}
}
🔧 배포 스크립트 구현
이제 실제 배포를 수행하는 스크립트를 작성해보겠습니다.
deploy.sh - 메인 배포 스크립트
#!/bin/bash
set -e
# 환경변수 설정
NGINX_CONF_PATH="/etc/nginx/nginx.conf"
KEEP_ALIVE_TIME=${KEEP_ALIVE_TIME:-65}
GRACE_PERIOD=${GRACE_PERIOD:-10}
TOTAL_WAIT_TIME=$((KEEP_ALIVE_TIME + GRACE_PERIOD))
# 색상 정의
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 서버 down 설정 함수
mark_server_down() {
local server_name=$1
log_info "Marking server ${server_name} as down..."
sed -i "s/server ${server_name}:[0-9]*;/server ${server_name}:8080 down;/" $NGINX_CONF_PATH
# 설정 검증
if nginx -t; then
nginx -s reload
log_info "Server ${server_name} marked as down successfully"
else
log_error "Nginx configuration test failed"
exit 1
fi
}
# 서버 up 설정 함수
mark_server_up() {
local server_name=$1
log_info "Marking server ${server_name} as up..."
sed -i "s/server ${server_name}:[0-9]* down;/server ${server_name}:8080;/" $NGINX_CONF_PATH
if nginx -t; then
nginx -s reload
log_info "Server ${server_name} marked as up successfully"
else
log_error "Nginx configuration test failed"
exit 1
fi
}
# 연결 대기 함수
wait_for_connections() {
log_info "Waiting ${TOTAL_WAIT_TIME}s for existing connections to complete..."
for i in $(seq 1 $TOTAL_WAIT_TIME); do
echo -n "."
sleep 1
done
echo ""
log_info "Wait completed"
}
# 애플리케이션 배포 함수
deploy_application() {
local server_name=$1
log_info "Deploying application to ${server_name}..."
# 실제 배포 로직을 여기에 구현
# 예: Docker 컨테이너 재시작, 코드 업데이트 등
# 예제: Docker 컨테이너 재시작
docker-compose -f /path/to/docker-compose.yml pull app
docker-compose -f /path/to/docker-compose.yml up -d app
# 헬스체크 대기
wait_for_health_check $server_name
log_info "Application deployed successfully to ${server_name}"
}
# 헬스체크 함수
wait_for_health_check() {
local server_name=$1
local max_attempts=30
local attempt=1
log_info "Waiting for ${server_name} health check..."
while [ $attempt -le $max_attempts ]; do
if curl -f "http://${server_name}:8080/health" >/dev/null 2>&1; then
log_info "Health check passed for ${server_name}"
return 0
fi
log_warn "Health check attempt ${attempt}/${max_attempts} failed, retrying..."
sleep 2
attempt=$((attempt + 1))
done
log_error "Health check failed for ${server_name} after ${max_attempts} attempts"
exit 1
}
# 메인 배포 로직
main() {
local servers=("server1" "server2" "server3")
log_info "Starting graceful deployment process..."
for server in "${servers[@]}"; do
log_info "🚀 Processing server: ${server}"
# 1. 서버를 down으로 마킹
mark_server_down $server
# 2. 기존 연결 완료 대기
wait_for_connections
# 3. 애플리케이션 배포
deploy_application $server
# 4. 서버를 다시 up으로 마킹
mark_server_up $server
log_info "✅ Server ${server} deployment completed"
echo "----------------------------------------"
done
log_info "🎉 All servers deployed successfully!"
}
# 스크립트 실행
main "$@"
health-check.sh - 헬스체크 스크립트
#!/bin/bash
SERVER_URL=${1:-"http://localhost:8080"}
MAX_ATTEMPTS=${2:-10}
SLEEP_INTERVAL=${3:-2}
for i in $(seq 1 $MAX_ATTEMPTS); do
if curl -f "${SERVER_URL}/health" >/dev/null 2>&1; then
echo "✅ Health check passed"
exit 0
fi
echo "❌ Health check failed (attempt ${i}/${MAX_ATTEMPTS})"
sleep $SLEEP_INTERVAL
done
echo "🚨 Health check failed after ${MAX_ATTEMPTS} attempts"
exit 1
⏰ Keep-Alive 고려사항
Keep-Alive 설정은 graceful deployment의 핵심입니다. 클라이언트와 서버 간의 연결 지속 시간을 고려해야 해요.
주요 설정값들
# 클라이언트와의 Keep-Alive
keepalive_timeout 65s; # 클라이언트 연결 유지 시간
keepalive_requests 1000; # 연결당 최대 요청 수
# 업스트림과의 Keep-Alive
upstream app_servers {
server server1:8080;
server server2:8080;
server server3:8080;
keepalive 32; # 업스트림 연결 풀 크기
}
대기 시간 계산 📊
총 대기 시간 = Keep-Alive Timeout + Grace Period
= 65초 + 10초 = 75초
이렇게 여유 시간을 두는 이유는:
- 네트워크 지연으로 인한 요청 처리 시간
- 애플리케이션 내부 처리 시간
- 예상치 못한 상황에 대한 버퍼
🎯 실제 운영 시나리오
Docker Compose를 사용한 배포
# docker-compose.yml
version: '3.8'
services:
app:
image: ${APP_IMAGE:-myapp:latest}
ports:
- "8080:8080"
environment:
- NODE_ENV=${NODE_ENV:-production}
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
restart: unless-stopped
배포 자동화 예제
#!/bin/bash
# auto-deploy.sh
set -e
# 환경 변수 로드
source .env
# 새 이미지 빌드
docker build -t myapp:${BUILD_NUMBER} .
docker tag myapp:${BUILD_NUMBER} myapp:latest
# 배포 실행
./deploy.sh
# 이전 이미지 정리 (선택사항)
docker image prune -f
🔍 트러블슈팅
자주 발생하는 문제들
1. 헬스체크 실패 🚨
# 디버깅용 헬스체크
curl -v http://server1:8080/health
# 로그 확인
docker logs container_name
# 포트 확인
netstat -tlnp | grep 8080
2. Nginx 설정 오류 ⚠️
# 설정 파일 검증
nginx -t
# 설정 다시 로드
nginx -s reload
# 프로세스 확인
ps aux | grep nginx
3. 세션 유지 문제 🔄
만약 세션 기반 애플리케이션이라면:
upstream app_servers {
ip_hash; # 클라이언트 IP 기반 세션 유지
server server1:8080;
server server2:8080;
server server3:8080;
}
모니터링 및 로깅 📊
# 실시간 연결 상태 확인
watch -n 1 "netstat -an | grep :8080 | wc -l"
# Nginx 액세스 로그 모니터링
tail -f /var/log/nginx/access.log | grep -E "(200|500|502|503)"
# 시스템 리소스 확인
htop
🎁 추가 개선사항
1. 롤백 기능
# rollback.sh
#!/bin/bash
PREVIOUS_IMAGE=${1:-"myapp:previous"}
log_info "Rolling back to ${PREVIOUS_IMAGE}..."
for server in "${servers[@]}"; do
mark_server_down $server
# 이전 버전으로 롤백
docker tag $PREVIOUS_IMAGE myapp:latest
docker-compose up -d app
wait_for_health_check $server
mark_server_up $server
done
2. 배포 시간 측정 ⏱️
# 배포 시작 시간 기록
DEPLOY_START_TIME=$(date +%s)
# ... 배포 로직 ...
# 배포 완료 시간 계산
DEPLOY_END_TIME=$(date +%s)
DEPLOY_DURATION=$((DEPLOY_END_TIME - DEPLOY_START_TIME))
log_info "Total deployment time: ${DEPLOY_DURATION} seconds"
🏁 마무리
Nginx를 활용한 graceful deployment는 서비스의 안정성을 크게 향상시킬 수 있는 중요한 기법입니다. 핵심은 다음과 같아요:
- 점진적 트래픽 차단 - down 설정으로 새 요청 차단
- 충분한 대기 시간 - Keep-Alive + 여유 시간 확보
- 헬스체크 검증 - 배포 후 서비스 정상 동작 확인
- 모니터링 및 로깅 - 배포 과정 추적 및 문제 해결
이런 방식으로 구현하면 사용자는 서비스 중단을 전혀 느끼지 못하면서도 안전하게 새로운 버전을 배포할 수 있습니다! 🎉
더 궁금한 점이 있으시면 언제든 댓글로 남겨주세요. 함께 더 나은 배포 전략을 만들어가요! 💪
하지만 돈 짱 많아서 Nginx plus 쓰고 시푸다..
🏷️ Tags
#nginx #graceful-deployment #무중단배포 #devops #docker #load-balancer #upstream #keep-alive #health-check #automation #deployment-strategy #서버운영 #인프라 #배포자동화 #웹서버
'🛠️ 개발 도구 & 환경' 카테고리의 다른 글
| 안전한 웹사이트 개발을 위한 첫 단계 | Certificate Manager 🔐 (0) | 2026.03.21 |
|---|---|
| 🏦☁️ 핀테크 기업 혁신의 시작 | 금융 AI까지 한 번에 도입하세요 ✔️ (0) | 2026.03.21 |
| 공공에서도 보안 걱정 없이 활용 가능한 오픈소스 발키(Valkey) 기반 완전관리형 캐시 DB🔥 (0) | 2026.03.07 |
| ☁️ Apache Kafka 기반 서버리스 스트리밍 서비스, Data Stream이란? (2) | 2026.03.07 |
| 🔐 NCP에서 Let's Encrypt SSL 인증서 설정하기 (0) | 2025.06.01 |