Spring Boot + Kubernetes +

Spring Boot + Kubernetes + MySQL: 트래픽을 끊지 않고 안전하게 종료(Graceful Shutdown)하는 실전 설정

쿠버네티스에서 Spring Boot 애플리케이션을 롤링 업데이트/스케일 인/노드 드레인할 때, “종료 직전까지 들어온 요청”과 “MySQL 커넥션 풀”을 어떻게 정리하느냐에 따라 5xx, 타임아웃, 커넥션 누수가 발생합니다. 이 글은 쿠버네티스(Pod termination + probe)Spring Boot(Graceful shutdown + Actuator probes)가 제공하는 공식 메커니즘을 조합해, 종료 구간의 오류를 최소화하는 구성을 정리합니다.

TL;DR (바로 적용 체크리스트)

  1. 쿠버네티스: readinessProbe로 트래픽 컷(Ready=false) → terminationGracePeriodSeconds로 정리 시간 확보
  2. Spring Boot: server.shutdown=graceful로 graceful shutdown 활성화
  3. Spring Boot Actuator: /actuator/health/readiness, /actuator/health/liveness를 probe endpoint로 사용(공식 “Kubernetes probes” 지원)
  4. preStop 훅(선택): readiness가 먼저 내려가도록 지연을 주거나, 앱이 정리할 시간을 보조

종료 시 실제로 무슨 일이 일어나는가: Kubernetes Pod termination의 공식 흐름

쿠버네티스는 Pod를 종료할 때 컨테이너 프로세스에 종료 시그널을 전달하고, terminationGracePeriodSeconds 동안 정상 종료를 기다린 뒤, 시간이 지나면 강제 종료를 수행합니다. 또한 Pod가 종료되기 시작하면 서비스 엔드포인트에서 제거되도록 동작합니다(Ready 상태에 따라 트래픽 라우팅이 달라짐). 이 “정리 구간”을 앱이 제대로 활용할 수 있게 만드는 것이 핵심입니다.

핵심 목표

  • 새 요청 유입 차단: readiness를 먼저 내려서 Load Balancer/Service가 새 요청을 보내지 않게 함
  • 진행 중 요청 마무리: Spring Boot graceful shutdown이 진행 중 요청을 완료할 시간을 확보
  • DB 커넥션 정상 반납: 커넥션 풀(HikariCP 등)이 정상적으로 close되도록 종료 시간을 확보

시퀀스(개념도)

Kubernetes (kubelet)

Spring Boot App (HTTP server)

Service/Ingress

MySQL

SIGTERM / termination 시작

readiness 실패(Ready=false)로 전환

새 트래픽 라우팅 중단

진행 중 요청 처리 마무리 / 커넥션 정리

그림: 종료 시그널 → readiness 하강 → 새 트래픽 차단 → graceful shutdown으로 정리(개념도) | 직접 생성. 무단 사용 금지

Spring Boot: Graceful Shutdown 활성화(공식 설정)

Spring Boot는 서버(예: Tomcat/Jetty/Undertow)가 종료될 때, 새로운 요청을 받지 않도록 하고 진행 중 요청을 마무리할 수 있도록 graceful shutdown 설정을 제공합니다. 아래 설정은 Spring Boot 공식 문서에 나오는 구성입니다.

# application.yaml
server:
  shutdown: graceful

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

spring.lifecycle.timeout-per-shutdown-phase는 종료 단계에서 대기할 시간을 지정합니다(공식 문서 기준). 쿠버네티스의 terminationGracePeriodSeconds는 이 값과 애플리케이션 정리 시간을 충분히 커버하도록 더 크게 잡는 것이 안전합니다.

Actuator: Kubernetes Probe용 엔드포인트를 공식 방식으로 연결

Spring Boot Actuator는 쿠버네티스에서 흔히 쓰는 liveness/readiness probe에 맞춘 health endpoint를 제공합니다. 일반적으로 다음과 같은 경로를 사용합니다.

  • /actuator/health/liveness
  • /actuator/health/readiness

아래는 Actuator를 켜고 probe endpoint 접근을 허용하는 예시입니다(환경/보안 요구에 맞게 노출 범위는 조정하세요).

# application.yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info
  endpoint:
    health:
      probes:
        enabled: true

Kubernetes Deployment 예시: readiness/liveness + 종료 유예 시간

아래 예시는 Spring Boot Actuator probe endpoint를 쿠버네티스 probe로 연결하는 전형적인 형태입니다. 실제 경로/포트는 서비스 구성에 맞게 변경하세요.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-springboot
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
  template:
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: app
          image: your-registry/your-app:1.0.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
            timeoutSeconds: 2
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10
            timeoutSeconds: 2
            failureThreshold: 3

포인트: readiness는 “트래픽을 받아도 되는가”에 직접 연결됩니다. 종료 시점에 readiness가 빨리 내려가면(Ready=false), 새 요청 유입을 막는 데 도움이 됩니다.

preStop 훅은 언제 필요할까? (선택)

쿠버네티스는 종료 과정에서 preStop 훅을 제공할 수 있습니다. 환경에 따라 readiness 전환 타이밍이나, 로드밸런서/인그레스의 엔드포인트 제거 지연 등을 고려해 약간의 지연을 줄 때 사용합니다. 다만, 무작정 sleep을 늘리기보다는 “readiness가 먼저 내려가고, 그 다음 SIGTERM/정리”가 자연스럽게 이뤄지는지 관측 기반으로 결정하는 것이 좋습니다.

실무 의사결정 매트릭스(운영 관점)

이슈 증상 우선 점검
종료 중 5xx 증가 업데이트/스케일 인 순간 오류 readinessProbe 경로/timeout, server.shutdown=graceful 적용 여부
종료가 너무 빨리 강제 종료됨 로그가 중간에 끊김 terminationGracePeriodSeconds > Spring shutdown timeout인지 확인
DB 커넥션 이상(잠김/재시도 폭증) 종료 직전/직후 커넥션 에러 종료 중 트래픽 차단(readiness), 정리 시간(boot lifecycle timeout) 확보

검증 절차(운영 체크리스트)

  1. Pod 하나를 강제로 재시작(롤링 업데이트)했을 때, readiness가 먼저 내려가는지 확인
  2. 종료 시점에 진행 중 요청이 정상 완료되는지(클라이언트 타임아웃/5xx) 확인
  3. 정리 시간이 부족하면 terminationGracePeriodSeconds와 Boot shutdown timeout을 조정
  4. Actuator endpoint 접근이 막히면(403/404) 노출 설정 및 경로를 확인

자주 겪는 문제(FAQ)

Q1. readiness가 내려가기 전에 새 요청이 계속 들어옵니다.

readinessProbe가 실제로 트래픽 경로(Service/Ingress)가 참조하는 Pod readiness와 연결되어 있는지 확인하세요. 또한 probe의 periodSeconds/failureThreshold 값이 너무 커서 전환이 늦어질 수 있습니다(쿠버네티스 probe 공식 동작에 따름).

Q2. graceful shutdown을 켰는데도 일부 요청이 끊깁니다.

클라이언트/프록시(예: Ingress, API Gateway)의 타임아웃이 앱의 정리 시간보다 짧으면, 서버가 정상적으로 처리 중이어도 클라이언트가 먼저 끊을 수 있습니다. 쿠버네티스의 종료 유예 시간, Spring의 shutdown timeout, 그리고 프록시 타임아웃을 함께 정렬해야 합니다.

Q3. liveness/readiness endpoint를 노출하는 게 불안합니다.

Actuator 노출 범위는 최소화하고(필요 엔드포인트만), 네트워크 정책/인그레스 규칙으로 접근을 제한하는 것이 일반적입니다. 이 글은 “공식 엔드포인트를 probe로 연결하는 방법”에 초점을 두며, 보안 정책은 환경 요구에 맞춰 별도 설계가 필요합니다.

공식 근거(원문 링크)

주의: 위 링크를 기준으로만 문장/동작을 확정했으며, 환경별(Ingress/LB/프록시/드라이버/풀 설정) 차이는 운영 관측으로 조정이 필요합니다.

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