이미지 로딩 중...
AI Generated
2025. 11. 8. · 4 Views
Next.js 실전 운영 10편 블루-그린 배포와 카나리 배포 전략
무중단 배포를 위한 블루-그린 배포와 카나리 배포 전략을 실무 관점에서 완벽하게 이해하고, Next.js 환경에서 직접 구현할 수 있는 방법을 단계별로 배워봅니다.
목차
- 블루-그린_배포_기본_개념
- Docker_기반_블루-그린_구현
- Nginx_라우팅_전환
- 카나리_배포_전략
- 카나리_배포_구현
- 헬스체크_시스템
- 자동_롤백_스크립트
- 배포_모니터링_설정
- CI/CD_파이프라인_통합
- 프로덕션_배포_체크리스트
- 근본 원인 분석 시작
1. 블루-그린_배포_기본_개념
시작하며
여러분이 프로덕션 환경에 새로운 기능을 배포하는 순간, 사용자들이 갑자기 에러 화면을 보게 되는 상황을 겪어본 적 있나요? 배포 중에는 서버가 재시작되고, 그 짧은 시간 동안 사용자들은 서비스를 이용할 수 없게 됩니다.
이런 다운타임은 단순히 불편함을 넘어서 비즈니스에 직접적인 손실을 가져옵니다. 특히 실시간 거래, 예약 시스템, 결제 서비스처럼 중단이 허용되지 않는 서비스에서는 치명적입니다.
바로 이럴 때 필요한 것이 블루-그린 배포입니다. 두 개의 동일한 프로덕션 환경을 유지하면서, 트래픽을 순간적으로 전환하여 완벽한 무중단 배포를 실현할 수 있습니다.
개요
간단히 말해서, 블루-그린 배포는 두 개의 독립적인 프로덕션 환경을 운영하면서 트래픽을 전환하는 배포 전략입니다. 하나는 현재 운영 중인 환경(블루), 다른 하나는 새 버전을 배포할 환경(그린)입니다.
왜 이 방식이 필요할까요? 전통적인 배포 방식에서는 서버를 내리고 새 버전을 올리는 동안 필연적으로 다운타임이 발생합니다.
하지만 블루-그린 배포를 사용하면 새 버전을 미리 준비한 후, 로드밸런서나 리버스 프록시에서 트래픽만 전환하면 됩니다. 예를 들어, 대형 이커머스 사이트에서 블랙프라이데이 세일 직전에 새 기능을 배포해야 할 때 매우 유용합니다.
기존에는 배포를 위해 새벽 시간대를 활용하거나 점검 시간을 공지했다면, 이제는 언제든지 자유롭게 배포할 수 있습니다. 이 방식의 핵심 특징은 첫째, 즉각적인 롤백이 가능하다는 점입니다.
문제가 발생하면 트래픽을 다시 이전 환경으로 돌리기만 하면 됩니다. 둘째, 새 버전을 충분히 테스트한 후에 전환할 수 있어 안정성이 높습니다.
셋째, 배포 과정이 단순하고 예측 가능합니다. 이러한 특징들이 대규모 서비스에서 배포의 신뢰성과 안정성을 크게 향상시킵니다.
코드 예제
// docker-compose.blue-green.yml
version: '3.8'
services:
# 블루 환경 (현재 운영 중)
nextjs-blue:
build:
context: .
dockerfile: Dockerfile
container_name: nextjs-blue
environment:
- NODE_ENV=production
- ENV_COLOR=blue
ports:
- "3000:3000"
restart: unless-stopped
# 그린 환경 (새 버전 배포)
nextjs-green:
build:
context: .
dockerfile: Dockerfile
container_name: nextjs-green
environment:
- NODE_ENV=production
- ENV_COLOR=green
ports:
- "3001:3000"
restart: unless-stopped
# Nginx 로드밸런서
nginx:
image: nginx:alpine
container_name: nginx-lb
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- nextjs-blue
- nextjs-green
설명
이것이 하는 일: 이 Docker Compose 설정은 Next.js 애플리케이션을 위한 블루-그린 배포 환경을 구축합니다. 두 개의 독립적인 컨테이너와 하나의 Nginx 로드밸런서로 구성됩니다.
첫 번째로, nextjs-blue 서비스는 현재 운영 중인 환경을 나타냅니다. 포트 3000번에서 실행되며, ENV_COLOR 환경 변수를 통해 자신이 블루 환경임을 식별합니다.
restart: unless-stopped 설정으로 컨테이너가 예기치 않게 종료되더라도 자동으로 재시작됩니다. 이렇게 하는 이유는 운영 환경의 안정성을 보장하기 위함입니다.
그 다음으로, nextjs-green 서비스가 실행되면서 새 버전이 배포될 준비를 합니다. 동일한 Dockerfile을 사용하지만 포트 3001번에서 독립적으로 실행됩니다.
여기서 중요한 점은 두 환경이 완전히 격리되어 있어서 그린 환경에서 문제가 발생해도 블루 환경에는 전혀 영향을 주지 않는다는 것입니다. 마지막으로, Nginx 컨테이너가 80번 포트를 통해 외부 트래픽을 받아서 블루 또는 그린 환경으로 라우팅합니다.
depends_on 설정으로 Next.js 컨테이너들이 먼저 시작된 후에 Nginx가 시작되도록 보장합니다. 여러분이 이 설정을 사용하면 새 버전 배포 시 그린 환경에 먼저 배포하고, 헬스체크와 테스트를 완료한 후, Nginx 설정만 변경하여 트래픽을 전환할 수 있습니다.
이를 통해 제로 다운타임 배포가 가능해지고, 문제 발생 시 즉시 이전 환경으로 롤백할 수 있습니다. 또한 두 환경을 동시에 운영하므로 A/B 테스트나 카나리 배포로 확장하기도 쉽습니다.
실전 팁
💡 환경 변수로 ENV_COLOR를 설정하면 로그나 모니터링에서 어느 환경에서 발생한 이벤트인지 쉽게 구분할 수 있습니다
💡 포트를 다르게 설정할 때는 방화벽 규칙도 함께 확인하세요. 내부 포트는 외부에 노출되지 않도록 보안 그룹을 설정해야 합니다
💡 depends_on은 시작 순서만 보장하고 애플리케이션이 준비되었는지는 확인하지 않으므로, Nginx에서 헬스체크를 추가로 구현하는 것이 좋습니다
💡 리소스를 절약하려면 배포 시에만 그린 환경을 시작하고, 전환 후에는 블루 환경을 종료하는 방식을 고려하세요
💡 데이터베이스 마이그레이션이 필요한 경우, 하위 호환성을 유지하면서 단계적으로 마이그레이션해야 두 환경이 동시에 작동할 수 있습니다
2. Docker_기반_블루-그린_구현
시작하며
여러분의 Next.js 애플리케이션을 배포할 때, 로컬에서는 잘 작동하던 것이 프로덕션에서 갑자기 오류가 나는 경험을 해보셨나요? 노드 버전이 다르거나, 환경 변수가 누락되거나, 의존성 버전이 맞지 않는 등의 문제가 발생합니다.
이런 환경 불일치 문제는 팀원 간, 개발 환경과 프로덕션 환경 간에 지속적으로 발생합니다. 특히 블루-그린 배포에서는 두 환경이 정확히 동일해야 하는데, 수동으로 이를 보장하기는 매우 어렵습니다.
바로 이럴 때 필요한 것이 Docker입니다. 컨테이너 기술을 활용하면 애플리케이션과 모든 의존성을 하나의 이미지로 패키징하여, 어디서든 동일하게 실행할 수 있습니다.
개요
간단히 말해서, Docker는 애플리케이션을 컨테이너라는 격리된 환경에서 실행할 수 있게 해주는 플랫폼입니다. Dockerfile에 빌드 과정을 정의하면, 동일한 이미지를 어디서든 재현 가능하게 만들 수 있습니다.
왜 블루-그린 배포에서 Docker가 중요할까요? 블루와 그린 환경이 정확히 동일한 런타임, 의존성, 설정을 가져야만 배포가 안전합니다.
Docker 이미지를 사용하면 이 동일성이 자동으로 보장됩니다. 예를 들어, 새 버전을 그린 환경에 배포할 때, 동일한 이미지에서 시작하므로 예상치 못한 차이가 발생하지 않습니다.
기존에는 각 서버에 직접 접속해서 코드를 pull하고 의존성을 설치하고 빌드했다면, 이제는 이미지를 빌드하고 컨테이너를 실행하기만 하면 됩니다. 핵심 특징으로는 첫째, 멀티 스테이지 빌드를 통해 이미지 크기를 최소화할 수 있습니다.
둘째, 레이어 캐싱을 활용하여 빌드 속도를 대폭 개선할 수 있습니다. 셋째, 환경 변수를 통해 동일한 이미지로 다양한 설정을 적용할 수 있습니다.
이러한 특징들이 빠르고 안정적인 배포 파이프라인을 구축하는 데 필수적입니다.
코드 예제
# Dockerfile - 멀티 스테이지 빌드
# Stage 1: 의존성 설치
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm && pnpm install --frozen-lockfile
# Stage 2: 빌드
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js 빌드 시 환경 변수 주입
ENV NEXT_TELEMETRY_DISABLED 1
RUN corepack enable pnpm && pnpm build
# Stage 3: 프로덕션 실행
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# 빌드 결과물만 복사
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
설명
이것이 하는 일: 이 Dockerfile은 Next.js 애플리케이션을 위한 최적화된 프로덕션 이미지를 생성합니다. 멀티 스테이지 빌드 방식으로 불필요한 파일을 제거하고 이미지 크기를 최소화합니다.
첫 번째 단계(deps)에서는 의존성만 설치합니다. node:20-alpine 이미지를 베이스로 사용하여 경량화된 환경을 구축하고, libc6-compat를 추가로 설치하여 호환성을 보장합니다.
pnpm install --frozen-lockfile을 사용하여 lock 파일과 정확히 일치하는 버전을 설치합니다. 이렇게 의존성을 별도 스테이지로 분리하는 이유는 Docker의 레이어 캐싱을 활용하기 위함입니다.
package.json이 변경되지 않으면 이 스테이지는 캐시에서 가져와서 빌드 속도가 크게 향상됩니다. 그 다음으로, builder 스테이지에서 실제 애플리케이션 빌드가 실행됩니다.
deps 스테이지에서 설치한 node_modules를 COPY --from=deps로 가져오고, 소스 코드를 복사한 후 pnpm build를 실행합니다. NEXT_TELEMETRY_DISABLED 환경 변수로 Next.js 텔레메트리를 비활성화하여 개인정보를 보호합니다.
빌드 결과물은 .next 디렉토리에 생성되며, standalone 모드를 사용하면 자체 실행 가능한 서버가 만들어집니다. 마지막으로, runner 스테이지에서 최소한의 파일만 포함한 실행 이미지를 만듭니다.
보안을 위해 nodejs 그룹과 nextjs 사용자를 생성하고, 루트 권한이 아닌 일반 사용자로 애플리케이션을 실행합니다. --chown 옵션으로 파일 소유권을 nextjs 사용자에게 할당하여 권한 문제를 방지합니다.
이 단계에서는 빌드 도구나 소스 코드 없이 실행에 필요한 파일만 포함되므로, 최종 이미지 크기가 개발 이미지보다 훨씬 작아집니다. 여러분이 이 Dockerfile을 사용하면 약 100-150MB의 경량 이미지를 생성할 수 있습니다.
이는 배포 속도를 크게 향상시키고, 컨테이너 시작 시간을 단축시킵니다. 또한 멀티 스테이지 빌드 덕분에 민감한 정보(소스 코드, 빌드 도구)가 프로덕션 이미지에 포함되지 않아 보안성도 향상됩니다.
동일한 이미지로 블루와 그린 환경을 구축하므로 환경 불일치 문제가 완전히 사라집니다.
실전 팁
💡 Next.js의 standalone 모드를 활성화하려면 next.config.js에서 output: 'standalone' 옵션을 설정해야 합니다
💡 .dockerignore 파일을 만들어서 node_modules, .next, .git 등을 제외하면 빌드 컨텍스트 크기가 줄어들어 이미지 빌드가 훨씬 빠릅니다
💡 alpine 이미지는 작지만 glibc가 아닌 musl을 사용하므로, 네이티브 바이너리를 사용하는 패키지는 호환성 문제가 있을 수 있습니다
💡 멀티 아키텍처 지원이 필요하면 docker buildx를 사용하여 AMD64와 ARM64 이미지를 동시에 빌드할 수 있습니다
💡 프로덕션에서는 latest 태그 대신 명확한 버전 태그(예: v1.2.3)를 사용하여 예상치 못한 버전 변경을 방지하세요
3. Nginx_라우팅_전환
시작하며
여러분이 블루와 그린 환경을 모두 준비했다면, 이제 가장 중요한 순간이 찾아옵니다. 바로 사용자 트래픽을 새 버전으로 전환하는 순간입니다.
이 과정에서 실수하면 사용자들이 에러를 경험하거나, 일부는 구 버전을, 일부는 신 버전을 보게 되는 혼란이 발생할 수 있습니다. 이런 트래픽 전환은 단순해 보이지만 실제로는 매우 중요한 작업입니다.
한 번의 잘못된 설정으로 전체 서비스가 다운될 수도 있고, 롤백이 불가능한 상황이 만들어질 수도 있습니다. 바로 이럴 때 필요한 것이 Nginx의 upstream 전환 메커니즘입니다.
설정 파일 하나만 변경하고 리로드하면, 진행 중인 연결은 유지하면서 새로운 요청만 다른 환경으로 라우팅할 수 있습니다.
개요
간단히 말해서, Nginx는 리버스 프록시로서 클라이언트 요청을 받아서 백엔드 서버로 전달하는 역할을 합니다. upstream 블록에서 백엔드 서버 목록을 정의하고, 간단한 심볼릭 링크 전환으로 트래픽을 옮길 수 있습니다.
왜 Nginx를 사용할까요? 첫째, graceful reload 기능으로 기존 연결을 끊지 않으면서 새 설정을 적용할 수 있습니다.
둘째, 헬스체크를 통해 비정상적인 백엔드를 자동으로 제외할 수 있습니다. 셋째, 로드밸런싱, 캐싱, SSL 종료 등 다양한 기능을 제공합니다.
예를 들어, 대규모 트래픽을 처리하는 서비스에서 블루-그린 전환을 하더라도 사용자는 전혀 눈치채지 못합니다. 기존에는 DNS를 변경하거나 로드밸런서 설정을 수동으로 바꿨다면, 이제는 스크립트 한 줄로 즉각적인 전환이 가능합니다.
핵심 특징으로는 첫째, 심볼릭 링크를 활용한 원자적(atomic) 전환입니다. 둘째, nginx -s reload로 다운타임 없는 설정 적용이 가능합니다.
셋째, fail_timeout과 max_fails로 자동 장애 감지를 설정할 수 있습니다. 이러한 특징들이 안전하고 신뢰할 수 있는 배포 프로세스를 만들어줍니다.
코드 예제
# nginx.conf - 블루-그린 전환 설정
events {
worker_connections 1024;
}
http {
# 업스트림 서버 풀 정의
upstream nextjs_backend {
# 현재 활성 환경 (심볼릭 링크로 전환)
include /etc/nginx/conf.d/active-env.conf;
}
server {
listen 80;
server_name yourdomain.com;
# 헬스체크 엔드포인트
location /health {
access_log off;
proxy_pass http://nextjs_backend/api/health;
proxy_set_header Host $host;
}
# 메인 애플리케이션
location / {
proxy_pass http://nextjs_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
# 타임아웃 설정
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
}
설명
이것이 하는 일: 이 Nginx 설정은 블루-그린 배포에서 트래픽을 안전하게 전환하는 메커니즘을 구현합니다. upstream 블록과 심볼릭 링크를 조합하여 원자적 전환을 가능하게 합니다.
첫 번째로, upstream nextjs_backend 블록은 백엔드 서버 풀을 정의합니다. 여기서 중요한 부분은 include 지시자입니다.
실제 서버 목록을 직접 작성하지 않고, 외부 파일(active-env.conf)을 포함시킵니다. 이 파일은 심볼릭 링크로, blue-env.conf 또는 green-env.conf를 가리킵니다.
예를 들어, active-env.conf → blue-env.conf일 때는 블루 환경으로 트래픽이 가고, 링크를 green-env.conf로 변경하면 그린 환경으로 전환됩니다. 이렇게 하는 이유는 설정 변경을 최소화하고, 전환 과정을 간단하고 안전하게 만들기 위함입니다.
그 다음으로, /health 엔드포인트가 실행됩니다. 이는 헬스체크 전용 경로로, access_log를 끄면 로그 파일이 불필요하게 커지는 것을 방지합니다.
proxy_pass로 백엔드의 /api/health로 요청을 전달하고, 응답 코드가 200이면 서버가 정상이라고 판단합니다. 배포 자동화 스크립트에서 이 엔드포인트를 호출하여 새 환경이 준비되었는지 확인할 수 있습니다.
마지막으로, 메인 애플리케이션 경로(/)가 처리됩니다. proxy_http_version 1.1과 Upgrade 헤더 설정은 WebSocket 연결을 지원하기 위함입니다.
Next.js의 Hot Module Replacement나 실시간 통신 기능을 사용할 때 필수적입니다. proxy_set_header Host는 백엔드에서 원래 호스트 이름을 알 수 있게 하고, proxy_cache_bypass는 업그레이드 요청 시 캐시를 우회합니다.
타임아웃 설정은 느린 API 요청이나 대용량 파일 업로드를 처리할 때 연결이 끊기지 않도록 보장합니다. 여러분이 이 설정을 사용하면 배포 시 다음과 같이 작동합니다: (1) 그린 환경에 새 버전 배포, (2) 헬스체크로 정상 작동 확인, (3) ln -sf green-env.conf active-env.conf로 심볼릭 링크 변경, (4) nginx -s reload로 설정 리로드.
이 과정에서 기존 연결은 유지되고, 새 요청만 그린으로 라우팅됩니다. 문제가 발생하면 즉시 블루로 링크를 되돌리고 reload하면 롤백이 완료됩니다.
실전 팁
💡 blue-env.conf와 green-env.conf 파일은 각각 "server nextjs-blue:3000 max_fails=3 fail_timeout=30s;" 형식으로 작성합니다
💡 nginx -t 명령어로 설정 파일의 문법을 검증한 후에 reload하면 잘못된 설정으로 인한 서비스 중단을 방지할 수 있습니다
💡 reload 대신 restart를 사용하면 모든 연결이 끊기므로, 반드시 -s reload 옵션을 사용하세요
💡 로그 파일에서 upstream 전환 시점을 추적하려면 access_log에 $upstream_addr 변수를 추가하여 어느 백엔드가 응답했는지 기록할 수 있습니다
💡 Nginx Plus(상용 버전)를 사용하면 동적 업스트림 설정과 고급 헬스체크 기능을 사용할 수 있지만, 오픈소스 버전으로도 충분히 구현 가능합니다
4. 카나리_배포_전략
시작하며
여러분이 블루-그린 배포로 새 버전을 출시했는데, 배포 후 몇 분 만에 사용자들로부터 대량의 에러 리포트가 들어온다고 상상해보세요. 전체 트래픽이 한 번에 새 버전으로 전환되었기 때문에, 문제가 발생하면 모든 사용자가 영향을 받습니다.
이런 전체 전환 방식은 작은 버그라도 치명적인 영향을 미칠 수 있습니다. 특히 실제 프로덕션 환경에서만 재현되는 문제들은 아무리 철저히 테스트해도 사전에 발견하기 어렵습니다.
바로 이럴 때 필요한 것이 카나리 배포입니다. 전체 트래픽 중 일부만 새 버전으로 보내고, 문제가 없으면 점진적으로 비율을 늘려가는 방식으로 리스크를 최소화할 수 있습니다.
개요
간단히 말해서, 카나리 배포는 새 버전을 소수의 사용자에게 먼저 공개하고, 모니터링 결과에 따라 점진적으로 확대하는 배포 전략입니다. 이름은 광부들이 유독 가스를 감지하기 위해 카나리아 새를 사용했던 것에서 유래했습니다.
왜 카나리 배포가 중요할까요? 블루-그린 배포는 빠른 전환과 롤백이 장점이지만, 문제를 발견하기 전까지는 모든 사용자가 영향을 받습니다.
반면 카나리 배포는 처음에 5-10%의 트래픽만 새 버전으로 보냅니다. 에러율, 응답 시간, 비즈니스 지표를 모니터링하면서 이상이 없으면 20%, 50%, 100%로 점진적으로 늘립니다.
예를 들어, 결제 시스템 업데이트 같은 중요한 변경사항을 배포할 때, 소수 사용자로 먼저 검증하면 대규모 장애를 예방할 수 있습니다. 기존의 블루-그린 배포가 "모 아니면 도" 방식이라면, 카나리 배포는 "단계적 검증" 방식입니다.
핵심 특징으로는 첫째, 점진적 배포로 리스크를 분산시킵니다. 둘째, 실시간 모니터링 데이터를 기반으로 배포 속도를 조절할 수 있습니다.
셋째, 자동화된 지표 분석으로 문제를 조기에 발견할 수 있습니다. 넷째, A/B 테스트와 결합하여 비즈니스 가치를 측정할 수 있습니다.
이러한 특징들이 대규모 서비스에서 안전한 배포를 가능하게 합니다.
코드 예제
// deploy-canary.sh - 카나리 배포 자동화 스크립트
#!/bin/bash
# 카나리 배포 단계 정의
STAGES=(5 10 25 50 100)
CANARY_DURATION=300 # 각 단계 모니터링 시간 (초)
ERROR_THRESHOLD=5.0 # 에러율 임계값 (%)
deploy_canary_stage() {
local percentage=$1
echo "📊 카나리 배포: ${percentage}% 트래픽 전환 중..."
# Nginx 가중치 설정 변경
cat > /etc/nginx/conf.d/upstream.conf <<EOF
upstream nextjs_backend {
server nextjs-blue:3000 weight=$((100 - percentage));
server nextjs-green:3000 weight=${percentage};
}
EOF
# Nginx 리로드
nginx -t && nginx -s reload
echo "⏳ ${CANARY_DURATION}초간 모니터링 중..."
sleep $CANARY_DURATION
# 에러율 확인
error_rate=$(curl -s http://monitoring.local/api/error-rate)
if (( $(echo "$error_rate > $ERROR_THRESHOLD" | bc -l) )); then
echo "❌ 에러율 ${error_rate}%가 임계값 초과. 롤백합니다."
rollback
exit 1
fi
echo "✅ ${percentage}% 단계 성공 (에러율: ${error_rate}%)"
}
# 단계별 배포 실행
for stage in "${STAGES[@]}"; do
deploy_canary_stage $stage
done
echo "🎉 카나리 배포 완료!"
설명
이것이 하는 일: 이 스크립트는 카나리 배포의 전체 프로세스를 자동화합니다. 여러 단계를 거치면서 트래픽 비율을 조절하고, 각 단계마다 시스템 상태를 검증합니다.
첫 번째로, 배포 단계를 배열로 정의합니다. STAGES=(5 10 25 50 100)은 처음에 5%의 트래픽만 새 버전으로 보내고, 점차 늘려가는 계획입니다.
CANARY_DURATION은 각 단계에서 얼마나 오래 모니터링할지 결정합니다. 300초(5분)는 에러가 누적되어 통계적으로 유의미한 결과를 얻기에 충분한 시간입니다.
ERROR_THRESHOLD는 자동 롤백을 트리거하는 임계값으로, 에러율이 5%를 넘으면 배포를 중단합니다. 이렇게 명확한 기준을 설정하는 이유는 주관적 판단을 배제하고 객관적으로 배포 성공 여부를 결정하기 위함입니다.
그 다음으로, deploy_canary_stage 함수가 실행되면서 각 단계의 배포를 처리합니다. 이 함수는 Nginx의 upstream 설정 파일을 동적으로 생성합니다.
weight 파라미터를 사용하여 로드밸런싱 비율을 조절하는데, 예를 들어 25% 단계에서는 블루에 weight=75, 그린에 weight=25를 설정합니다. cat과 heredoc(<<EOF)을 사용하여 파일을 생성하고, nginx -t로 문법을 검증한 후 reload합니다.
그런 다음 CANARY_DURATION만큼 대기하면서 실제 트래픽이 새 버전을 통과하도록 합니다. 마지막으로, 모니터링 시스템에서 에러율을 수집하여 임계값과 비교합니다.
curl로 모니터링 API를 호출하고, bc 명령어로 부동소수점 비교를 수행합니다. 에러율이 임계값을 초과하면 즉시 롤백 함수를 호출하고 스크립트를 종료합니다.
에러율이 정상 범위면 다음 단계로 진행합니다. for 루프로 모든 단계를 순회하면서 이 과정을 반복하고, 최종적으로 100%에 도달하면 배포가 완료됩니다.
여러분이 이 스크립트를 사용하면 수동 개입 없이 안전한 배포가 가능합니다. 에러가 발생하면 자동으로 롤백되고, 정상이면 점진적으로 확대됩니다.
각 단계의 모니터링 시간과 에러 임계값을 조정하여 여러분의 서비스 특성에 맞게 커스터마이징할 수 있습니다. 또한 에러율 외에도 응답 시간, CPU 사용률, 비즈니스 지표 등 다양한 메트릭을 추가로 검증할 수 있습니다.
실전 팁
💡 첫 단계를 5%보다 더 작게(1-2%) 설정하면 더 안전하지만, 통계적 유의성을 확보하려면 충분한 샘플 수가 필요합니다
💡 비즈니스 핵심 시간대(예: 점심시간, 저녁시간)를 피해서 배포하면 문제 발생 시 영향을 최소화할 수 있습니다
💡 자동 롤백 외에도 슬랙이나 이메일로 알림을 보내면 팀원들이 상황을 즉시 파악할 수 있습니다
💡 단계별 모니터링 시간을 트래픽 양에 따라 동적으로 조절하면(예: 트래픽이 많으면 짧게, 적으면 길게) 더 효율적입니다
💡 데이터베이스 마이그레이션이 포함된 배포는 카나리 방식이 어려울 수 있으므로, 스키마 변경을 별도 단계로 분리하는 것이 좋습니다
5. 카나리_배포_구현
시작하며
여러분이 카나리 배포 전략을 이해했다면, 이제 실제로 Nginx에서 트래픽 비율을 제어하는 방법을 알아야 합니다. 이론적으로는 간단해 보이지만, 실제 구현에서는 세션 유지, 헬스체크, 동적 가중치 변경 등 고려할 사항이 많습니다.
특히 사용자 경험을 해치지 않으면서 트래픽을 분산시키는 것이 중요합니다. 같은 사용자가 요청마다 다른 버전을 보게 되면 혼란스러울 수 있고, 세션 불일치 문제가 발생할 수 있습니다.
바로 이럴 때 필요한 것이 Nginx의 가중치 기반 로드밸런싱과 IP 해시 알고리즘입니다. 이를 조합하면 점진적이면서도 일관성 있는 트래픽 분산이 가능합니다.
개요
간단히 말해서, Nginx의 upstream 모듈은 weight(가중치)와 hash 알고리즘을 통해 트래픽 분산 비율과 방식을 제어할 수 있습니다. weight는 각 서버가 받을 트래픽의 상대적 비율을 결정하고, ip_hash는 같은 IP에서 온 요청을 항상 같은 서버로 보냅니다.
왜 이런 조합이 필요할까요? weight만 사용하면 트래픽 비율은 조절할 수 있지만, 같은 사용자가 때로는 블루를, 때로는 그린을 보게 됩니다.
반대로 ip_hash만 사용하면 일관성은 보장되지만 비율 조절이 어렵습니다. 둘을 함께 사용하면 "전체 트래픽의 20%를 그린으로, 그리고 같은 사용자는 계속 같은 버전을 보도록" 설정할 수 있습니다.
예를 들어, SPA 애플리케이션에서 API 요청과 정적 파일 요청이 섞여 있을 때, 세션 유지는 필수적입니다. 기존의 단순한 라운드로빈 방식에서는 세션 불일치 문제가 발생했다면, 이제는 sticky session을 유지하면서도 점진적 배포가 가능합니다.
핵심 특징으로는 첫째, weight 값을 실시간으로 변경하여 배포 속도를 조절할 수 있습니다. 둘째, consistent hash를 사용하여 서버 추가/제거 시에도 최소한의 세션만 재배치됩니다.
셋째, backup 서버를 지정하여 장애 발생 시 자동 페일오버가 가능합니다. 이러한 특징들이 안정적이고 유연한 카나리 배포 시스템을 구축하게 해줍니다.
코드 예제
# nginx-canary.conf - 카나리 배포 upstream 설정
upstream nextjs_backend {
# IP 해시로 세션 고정 (같은 사용자는 같은 서버로)
ip_hash;
# 블루 환경 (기존 버전) - 가중치 80
server nextjs-blue:3000 weight=80 max_fails=3 fail_timeout=30s;
# 그린 환경 (새 버전) - 가중치 20 (20% 트래픽)
server nextjs-green:3000 weight=20 max_fails=3 fail_timeout=30s;
# 백업 서버 (모든 메인 서버 다운 시)
server nextjs-backup:3000 backup;
# 연결 유지 설정
keepalive 32;
keepalive_timeout 60s;
}
server {
listen 80;
# 카나리 헤더로 강제 라우팅 (테스트용)
location / {
set $backend nextjs_backend;
# X-Canary: green 헤더가 있으면 그린으로 강제 라우팅
if ($http_x_canary = "green") {
proxy_pass http://nextjs-green:3000;
break;
}
# X-Canary: blue 헤더가 있으면 블루로 강제 라우팅
if ($http_x_canary = "blue") {
proxy_pass http://nextjs-blue:3000;
break;
}
# 기본: upstream 로드밸런싱
proxy_pass http://$backend;
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;
}
}
설명
이것이 하는 일: 이 Nginx 설정은 세션 일관성을 유지하면서 카나리 배포를 구현합니다. 가중치 기반 로드밸런싱과 강제 라우팅 기능을 함께 제공합니다.
첫 번째로, upstream 블록에서 ip_hash 지시자가 세션 고정을 담당합니다. 이는 클라이언트 IP 주소를 해싱하여 항상 동일한 백엔드 서버로 라우팅합니다.
예를 들어, IP 192.168.1.100에서 온 첫 요청이 블루로 갔다면, 이후 모든 요청도 블루로 갑니다. weight=80과 weight=20 설정으로 전체적으로는 80:20 비율로 트래픽이 분산되지만, 개별 사용자 관점에서는 항상 같은 버전을 사용합니다.
max_fails=3과 fail_timeout=30s는 헬스체크 설정으로, 30초 이내에 3번 실패하면 해당 서버를 30초간 비활성화합니다. 이렇게 하는 이유는 비정상적인 서버로 트래픽을 보내지 않기 위함입니다.
그 다음으로, backup 서버가 정의됩니다. 이는 모든 주 서버(블루, 그린)가 다운되었을 때만 활성화되는 비상 서버입니다.
keepalive 설정은 백엔드와의 연결을 재사용하여 성능을 향상시킵니다. 32개의 연결을 풀에 유지하고, 60초 동안 재사용할 수 있습니다.
이는 특히 TLS 연결에서 핸드셰이크 오버헤드를 크게 줄여줍니다. 마지막으로, location 블록에서 조건부 라우팅이 구현됩니다.
이는 개발자가 특정 버전을 테스트할 수 있게 해주는 기능입니다. curl -H "X-Canary: green" https://yourdomain.com처럼 헤더를 추가하면, ip_hash를 무시하고 무조건 그린 환경으로 라우팅됩니다.
if 문에서 break를 사용하면 이후의 proxy_pass가 실행되지 않습니다. 헤더가 없는 일반 사용자 요청은 upstream 로드밸런싱을 따릅니다.
proxy_set_header로 원본 IP를 전달하여 백엔드에서도 실제 클라이언트 정보를 알 수 있게 합니다. 여러분이 이 설정을 사용하면 다음과 같은 시나리오가 가능합니다: (1) 일반 사용자 80%는 블루를, 20%는 그린을 일관성 있게 사용, (2) 개발팀은 X-Canary 헤더로 그린 버전을 집중 테스트, (3) 서버 장애 시 자동으로 정상 서버로만 트래픽 전송, (4) 백업 서버로 최소한의 서비스 유지.
weight 값만 변경하면(예: 50:50, 20:80, 0:100) 점진적으로 배포를 확대할 수 있습니다.
실전 팁
💡 ip_hash는 X-Forwarded-For 헤더가 아닌 $remote_addr을 사용하므로, 프록시 뒤에 있다면 hash $proxy_add_x_forwarded_for를 사용하세요
💡 세션 고정이 필요 없는 정적 파일이나 API는 별도 location 블록으로 분리하여 ip_hash 없이 순수한 가중치 기반 밸런싱을 사용하면 더 고른 분산이 가능합니다
💡 Nginx Plus에서는 sticky 지시자로 쿠키 기반 세션 유지를 사용할 수 있어, IP 변경에도 세션이 유지됩니다
💡 가중치 변경 후에는 기존 세션이 유지되므로, 실제 비율이 목표치에 도달하려면 세션 타임아웃만큼 시간이 걸린다는 점을 고려하세요
💡 모니터링 대시보드에서 각 upstream 서버의 요청 수를 추적하여 실제 트래픽 분산 비율을 확인할 수 있습니다
6. 헬스체크_시스템
시작하며
여러분이 새 버전을 배포했는데, 컨테이너는 정상적으로 시작되었지만 실제로는 데이터베이스 연결 실패로 모든 요청이 500 에러를 반환하는 상황을 상상해보세요. Nginx는 백엔드 서버가 "살아있다"고 판단하여 계속 트래픽을 보내고, 사용자들은 에러를 경험합니다.
이런 문제는 프로세스가 실행 중이라고 해서 서비스가 정상이라고 보장할 수 없기 때문에 발생합니다. 데이터베이스 연결, 외부 API, 캐시 서버 등 의존성 중 하나라도 문제가 있으면 애플리케이션은 정상 작동할 수 없습니다.
바로 이럴 때 필요한 것이 포괄적인 헬스체크 시스템입니다. 단순히 HTTP 200 응답을 확인하는 것이 아니라, 실제로 모든 의존성이 정상인지 검증하는 엔드포인트를 구축해야 합니다.
개요
간단히 말해서, 헬스체크는 애플리케이션이 실제로 요청을 처리할 준비가 되었는지 확인하는 엔드포인트입니다. /health나 /healthz 같은 경로로 제공되며, 200 OK 응답은 "모든 시스템 정상", 503 Service Unavailable은 "문제 있음"을 의미합니다.
왜 정교한 헬스체크가 필요할까요? 첫째, 배포 자동화에서 새 버전이 준비되었는지 확인할 수 있습니다.
둘째, 로드밸런서나 오케스트레이터(Kubernetes 등)가 비정상 인스턴스를 트래픽에서 제외할 수 있습니다. 셋째, 장애가 발생하면 자동으로 재시작하거나 알림을 보낼 수 있습니다.
예를 들어, 데이터베이스가 일시적으로 다운되었을 때, 헬스체크가 실패하면 Nginx가 자동으로 해당 서버를 제외하고 정상 서버로만 트래픽을 보냅니다. 기존에는 단순히 포트가 열려있는지만 확인했다면, 이제는 실제 비즈니스 로직이 작동하는지까지 검증할 수 있습니다.
핵심 특징으로는 첫째, liveness와 readiness를 구분하여 체크합니다. Liveness는 "프로세스가 살아있나?", readiness는 "트래픽을 받을 준비가 되었나?"를 확인합니다.
둘째, 의존성별로 체크 항목을 나누어 어느 부분에 문제가 있는지 파악할 수 있습니다. 셋째, 타임아웃과 재시도 로직으로 일시적 장애를 허용합니다.
이러한 특징들이 안정적인 무중단 배포와 자동 복구를 가능하게 합니다.
코드 예제
// app/api/health/route.ts - Next.js 헬스체크 API
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import { redis } from '@/lib/redis';
export async function GET() {
const checks = {
timestamp: new Date().toISOString(),
status: 'healthy',
checks: {} as Record<string, any>
};
try {
// 데이터베이스 연결 확인
const dbStart = Date.now();
await prisma.$queryRaw`SELECT 1`;
checks.checks.database = {
status: 'ok',
responseTime: Date.now() - dbStart
};
// Redis 연결 확인
const redisStart = Date.now();
await redis.ping();
checks.checks.redis = {
status: 'ok',
responseTime: Date.now() - redisStart
};
// 메모리 사용량 확인
const memUsage = process.memoryUsage();
checks.checks.memory = {
status: memUsage.heapUsed < memUsage.heapTotal * 0.9 ? 'ok' : 'warning',
heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024) + 'MB',
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024) + 'MB'
};
// 모든 체크 통과
return NextResponse.json(checks, { status: 200 });
} catch (error) {
checks.status = 'unhealthy';
checks.checks.error = {
message: error instanceof Error ? error.message : 'Unknown error'
};
return NextResponse.json(checks, { status: 503 });
}
}
설명
이것이 하는 일: 이 API 라우트는 Next.js 애플리케이션의 전반적인 건강 상태를 종합적으로 점검합니다. 여러 의존성을 개별적으로 확인하고, 결과를 구조화된 JSON으로 반환합니다.
첫 번째로, checks 객체를 초기화합니다. timestamp는 헬스체크가 실행된 시간을 기록하여, 오래된 캐시 응답이 아님을 보장합니다.
status 필드는 전체적인 건강 상태를 나타내며, checks 객체에는 각 의존성별 상세 정보가 담깁니다. 이렇게 구조화하는 이유는 모니터링 시스템에서 쉽게 파싱하고 분석할 수 있도록 하기 위함입니다.
그 다음으로, 데이터베이스 체크가 실행됩니다. prisma.$queryRawSELECT 1은 가장 가벼운 쿼리로, 실제로 데이터베이스에 연결하여 쿼리를 실행할 수 있는지 확인합니다.
Date.now()로 시작 시간을 기록하고, 쿼리 완료 후 응답 시간을 계산합니다. 응답 시간이 지나치게 길면(예: 1초 이상) 데이터베이스에 성능 문제가 있다는 신호일 수 있습니다.
Redis 체크도 동일한 방식으로 ping() 명령어로 연결 상태를 확인합니다. 이 두 체크가 실패하면 애플리케이션은 대부분의 기능을 수행할 수 없으므로, 503 상태를 반환하여 로드밸런서가 이 인스턴스를 제외하도록 합니다.
마지막으로, 메모리 사용량을 확인합니다. process.memoryUsage()로 현재 힙 메모리 사용량을 가져오고, heapUsed가 heapTotal의 90%를 초과하면 warning 상태로 표시합니다.
이는 메모리 누수나 과부하의 조기 경고 신호입니다. 메가바이트 단위로 변환하여 사람이 읽기 쉽게 만듭니다.
모든 체크를 통과하면 200 OK를 반환하고, 하나라도 실패하면 catch 블록에서 503 Service Unavailable을 반환합니다. 여러분이 이 헬스체크를 사용하면 배포 스크립트에서 while !
curl -f http://localhost:3000/api/health; do sleep 1; done 같은 방식으로 새 버전이 준비될 때까지 대기할 수 있습니다. Nginx에서는 이 엔드포인트를 주기적으로 호출하여 백엔드 상태를 모니터링합니다.
Kubernetes에서는 readinessProbe로 설정하여 파드가 준비되기 전까지는 서비스에 추가되지 않도록 합니다. 또한 모니터링 대시보드에서 각 의존성의 응답 시간을 추적하여 성능 저하를 조기에 발견할 수 있습니다.
실전 팁
💡 헬스체크는 자주 호출되므로 무거운 연산을 피하고, 결과를 5-10초간 캐싱하여 부하를 줄이는 것이 좋습니다
💡 /healthz (준비 상태)와 /livez (생존 상태)를 분리하면, Kubernetes 같은 오케스트레이터에서 더 정교한 제어가 가능합니다
💡 외부 API 호출은 타임아웃을 짧게(1-2초) 설정하여 헬스체크 자체가 느려지지 않도록 주의하세요
💡 프로덕션에서는 헬스체크 응답에 민감한 정보(버전, 내부 IP 등)를 포함하지 않도록 주의해야 합니다
💡 HTTP 상태 코드 외에도 X-Health-Status 같은 커스텀 헤더로 경고 수준을 전달하면, 로드밸런서가 더 세밀한 판단을 할 수 있습니다
7. 자동_롤백_스크립트
시작하며
여러분이 카나리 배포를 진행하는 중에 갑자기 에러율이 급증하는 상황을 발견했다고 상상해보세요. 수동으로 설정을 되돌리고 Nginx를 reload하고 확인하는 동안, 수백 명의 사용자가 에러를 경험합니다.
몇 분의 지연이 큰 피해로 이어집니다. 이런 긴급 상황에서는 사람의 판단과 수동 작업보다 자동화된 대응이 훨씬 빠르고 정확합니다.
특히 새벽 시간이나 주말에 문제가 발생하면 담당자가 즉시 대응하기 어려울 수 있습니다. 바로 이럴 때 필요한 것이 자동 롤백 시스템입니다.
사전에 정의된 임계값을 초과하면 즉시 이전 버전으로 되돌리고, 팀에 알림을 보내는 자동화된 안전장치입니다.
개요
간단히 말해서, 자동 롤백은 모니터링 지표가 임계값을 초과할 때 자동으로 이전 안정 버전으로 되돌리는 메커니즘입니다. 에러율, 응답 시간, 5xx 에러 수 등 다양한 지표를 실시간으로 추적하여 판단합니다.
왜 자동 롤백이 중요할까요? 첫째, 평균 복구 시간(MTTR)을 수분에서 수초로 단축시킵니다.
둘째, 인간의 실수나 지연을 제거하여 일관성 있는 대응을 보장합니다. 셋째, 담당자가 부재중이더라도 시스템이 스스로 안전을 유지합니다.
예를 들어, 금요일 저녁에 배포했는데 주말 동안 문제가 발생하더라도, 자동 롤백이 즉시 대응하여 월요일까지 장애가 지속되는 최악의 시나리오를 방지합니다. 기존에는 장애 감지 → 담당자 호출 → 상황 파악 → 수동 롤백의 긴 프로세스였다면, 이제는 감지와 동시에 자동 롤백이 실행됩니다.
핵심 특징으로는 첫째, 다층적 안전장치로 오탐을 방지합니다. 하나의 지표가 아닌 여러 지표를 종합 판단합니다.
둘째, 롤백 후 자동으로 검증하여 롤백 자체가 성공했는지 확인합니다. 셋째, 상세한 로그와 알림으로 사후 분석이 가능합니다.
이러한 특징들이 배포에 대한 자신감을 높이고, 대담한 실험을 가능하게 합니다.
코드 예제
// auto-rollback.sh - 자동 롤백 시스템
#!/bin/bash
# 설정
MONITORING_URL="http://monitoring.local/api/metrics"
CHECK_INTERVAL=30 # 30초마다 확인
CONSECUTIVE_FAILURES=3 # 연속 3회 실패 시 롤백
# 임계값 설정
ERROR_RATE_THRESHOLD=5.0
RESPONSE_TIME_THRESHOLD=2000 # ms
HTTP_5XX_THRESHOLD=10 # 분당 5xx 에러 수
failure_count=0
rollback() {
echo "🚨 자동 롤백 시작: $(date)"
# Nginx를 블루 환경으로 전환
ln -sf /etc/nginx/conf.d/blue-env.conf /etc/nginx/conf.d/active-env.conf
# Nginx 설정 검증 및 리로드
if nginx -t 2>/dev/null; then
nginx -s reload
echo "✅ 롤백 완료: 블루 환경으로 전환됨"
else
echo "❌ Nginx 설정 오류! 수동 개입 필요"
exit 1
fi
# 슬랙 알림
curl -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"🚨 자동 롤백 실행됨\\n에러율: ${error_rate}%\\n응답시간: ${response_time}ms\"}"
# 롤백 후 헬스체크
sleep 10
if curl -sf http://localhost/api/health > /dev/null; then
echo "✅ 롤백 후 헬스체크 통과"
exit 0
else
echo "❌ 롤백 후에도 헬스체크 실패!"
exit 1
fi
}
# 메인 모니터링 루프
while true; do
# 메트릭 수집
metrics=$(curl -s "$MONITORING_URL")
error_rate=$(echo "$metrics" | jq -r '.error_rate')
response_time=$(echo "$metrics" | jq -r '.avg_response_time')
http_5xx=$(echo "$metrics" | jq -r '.http_5xx_count')
# 임계값 체크
if (( $(echo "$error_rate > $ERROR_RATE_THRESHOLD" | bc -l) )) || \
(( $(echo "$response_time > $RESPONSE_TIME_THRESHOLD" | bc -l) )) || \
(( http_5xx > HTTP_5XX_THRESHOLD )); then
((failure_count++))
echo "⚠️ 임계값 초과 감지 ($failure_count/$CONSECUTIVE_FAILURES)"
echo " 에러율: ${error_rate}%, 응답시간: ${response_time}ms, 5xx: ${http_5xx}"
if (( failure_count >= CONSECUTIVE_FAILURES )); then
rollback
fi
else
failure_count=0 # 정상이면 카운터 리셋
fi
sleep $CHECK_INTERVAL
done
설명
이것이 하는 일: 이 스크립트는 무한 루프로 실행되면서 시스템 상태를 지속적으로 감시하고, 문제 발생 시 자동으로 안전한 상태로 복구합니다. 첫 번째로, 설정 변수들이 정의됩니다.
MONITORING_URL은 메트릭을 제공하는 API 엔드포인트입니다. 이는 Prometheus, Datadog, 자체 구축 모니터링 시스템 등 어떤 것이든 가능합니다.
CHECK_INTERVAL=30은 30초마다 상태를 확인한다는 의미로, 너무 짧으면 불필요한 부하가 생기고, 너무 길면 문제 감지가 늦어집니다. CONSECUTIVE_FAILURES=3은 오탐을 방지하기 위한 설정으로, 일시적인 스파이크가 아닌 지속적인 문제일 때만 롤백합니다.
각 임계값은 여러분의 서비스 SLA에 맞게 조정해야 합니다. 그 다음으로, rollback 함수가 실제 복구 작업을 수행합니다.
ln -sf 명령어로 심볼릭 링크를 블루 환경으로 변경하는데, -f 옵션으로 기존 링크를 덮어씁니다. 이 작업은 원자적(atomic)이므로, 중간에 끊기거나 불완전한 상태가 발생하지 않습니다.
nginx -t로 설정 파일 문법을 먼저 검증하여, 잘못된 설정으로 인해 Nginx가 완전히 다운되는 최악의 상황을 방지합니다. 2>/dev/null로 에러 메시지를 숨기고 종료 코드만 확인합니다.
슬랙 웹훅으로 팀에 즉시 알림을 보내서, 담당자들이 상황을 인지하고 사후 조치를 취할 수 있게 합니다. 마지막으로, 메인 루프에서 모니터링과 판단이 이루어집니다.
curl로 메트릭 API를 호출하고, jq로 JSON을 파싱하여 필요한 값을 추출합니다. bc -l로 부동소수점 비교를 수행하는데, bash의 기본 산술 연산은 정수만 지원하기 때문입니다.
|| (논리 OR) 연산자로 여러 조건 중 하나라도 만족하면 failure_count를 증가시킵니다. 연속 3회 실패 시 롤백을 실행하고, 그 사이에 정상 상태로 돌아오면 카운터를 리셋합니다.
이는 일시적인 네트워크 지연이나 버스트 트래픽으로 인한 오탐을 방지합니다. 여러분이 이 스크립트를 systemd 서비스나 supervisor로 백그라운드에서 실행하면, 24시간 자동 감시 시스템이 구축됩니다.
배포 후 자신있게 퇴근할 수 있고, 문제가 발생하더라도 시스템이 스스로 복구합니다. 롤백 로그를 분석하여 어떤 조건에서 문제가 발생했는지 파악하고, 다음 배포에 반영할 수 있습니다.
실전 팁
💡 CONSECUTIVE_FAILURES를 2-3회로 설정하면 1-2분 내에 롤백되지만, 5-6회로 설정하면 더 신중하게 판단하여 오탐이 줄어듭니다
💡 롤백 후 일정 시간(예: 30분) 동안은 재배포를 막는 쿨다운 로직을 추가하면, 같은 버전을 반복 배포하는 것을 방지할 수 있습니다
💡 메트릭 API가 다운되면 스크립트가 멈추므로, curl에 --max-time 5 같은 타임아웃을 설정하고 실패 시 기본값을 사용하세요
💡 롤백 이벤트를 데이터베이스나 로그 파일에 기록하여, 얼마나 자주 롤백이 발생하는지 추적하고 배포 프로세스를 개선하세요
💡 여러 지표를 가중 평균으로 점수화하여 하나의 "건강 점수"를 만들면, 복잡한 조건문 없이 간단하게 판단할 수 있습니다
8. 배포_모니터링_설정
시작하며
여러분이 배포를 완료하고 모든 것이 정상이라고 생각했는데, 며칠 후 사용자들의 불만이 쌓여서야 성능 저하를 발견한 경험이 있나요? 에러는 없지만 응답 시간이 2배로 늘어났거나, 특정 기능의 전환율이 떨어졌을 수 있습니다.
이런 미묘한 문제들은 단순한 에러 로그나 헬스체크로는 발견할 수 없습니다. 실제 사용자 경험과 비즈니스 지표를 실시간으로 추적해야만 조기에 발견할 수 있습니다.
바로 이럴 때 필요한 것이 포괄적인 배포 모니터링 시스템입니다. 기술 지표(응답 시간, 에러율)와 비즈니스 지표(전환율, 이탈률)를 함께 추적하여 배포의 성공 여부를 종합적으로 판단할 수 있습니다.
개요
간단히 말해서, 배포 모니터링은 새 버전이 배포된 후 시스템과 비즈니스에 미치는 영향을 측정하는 프로세스입니다. 메트릭 수집, 시각화, 알림, 이상 탐지가 통합된 시스템입니다.
왜 배포 전용 모니터링이 필요할까요? 일반적인 시스템 모니터링은 절대값을 추적하지만, 배포 모니터링은 변화를 추적합니다.
"현재 응답 시간이 500ms"보다 "배포 후 응답 시간이 200ms에서 500ms로 증가"가 더 중요한 정보입니다. 또한 블루와 그린 환경을 비교하여 새 버전의 성능을 상대적으로 평가할 수 있습니다.
예를 들어, A/B 테스트처럼 동일한 시간대에 두 버전의 전환율을 비교하면 객관적인 판단이 가능합니다. 기존에는 배포 후 수동으로 대시보드를 확인했다면, 이제는 이상 징후를 자동으로 감지하고 알림을 받을 수 있습니다.
핵심 특징으로는 첫째, 환경별 태그(blue/green)로 메트릭을 분리하여 버전별 성능을 비교할 수 있습니다. 둘째, 배포 이벤트를 타임라인에 표시하여 전후 비교가 쉽습니다.
셋째, 골든 시그널(지연, 트래픽, 에러, 포화도)을 중심으로 핵심 지표를 추적합니다. 이러한 특징들이 데이터 기반의 배포 결정을 가능하게 합니다.
코드 예제
// lib/monitoring.ts - 배포 모니터링 유틸리티
import { StatsD } from 'hot-shots';
const statsd = new StatsD({
host: process.env.STATSD_HOST || 'localhost',
port: 8125,
prefix: 'nextjs.',
globalTags: {
env: process.env.ENV_COLOR || 'unknown',
version: process.env.APP_VERSION || 'dev'
}
});
// 배포 관련 메트릭 추적
export class DeploymentMonitor {
// HTTP 요청 추적
static trackRequest(path: string, method: string, statusCode: number, duration: number) {
// 응답 시간 히스토그램
statsd.histogram('http.response_time', duration, {
path,
method,
status: String(statusCode)
});
// 요청 카운터
statsd.increment('http.requests', 1, {
path,
method,
status: String(statusCode)
});
// 에러 추적
if (statusCode >= 500) {
statsd.increment('http.errors.5xx', 1, { path });
} else if (statusCode >= 400) {
statsd.increment('http.errors.4xx', 1, { path });
}
}
// 비즈니스 이벤트 추적
static trackBusinessEvent(event: string, value?: number, tags?: Record<string, string>) {
statsd.increment(`business.${event}`, value || 1, tags);
}
// 배포 이벤트 마커
static markDeployment(version: string, environment: string) {
statsd.event({
title: 'Deployment',
text: `Deployed ${version} to ${environment}`,
tags: { version, environment }
});
}
// 리소스 사용량 추적
static trackResources() {
const usage = process.resourceUsage();
statsd.gauge('process.cpu_user', usage.userCPUTime);
statsd.gauge('process.cpu_system', usage.systemCPUTime);
const mem = process.memoryUsage();
statsd.gauge('process.memory.heap_used', mem.heapUsed);
statsd.gauge('process.memory.heap_total', mem.heapTotal);
statsd.gauge('process.memory.external', mem.external);
}
}
// Next.js 미들웨어에서 사용
export function monitoringMiddleware(req: Request, handler: Function) {
const startTime = Date.now();
return handler(req)
.then((response: Response) => {
const duration = Date.now() - startTime;
const url = new URL(req.url);
DeploymentMonitor.trackRequest(
url.pathname,
req.method,
response.status,
duration
);
return response;
});
}
설명
이것이 하는 일: 이 모니터링 유틸리티는 Next.js 애플리케이션의 모든 중요한 지표를 수집하고 StatsD 프로토콜로 메트릭 서버에 전송합니다. 환경별, 버전별 태깅으로 블루-그린 비교가 가능합니다.
첫 번째로, StatsD 클라이언트를 초기화합니다. hot-shots 라이브러리는 가볍고 성능이 좋아서 프로덕션 환경에 적합합니다.
globalTags로 env와 version을 설정하면 모든 메트릭에 자동으로 태그가 붙습니다. 예를 들어, ENV_COLOR=blue인 컨테이너에서 발생한 메트릭은 모두 env:blue 태그를 가지게 되어, Grafana 대시보드에서 "blue 환경의 응답 시간"과 "green 환경의 응답 시간"을 별도로 시각화할 수 있습니다.
prefix: 'nextjs.'는 다른 애플리케이션의 메트릭과 구분하기 위함입니다. 그 다음으로, DeploymentMonitor 클래스가 다양한 추적 메서드를 제공합니다.
trackRequest는 모든 HTTP 요청을 기록하는데, histogram으로 응답 시간 분포를 추적하고, increment로 요청 수를 카운팅합니다. 응답 시간을 histogram으로 보내는 이유는 평균뿐만 아니라 p50, p95, p99 같은 백분위수를 계산할 수 있기 때문입니다.
평균 응답 시간이 200ms라도 p99가 5초라면 일부 사용자는 매우 느린 경험을 하고 있다는 의미입니다. 5xx와 4xx 에러를 별도로 카운팅하여 서버 문제와 클라이언트 문제를 구분합니다.
이어서, trackBusinessEvent는 기술 지표가 아닌 비즈니스 지표를 추적합니다. 예를 들어, "회원가입 완료", "결제 성공", "장바구니 추가" 같은 이벤트를 기록하면 새 버전이 비즈니스에 미치는 영향을 측정할 수 있습니다.
만약 그린 환경의 결제 성공률이 블루보다 낮다면, 기술적으로는 정상이어도 비즈니스적으로는 문제가 있는 것입니다. markDeployment는 배포 시점을 이벤트로 기록하여, Grafana 대시보드에 수직선으로 표시되어 "이 시점 이후 응답 시간이 증가했다"는 시각적 상관관계를 파악할 수 있습니다.
마지막으로, monitoringMiddleware가 모든 요청을 자동으로 추적합니다. Next.js API 라우트나 미들웨어에 통합하면 수동으로 메트릭을 보낼 필요가 없습니다.
startTime을 기록하고, 응답이 완료되면 duration을 계산하여 trackRequest로 전송합니다. then 체인을 사용하여 응답을 가로채지 않고 투명하게 통과시킵니다.
여러분이 이 모니터링 시스템을 사용하면 Grafana 대시보드에서 다음과 같은 비교가 가능합니다: (1) 블루 vs 그린 응답 시간 비교 그래프, (2) 환경별 에러율 추이, (3) 버전별 비즈니스 전환율, (4) 배포 이벤트 마커와 메트릭 변화의 상관관계. 이를 통해 "새 버전이 10% 빠르다" 또는 "결제 성공률이 2% 하락했다" 같은 객관적 데이터로 배포를 평가할 수 있습니다.
실전 팁
💡 StatsD는 UDP를 사용하므로 메트릭 전송 실패가 애플리케이션에 영향을 주지 않지만, 샘플링(예: 10% 샘플링)을 사용하면 네트워크 부하를 크게 줄일 수 있습니다
💡 카디널리티가 높은 태그(예: userId)는 메트릭 저장소에 큰 부담을 주므로, 집계된 형태(예: userTier)로 변환하여 사용하세요
💡 Datadog, New Relic 같은 SaaS를 사용하면 즉시 사용 가능한 대시보드와 알림을 제공하지만, Prometheus + Grafana 조합도 오픈소스로 충분히 강력합니다
💡 배포 후 24-48시간은 집중 모니터링 기간으로 설정하여, 지연된 문제(메모리 누수, 누적 오류)도 발견할 수 있도록 하세요
💡 alert 룰을 설정할 때 "5분 동안 p95 응답 시간이 평소보다 50% 증가" 같은 상대적 기준을 사용하면 트래픽 변화에도 잘 작동합니다
9. CI/CD_파이프라인_통합
시작하며
여러분이 지금까지 배운 블루-그린 배포, 카나리 배포, 자동 롤백을 수동으로 실행한다면 어떨까요? 매번 명령어를 입력하고, 헬스체크를 확인하고, 모니터링 대시보드를 지켜보는 것은 시간이 오래 걸리고 실수하기 쉽습니다.
이런 반복적인 작업은 사람이 할수록 오류 가능성이 높아집니다. 특히 급하게 핫픽스를 배포해야 할 때, 수동 프로세스는 병목이 됩니다.
바로 이럴 때 필요한 것이 CI/CD 파이프라인입니다. 코드를 푸시하면 자동으로 빌드, 테스트, 배포, 모니터링, 롤백까지 모든 과정이 자동으로 실행됩니다.
개요
간단히 말해서, CI/CD는 Continuous Integration(지속적 통합)과 Continuous Deployment(지속적 배포)의 약자로, 코드 변경사항을 자동으로 빌드하고 테스트하고 배포하는 자동화된 파이프라인입니다. 왜 CI/CD가 필수적일까요?
첫째, 배포 프로세스를 표준화하여 누가 배포하든 동일한 절차를 따르게 합니다. 둘째, 사람의 개입을 최소화하여 휴먼 에러를 제거합니다.
셋째, 배포 빈도를 크게 높일 수 있습니다. 수동 배포는 하루에 1-2번이 한계지만, 자동화하면 하루에 수십 번도 가능합니다.
예를 들어, Netflix는 하루에 수천 번 배포하는데, 이는 완전히 자동화된 파이프라인 덕분입니다. 기존에는 "배포"가 위험하고 무거운 작업이었다면, CI/CD를 통해 배포가 일상적이고 안전한 작업이 됩니다.
핵심 특징으로는 첫째, Git 브랜치 전략(GitFlow, Trunk-based)과 통합하여 특정 브랜치 푸시 시 자동 배포됩니다. 둘째, 단계별 승인 프로세스로 프로덕션 배포는 수동 승인을 받을 수 있습니다.
셋째, 환경별 설정으로 dev, staging, production 각각 다른 설정을 적용합니다. 이러한 특징들이 빠르고 안전한 배포 문화를 만들어줍니다.
코드 예제
# .github/workflows/deploy-canary.yml
name: Canary Deployment
on:
push:
branches: [main]
workflow_dispatch: # 수동 실행 허용
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
steps:
- uses: actions/checkout@v4
- name: Docker 메타데이터 생성
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix={{branch}}-
type=ref,event=branch
- name: Docker 빌드 및 푸시
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy-canary:
needs: build
runs-on: ubuntu-latest
steps:
- name: 서버 SSH 접속 및 배포
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
script: |
# 그린 환경에 새 버전 배포
docker pull ${{ needs.build.outputs.image-tag }}
docker stop nextjs-green || true
docker rm nextjs-green || true
docker run -d \
--name nextjs-green \
-p 3001:3000 \
-e ENV_COLOR=green \
${{ needs.build.outputs.image-tag }}
# 헬스체크 대기
for i in {1..30}; do
if curl -f http://localhost:3001/api/health; then
echo "✅ 그린 환경 준비 완료"
break
fi
sleep 2
done
# 카나리 배포 실행
bash /opt/scripts/deploy-canary.sh
- name: 슬랙 알림
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: '배포 ${{ job.status }}: ${{ github.event.head_commit.message }}'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
설명
이것이 하는 일: 이 GitHub Actions 워크플로우는 코드 푸시부터 카나리 배포까지 전체 프로세스를 자동화합니다. 빌드와 배포를 분리하여 재사용성과 디버깅 용이성을 높입니다.
첫 번째로, 트리거 조건을 정의합니다. on: push: branches: [main]은 main 브랜치에 푸시될 때만 실행됩니다.
workflow_dispatch를 추가하면 GitHub UI에서 수동으로도 실행할 수 있어, 급한 핫픽스나 테스트 배포에 유용합니다. env 섹션에서 REGISTRY와 IMAGE_NAME을 정의하여 여러 스텝에서 재사용합니다.
ghcr.io는 GitHub Container Registry로, GitHub Actions와 통합이 잘 되어있고 무료입니다. 그 다음으로, build 잡이 Docker 이미지를 생성합니다.
outputs로 image-tag를 출력하면 후속 잡에서 이 값을 사용할 수 있습니다. docker/metadata-action은 Git SHA와 브랜치 이름을 조합하여 고유한 이미지 태그를 생성합니다.
예를 들어, main 브랜치의 커밋 abc1234는 main-abc1234 태그를 받습니다. cache-from과 cache-to로 GitHub Actions 캐시를 활용하면 변경된 레이어만 빌드하여 속도가 크게 향상됩니다.
첫 빌드는 10분 걸려도 이후는 1-2분이면 완료됩니다. 이어서, deploy-canary 잡이 실제 배포를 수행합니다.
needs: build로 빌드가 성공한 후에만 실행되도록 의존성을 설정합니다. appleboy/ssh-action으로 배포 서버에 SSH 접속하여 명령어를 실행합니다.
secrets에 저장된 인증 정보를 사용하므로 코드에 민감한 정보가 노출되지 않습니다. script 블록에서는 먼저 새 이미지를 pull하고, 기존 그린 컨테이너를 정리한 후(|| true로 존재하지 않아도 에러 무시), 새 컨테이너를 시작합니다.
for 루프로 최대 60초(30회 × 2초) 동안 헬스체크를 시도하고, 준비되면 카나리 배포 스크립트를 실행합니다. 마지막으로, 슬랙 알림 스텝이 배포 결과를 팀에 공유합니다.
if: always()로 성공/실패 여부와 관계없이 항상 실행되도록 합니다. status와 커밋 메시지를 포함하여 누가 무엇을 배포했는지 추적할 수 있습니다.
여러분이 이 파이프라인을 사용하면 git push origin main 명령어 하나로 모든 배포가 자동으로 진행됩니다. GitHub Actions 탭에서 실행 과정을 실시간으로 확인할 수 있고, 각 스텝의 로그를 볼 수 있습니다.
실패하면 어느 단계에서 실패했는지 명확히 알 수 있어 디버깅이 쉽습니다. 또한 여러 개발자가 동시에 배포를 시도하더라도 큐에서 순차적으로 처리되어 충돌을 방지합니다.
실전 팁
💡 environment를 사용하면 production 배포 시 수동 승인을 요구할 수 있어, 실수로 배포되는 것을 방지합니다
💡 matrix 전략으로 여러 환경(dev, staging, prod)에 병렬로 배포하면 시간을 절약할 수 있지만, 프로덕션은 단독으로 실행하는 것이 안전합니다
💡 secrets는 GitHub 저장소 Settings > Secrets에서 관리하고, 팀원들과 공유할 때는 1Password나 AWS Secrets Manager를 사용하세요
💡 빌드 시간을 줄이려면 pnpm을 npm 대신 사용하고, Docker 멀티 스테이지 빌드에서 의존성 설치 스테이지를 캐싱하세요
💡 배포 실패 시 자동으로 이슈를 생성하거나, 담당자를 mention하는 스텝을 추가하면 빠른 대응이 가능합니다
10. 프로덕션_배포_체크리스트
시작하며
여러분이 지금까지 배운 모든 기술과 도구를 갖추었다면, 이제 실제로 프로덕션에 배포할 준비가 되었습니다. 하지만 배포 직전에 "뭔가 빠뜨린 게 없을까?"라는 불안감을 느낀 적이 있나요?
이런 불안감은 정당합니다. 아무리 철저히 준비해도 작은 실수 하나가 큰 장애로 이어질 수 있습니다.
환경 변수 누락, 데이터베이스 마이그레이션 미실행, 캐시 무효화 실패 등이 흔한 원인입니다. 바로 이럴 때 필요한 것이 배포 체크리스트입니다.
항공기 조종사들이 이륙 전 체크리스트를 사용하듯이, 우리도 배포 전 모든 항목을 체계적으로 확인해야 합니다.
개요
간단히 말해서, 배포 체크리스트는 프로덕션 배포 전에 확인해야 할 모든 항목을 정리한 목록입니다. 기술적 준비사항, 비즈니스 고려사항, 롤백 계획 등을 포함합니다.
왜 체크리스트가 중요할까요? 연구에 따르면 체크리스트를 사용하면 복잡한 절차에서 실수가 30-50% 감소한다고 합니다.
특히 압박 상황에서는 경험 많은 엔지니어도 기본적인 것을 놓칠 수 있습니다. 체크리스트는 인지 부하를 줄이고, 팀원 간 커뮤니케이션을 명확하게 하며, 사후 분석에도 유용합니다.
예를 들어, 배포가 실패했을 때 체크리스트를 보면 어느 단계가 누락되었는지 쉽게 파악할 수 있습니다. 기존에는 경험에 의존하거나 매번 다른 절차를 따랐다면, 이제는 표준화되고 검증된 프로세스를 따를 수 있습니다.
핵심 특징으로는 첫째, 단계별로 구조화되어(배포 전, 배포 중, 배포 후) 언제 무엇을 해야 하는지 명확합니다. 둘째, 책임자를 명시하여 누가 확인해야 하는지 분명히 합니다.
셋째, 자동화 가능한 항목과 수동 확인이 필요한 항목을 구분합니다. 이러한 특징들이 안전하고 일관성 있는 배포를 보장합니다.
코드 예제
# deployment-checklist.md
## 프로덕션 배포 체크리스트
### 배포 전 (T-24시간)
- [ ] 릴리스 노트 작성 완료 (새 기능, 버그 수정, 알려진 이슈)
- [ ] 스테이징 환경에서 QA 테스트 통과
- [ ] 성능 테스트 완료 (부하 테스트, 스트레스 테스트)
- [ ] 보안 스캔 완료 (취약점 없음)
- [ ] 데이터베이스 마이그레이션 검토 및 백업 계획 수립
- [ ] 롤백 계획 문서화 (언제, 누가, 어떻게)
- [ ] 모니터링 대시보드 설정 확인
- [ ] 알림 채널 테스트 (슬랙, 이메일, PagerDuty)
### 배포 전 (T-1시간)
- [ ] 데이터베이스 백업 완료 확인
- [ ] 트래픽이 낮은 시간대인지 확인
- [ ] 배포 팀원 대기 상태 확인 (최소 2명)
- [ ] 고객 지원팀에 배포 알림
- [ ] 주요 고객에게 사전 공지 (필요 시)
- [ ] 기능 플래그 상태 확인
- [ ] CDN 캐시 무효화 계획 수립
### 배포 중
- [ ] 블루 환경 현재 상태 기록
- [ ] 그린 환경에 새 버전 배포
- [ ] 헬스체크 통과 확인 (DB, Redis, 외부 API)
- [ ] 스모크 테스트 실행 (핵심 기능 5-10개)
# 예시: 핵심 API 테스트
curl -f https://api.yourdomain.com/health
curl -f https://api.yourdomain.com/api/products
curl -f https://api.yourdomain.com/api/user/profile
- [ ] 카나리 배포 시작 (5% 트래픽)
- [ ] 초기 메트릭 확인 (5분 모니터링)
- 에러율 < 1%
- 응답 시간 < 500ms
- 5xx 에러 = 0
### 배포 중 (점진적 확대)
- [ ] 10% 트래픽 전환 → 10분 모니터링
- [ ] 25% 트래픽 전환 → 15분 모니터링
- [ ] 50% 트래픽 전환 → 20분 모니터링
- [ ] 100% 트래픽 전환
각 단계마다 확인:
- [ ] 에러율이 기준선 대비 증가하지 않음
- [ ] 응답 시간이 기준선 대비 증가하지 않음
- [ ] 비즈니스 지표 정상 (전환율, 이탈률)
- [ ] 사용자 피드백 채널 확인 (고객 지원 티켓)
### 배포 후 (T+1시간)
- [ ] 전체 트래픽 100% 전환 완료
- [ ] 블루 환경 대기 상태 유지 (즉시 롤백 가능)
- [ ] 모든 헬스체크 그린 상태
- [ ] 로그에 비정상적인 에러 없음
- [ ] 성능 메트릭 정상 범위
- [ ] CDN 캐시 무효화 완료
- [ ] 내부 팀에 배포 완료 공지
### 배포 후 (T+24시간)
- [ ] 24시간 모니터링 데이터 리뷰
- [ ] 메모리 누수 징후 없음 (힙 메모리 추세 확인)
- [ ] 데이터베이스 연결 풀 안정적
- [ ] 사용자 피드백 수집 및 분석
- [ ] 블루 환경 종료 (또는 다음 배포까지 대기)
- [ ] 배포 회고 미팅 일정 잡기
### 롤백이 필요한 경우
- 에러율 > 5%
- 5xx 에러 > 분당 10개
- 응답 시간 > 기준선의 2배
- 치명적 기능 장애 발견
- 보안 취약점 발견
**롤백 절차:**
6. 근본 원인 분석 시작
설명
이것이 하는 일: 이 체크리스트는 프로덕션 배포의 전체 생애주기를 커버하며, 각 단계에서 확인해야 할 항목과 기준을 명확히 제시합니다. 첫 번째로, 배포 전 단계가 두 개의 타임라인으로 나뉩니다.
T-24시간은 하루 전에 미리 준비해야 할 큰 항목들입니다. 릴리스 노트는 사용자와 지원팀에게 무엇이 바뀌는지 알려주고, QA 테스트는 기능적 정확성을 보장합니다.
성능 테스트는 특히 중요한데, 프로덕션 트래픽을 감당할 수 있는지 미리 검증합니다. 데이터베이스 마이그레이션이 포함된 경우, 다운타임 없이 실행 가능한지(하위 호환성), 롤백 가능한지 반드시 확인해야 합니다.
T-1시간은 배포 직전의 최종 점검으로, 실시간 상태를 확인합니다. 트래픽이 낮은 시간대를 선택하는 이유는 문제 발생 시 영향 받는 사용자 수를 최소화하기 위함입니다.
그 다음으로, 배포 중 단계가 점진적 확대 프로세스를 안내합니다. 각 트래픽 비율마다 모니터링 시간이 지정되어 있는데, 비율이 높아질수록 더 오래 관찰합니다.
이는 문제가 누적되어 나타날 수 있기 때문입니다. 5%에서는 괜찮았던 것이 50%에서 데이터베이스 연결 풀 고갈로 이어질 수 있습니다.
스모크 테스트는 자동화된 E2E 테스트보다 빠르고 간단하지만, 핵심 기능이 작동하는지 확인합니다. curl 예시처럼 주요 API 엔드포인트를 직접 호출하여 즉시 결과를 확인할 수 있습니다.
이어서, 배포 후 단계가 단기(1시간)와 장기(24시간) 모니터링으로 나뉩니다. 1시간 내에 대부분의 즉각적인 문제가 드러나지만, 메모리 누수나 캐시 관련 문제는 시간이 지나야 발견됩니다.
블루 환경을 24시간 유지하는 이유는 지연된 문제에도 빠르게 롤백하기 위함입니다. 배포 회고는 팀의 학습과 프로세스 개선을 위해 필수적입니다.
"이번 배포에서 무엇이 잘 되었고, 무엇을 개선할 수 있을까?"를 논의합니다. 마지막으로, 롤백 기준과 절차가 명확히 정의됩니다.
모호한 "문제가 있으면"이 아니라, 구체적인 수치(에러율 5%, 분당 10개 5xx)를 제시합니다. 이는 긴급 상황에서 빠른 의사결정을 가능하게 합니다.
pre-deployment-checks.sh 스크립트는 사람이 실수로 놓칠 수 있는 항목을 자동으로 검증합니다. CI/CD 파이프라인의 첫 단계로 실행하여, 준비가 안 된 상태에서 배포가 시작되는 것을 방지합니다.
여러분이 이 체크리스트를 사용하면 배포가 훨씬 덜 스트레스 받고 예측 가능해집니다. 각 항목을 체크하면서 진행하므로 "뭔가 빠뜨린 것 같은" 불안감이 사라집니다.
팀원들과 체크리스트를 공유하면 누가 배포를 담당하든 동일한 수준의 품질을 유지할 수 있습니다. 또한 배포 실패 시 체크리스트를 리뷰하여 어느 단계가 문제였는지 학습하고 개선할 수 있습니다.
실전 팁
💡 체크리스트를 GitHub 이슈 템플릿으로 만들면 각 배포마다 이슈를 생성하여 진행 상황을 추적하고, 나중에 배포 히스토리를 검색할 수 있습니다
💡 처음에는 모든 항목을 수동으로 확인하고, 점차 자동화할 수 있는 항목을 스크립트나 CI/CD 파이프라인으로 옮기세요
💡 롤백 절차를 정기적으로(분기별) 연습하면, 실제 긴급 상황에서 당황하지 않고 빠르게 대응할 수 있습니다
💡 배포 체크리스트를 팀 위키나 Notion에 문서화하고, 배포 회고에서 나온 개선사항을 지속적으로 반영하세요
💡 고객 지원팀과 배포 일정을 공유하고, 예상되는 변경사항을 미리 알려주면 사용자 문의에 더 잘 대응할 수 있습니다