Kubernetes 무중단 롤아웃의 핵심

Kubernetes 무중단 롤아웃의 핵심: Pod 종료(termination)와 Probe를 같이 설계하기 요약 이미지
요약 이미지(직접 생성). 무단 사용 금지.

Kubernetes 무중단 롤아웃의 핵심: Pod 종료(termination)와 Probe를 같이 설계하기

쿠버네티스에서 “무중단”을 얘기할 때 대부분은 RollingUpdate를 먼저 떠올리지만, 실제 장애는 배포 전략보다 Pod 종료(termination) 순간Probe(readiness/liveness/startup) 설계가 어긋날 때 더 자주 발생합니다. 이 글은 Kubernetes 공식 문서에 근거해서, 종료 신호(SIGTERM)부터 연결 드레인(drain), 그리고 readiness 전환 타이밍까지를 하나의 설계로 묶어 설명합니다.

TL;DR (현장에서 바로 쓰는 결론)

  • 종료는 “즉시 죽음”이 아니라 “종료 요청 + 유예 시간”입니다. Pod가 삭제되면 kubelet은 컨테이너에 종료 신호를 보내고, 설정된 유예 시간(terminationGracePeriodSeconds) 동안 기다립니다.
  • readiness는 “트래픽 받을 준비”의 스위치입니다. 종료 직전에 readiness를 먼저 내려야(=NotReady) Service 엔드포인트에서 빠지고, 새 연결 유입을 끊을 수 있습니다.
  • preStop은 “종료 직전 훅”입니다. preStop은 종료 과정의 일부로 호출되며, 여기서 드레인/대기(짧게) 또는 애플리케이션에 종료 준비를 시킬 수 있습니다.
  • liveness는 “죽였을 때 이득이 있을 때만” 쓰는 게 안전합니다. 무거운 liveness는 재시작 루프를 유발할 수 있습니다. 준비/기동(startupProbe)과 목적을 분리하세요.

1) Kubernetes가 Pod를 “종료”시키는 정확한 흐름

Kubernetes 공식 문서 기준으로 Pod를 삭제하면(Deployment 롤아웃/스케일 인/수동 삭제 포함) Pod는 Terminating 상태가 되며, kubelet은 컨테이너 런타임을 통해 컨테이너 종료를 트리거합니다. 이 과정에서 terminationGracePeriodSeconds로 정의된 유예 시간이 존재합니다.

핵심은 “Pod가 Terminating으로 보이는 순간”부터 “실제로 프로세스가 종료되는 순간” 사이에 트래픽 차단(readiness) + 연결 드레인 + 리소스 정리를 할 수 있는 창이 있다는 점입니다.

종료 타임라인(개념도)

T0: 삭제 요청 T1: NotReady(드레인) T2: SIGTERM 처리 T3: 강제 종료(유예 끝)

Endpoint에서 제외(readiness false) 기존 연결 처리/큐 비우기 애플리케이션 종료 루틴

권장: terminationGracePeriodSeconds 안에 preStop + SIGTERM 핸들링 + 드레인 시간을 모두 포함

이 다이어그램은 이해를 돕기 위한 개념도이며, Kubernetes 동작의 근거는 공식 문서(아래 ‘근거(원문)’ 섹션)에서 확인할 수 있습니다.

2) Probe를 “정확히” 분리해서 설계하는 법

Kubernetes는 컨테이너 상태를 판단하기 위해 livenessProbe, readinessProbe, startupProbe를 제공합니다. 이들은 목적이 다릅니다.

  • readinessProbe: 트래픽을 받을 준비가 되었는지(서비스 엔드포인트 포함 여부) 판단.
  • livenessProbe: 컨테이너가 “살아있지 않다”고 판단하면 재시작을 유도.
  • startupProbe: 초기 기동이 느린 컨테이너에서, 기동 완료 전까지 liveness/readiness 체크를 늦추는 용도.

실무에서 흔한 실수는 liveness를 readiness처럼 쓰는 것입니다. liveness는 실패 시 컨테이너 재시작이라는 강한 조치를 유발하므로, 오탐(일시적 GC/DB 지연/스파이크)으로도 재시작 루프가 생길 수 있습니다. 반면 readiness는 “트래픽에서 제외”라는 비교적 안전한 조치입니다.

예시: HTTP 서버(예: Spring Boot/NestJS)의 Probe 설계

아래는 컨테이너가 준비되면 readiness가 true가 되고, 기동 중에는 startupProbe가 보호막 역할을 하며, liveness는 최소한의 생존 확인만 하는 형태의 예시입니다. (엔드포인트 경로는 애플리케이션에 맞게 구성하세요.)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  template:
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: api
          image: example.com/api:1.0.0
          ports:
            - containerPort: 8080
          lifecycle:
            preStop:
              httpGet:
                path: /internal/drain
                port: 8080
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080
            periodSeconds: 5
            failureThreshold: 1
          livenessProbe:
            httpGet:
              path: /health/live
              port: 8080
            periodSeconds: 10
            failureThreshold: 3
          startupProbe:
            httpGet:
              path: /health/startup
              port: 8080
            periodSeconds: 5
            failureThreshold: 24

포인트

  • preStop에서 /internal/drain 같은 내부 엔드포인트를 호출해, 애플리케이션이 먼저 readiness를 내려 트래픽 유입을 끊게 만듭니다.
  • terminationGracePeriodSeconds는 “드레인 + 처리 중 요청 완료 + 종료”까지 포함한 충분한 값으로 잡습니다.
  • readinessProbe failureThreshold: 1처럼 빠르게 NotReady로 전환되게 하면, 롤아웃 중 유입 차단이 빨라집니다(서비스 특성에 맞춰 조정).

3) 애플리케이션 계층: SIGTERM을 제대로 처리해야 한다

Kubernetes는 종료 과정에서 컨테이너 프로세스에 종료 신호를 전달합니다. 이때 애플리케이션이 SIGTERM을 무시하면(또는 종료 훅이 너무 오래 걸리면) 유예 시간이 끝난 뒤 강제 종료될 수 있습니다.

Spring Boot: ‘graceful shutdown’ 기능 활용

Spring Boot는 웹 서버(서블릿/리액티브)에 대해 graceful shutdown 설정을 제공합니다. 설정 방식과 의미는 Spring Boot 공식 레퍼런스에서 확인할 수 있습니다.

# application.yml
server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

NestJS(Node.js): shutdown hook과 SIGTERM

NestJS는 애플리케이션 종료 이벤트를 처리하기 위한 shutdown hook 메커니즘을 제공합니다. Node.js는 프로세스에 전달되는 신호 이벤트(SIGTERM 등)를 다루는 API를 제공합니다. 구현에서는 (1) readiness를 내리고, (2) HTTP 서버를 새 연결을 받지 않게 닫고, (3) DB 커넥션 풀을 정리하는 흐름이 흔합니다.

// NestJS 예시(개념)
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableShutdownHooks();
  await app.listen(3000);
}

4) MySQL 커넥션/트랜잭션: “종료 직전”이 제일 위험하다

애플리케이션이 MySQL을 쓰는 경우, 종료 시점에는 다음 두 가지 리스크가 큽니다.

  • 새 연결 유입을 차단하지 못해 종료 직전까지 새 요청이 들어오고, 트랜잭션이 미완료로 남는 문제
  • 커넥션 풀 정리 없이 프로세스가 종료되어, 응답 지연/오류가 순간적으로 튀는 문제

따라서 “Kubernetes 설정만으로 무중단”을 기대하면 부족합니다. readiness를 먼저 내려 트래픽 유입을 끊고, 애플리케이션에서 종료 훅으로 커넥션 풀 종료/타임아웃을 명시적으로 관리해야 합니다. (프레임워크별 방법은 각 공식 문서의 graceful shutdown/종료 훅 섹션을 기준으로 구현하세요.)

5) 운영 체크리스트(배포 전 10분 점검)

  1. Deployment RollingUpdate에서 maxUnavailable=0가 필요한 서비스인지 판단(트래픽/비용/가용성).
  2. readinessProbe는 “외부 의존성(예: DB)”을 어디까지 포함할지 결정(과도하면 플래핑 위험).
  3. livenessProbe는 최소화: 오탐으로 재시작 루프가 나지 않는지 확인.
  4. startupProbe를 사용해 초기 기동 중 liveness 오탐을 방지(기동이 느린 이미지/마이그레이션 포함 시 특히).
  5. preStop + SIGTERM 핸들링 + terminationGracePeriodSeconds의 합이 실제 드레인 시간을 충족하는지 부하테스트로 확인.
  6. Ingress/LoadBalancer의 idle timeout과 애플리케이션 keep-alive 설정이 종료 설계와 충돌하지 않는지 점검.

6) 장애 패턴과 해결 힌트(현장용)

증상 가능한 원인 우선 조치
롤아웃 때 502/504가 순간적으로 튄다 readiness가 늦게 내려가거나, 종료 중에도 새 연결을 받음 readiness 전환을 더 빠르게, preStop에서 드레인 엔드포인트 호출, terminationGracePeriodSeconds 상향
Pod가 Terminating에서 오래 멈춘다 종료 훅이 과도하게 길거나 외부 의존성에서 블로킹 종료 단계별 타임아웃 도입, 외부 호출 최소화, 로깅으로 종료 단계 시간 측정
liveness 실패로 재시작 루프 liveness가 과하게 민감하거나, 기동 중 체크가 시작됨 startupProbe 도입/조정, liveness는 “정말 죽었을 때만” 실패하도록 단순화

7) 결론: ‘Probe + 종료’는 한 세트로 설계해야 한다

무중단은 “배포 전략”이 아니라 “종료 순간의 품질”에서 갈립니다. Kubernetes가 제공하는 termination 흐름과 probe 의미를 정확히 이해하고, 애플리케이션의 SIGTERM 처리/커넥션 드레인을 함께 설계하면 롤아웃의 신뢰도가 확 올라갑니다.

근거(원문)

이 글의 동작 설명과 용어 정의는 아래 공식 문서/원문을 기준으로 작성했습니다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux