이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • Rolling 배포 중 readiness, deregistration, draining, graceful shutdown 순서를 어떻게 맞추는가?
  • 배포 직후 짧은 502/503/connection reset이 왜 생기는가?
  • canary/blue-green 배포에서 네트워크 계층의 성공 기준을 어떻게 잡는가?

개요

배포는 새 프로세스를 띄우는 일이 아니라 트래픽 소유권을 안전하게 옮기는 일이다. Load Balancer는 target 등록/해제와 connection draining으로 이 전환을 돕지만, 앱이 readiness와 graceful shutdown을 제대로 제공하지 않으면 배포 중 짧은 장애가 생긴다.

  • 새 target은 준비되기 전 traffic을 받으면 안 된다.
  • 제거되는 target은 새 traffic을 받지 않되 기존 요청을 마칠 시간이 필요하다.
  • app shutdown timeout은 LB draining 시간과 맞아야 한다.
  • long request, upload, WebSocket/SSE는 draining 시간을 더 길게 요구한다.

원리

안전한 종료 순서는 다음에 가깝다.

1. SIGTERM received
2. readiness becomes false
3. LB stops sending new requests
4. in-flight requests finish
5. server stops accepting connections
6. process exits

이 순서가 어긋나면 SIGTERM 이후에도 새 요청을 받거나, LB가 아직 target으로 보내는데 프로세스가 먼저 죽어 502/connection reset이 난다.

Spring Boot Graceful Shutdown

Spring Boot 설정 예시다.

server:
  shutdown: graceful
 
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s
 
management:
  endpoint:
    health:
      probes:
        enabled: true

이 값은 Kubernetes terminationGracePeriodSeconds, ALB deregistration delay, gateway timeout과 함께 맞춰야 한다.

배포 중 흔한 장애

증상가능 원인
배포 직후 502target 등록 전 app listen 미완료, old target 먼저 종료
배포 직후 503healthy target 없음
connection resetprocess가 in-flight 요청보다 먼저 종료
일부 사용자만 실패canary target group 문제
long request만 실패draining/shutdown timeout 부족
WebSocket 대량 재연결target drain으로 connection 종료 후 reconnect storm

코드와 명령으로 확인하기

배포 타임라인을 남긴다.

10:00:00 new pod started
10:00:20 readiness UP
10:00:25 LB target healthy
10:02:00 old pod SIGTERM
10:02:01 readiness DOWN
10:02:10 LB target draining
10:02:35 in-flight requests completed
10:02:40 process exited

배포 중 smoke test는 전체 endpoint가 아니라 version별 target을 구분해야 한다.

for i in {1..20}; do
  curl -sI https://api.example.com/health \
    | grep -iE 'x-app-version|x-app-instance|x-request-id'
  sleep 1
done

Canary와 Rollback

canary 배포는 네트워크 계층에서도 기준이 필요하다.

  • canary target group 5xx
  • canary p95/p99 latency
  • target health transition count
  • connection reset/timeout count
  • business metric 또는 synthetic check

rollback은 traffic weight를 되돌리는 것뿐 아니라 DB migration, cache, message consumer, old asset compatibility까지 포함한다.

인프라 협업 포인트

  • 앱 팀은 shutdown에 필요한 최대 시간과 long request 종류를 알려야 한다.
  • 인프라 팀은 deregistration delay, health check interval/threshold, rolling update max unavailable 값을 공유해야 한다.
  • 배포 도구, orchestrator, LB가 각각 언제 target을 빼는지 타임라인으로 맞춘다.
  • DB migration은 old/new app이 동시에 동작할 수 있는 expand-contract 방식이 안전하다.

실전 팁

  • readiness down을 먼저 만들고 sleep으로 시간을 버는 preStop hook은 임시방편이지만, LB 반영 지연을 이해하는 데 도움이 된다.
  • shutdown timeout은 가장 긴 정상 요청보다 길어야 하지만 무한정 길면 배포가 느려진다.
  • WebSocket/SSE 서비스는 배포 공지 또는 재연결 UX가 필요할 수 있다.
  • canary가 실패하면 자동 rollback 기준을 사전에 정한다. 사람이 그래프를 보고 판단하는 시간도 장애 시간이다.
  • 개인 프로젝트에서도 systemd/docker compose 재시작 전에 health check와 graceful shutdown을 설정하면 배포 중 502가 줄어든다.

위험 신호!

  • SIGTERM 후에도 readiness가 계속 UP이다.
  • LB deregistration delay보다 app shutdown timeout이 짧다.
  • 배포 성공 기준이 “프로세스 시작”뿐이고 synthetic request가 없다.
  • DB migration이 old version과 호환되지 않는다.

확인 질문

확인 질문

  • 배포 중 readiness를 먼저 내려야 하는 이유는 무엇인가?
    • LB나 orchestrator가 새 요청을 보내지 않도록 한 뒤 기존 요청을 마무리해야 하기 때문이다.
  • deregistration delay와 app shutdown timeout이 어긋나면 어떤 일이 생기는가?
    • LB가 아직 보내는 중인데 app이 먼저 종료되거나, app은 기다리는데 LB가 먼저 끊어 connection reset/502가 날 수 있다.
  • canary 배포에서 전체 error rate만 보면 위험한 이유는 무엇인가?
    • 작은 비율의 canary 장애가 전체 평균에 희석되어 canary 대상 사용자 실패를 놓칠 수 있기 때문이다.

참고 문서