Spring Boot + Kubernetes + MySQL: 트래픽을 끊지 않고 안전하게 종료(Graceful Shutdown)하는 실전 설정
쿠버네티스에서 Spring Boot 애플리케이션을 롤링 업데이트/스케일 인/노드 드레인할 때, “종료 직전까지 들어온 요청”과 “MySQL 커넥션 풀”을 어떻게 정리하느냐에 따라 5xx, 타임아웃, 커넥션 누수가 발생합니다. 이 글은 쿠버네티스(Pod termination + probe)와 Spring Boot(Graceful shutdown + Actuator probes)가 제공하는 공식 메커니즘을 조합해, 종료 구간의 오류를 최소화하는 구성을 정리합니다.
TL;DR (바로 적용 체크리스트)
- 쿠버네티스:
readinessProbe로 트래픽 컷(Ready=false) →terminationGracePeriodSeconds로 정리 시간 확보 - Spring Boot:
server.shutdown=graceful로 graceful shutdown 활성화 - Spring Boot Actuator:
/actuator/health/readiness,/actuator/health/liveness를 probe endpoint로 사용(공식 “Kubernetes probes” 지원) - preStop 훅(선택): readiness가 먼저 내려가도록 지연을 주거나, 앱이 정리할 시간을 보조
종료 시 실제로 무슨 일이 일어나는가: Kubernetes Pod termination의 공식 흐름
쿠버네티스는 Pod를 종료할 때 컨테이너 프로세스에 종료 시그널을 전달하고, terminationGracePeriodSeconds 동안 정상 종료를 기다린 뒤, 시간이 지나면 강제 종료를 수행합니다. 또한 Pod가 종료되기 시작하면 서비스 엔드포인트에서 제거되도록 동작합니다(Ready 상태에 따라 트래픽 라우팅이 달라짐). 이 “정리 구간”을 앱이 제대로 활용할 수 있게 만드는 것이 핵심입니다.
핵심 목표
- 새 요청 유입 차단: readiness를 먼저 내려서 Load Balancer/Service가 새 요청을 보내지 않게 함
- 진행 중 요청 마무리: Spring Boot graceful shutdown이 진행 중 요청을 완료할 시간을 확보
- DB 커넥션 정상 반납: 커넥션 풀(HikariCP 등)이 정상적으로 close되도록 종료 시간을 확보
시퀀스(개념도)
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) 확보 |
검증 절차(운영 체크리스트)
- Pod 하나를 강제로 재시작(롤링 업데이트)했을 때, readiness가 먼저 내려가는지 확인
- 종료 시점에 진행 중 요청이 정상 완료되는지(클라이언트 타임아웃/5xx) 확인
- 정리 시간이 부족하면
terminationGracePeriodSeconds와 Boot shutdown timeout을 조정 - 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로 연결하는 방법”에 초점을 두며, 보안 정책은 환경 요구에 맞춰 별도 설계가 필요합니다.
공식 근거(원문 링크)
- Spring Boot Reference Documentation – Graceful shutdown: https://docs.spring.io/spring-boot/docs/current/reference/html/web.html#web.graceful-shutdown
- Spring Boot Reference Documentation – Actuator Kubernetes Probes(health groups / liveness / readiness): https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints.kubernetes-probes
- Kubernetes Documentation – Pod termination: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination
- Kubernetes Documentation – Liveness/Readiness/Startup probes: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
주의: 위 링크를 기준으로만 문장/동작을 확정했으며, 환경별(Ingress/LB/프록시/드라이버/풀 설정) 차이는 운영 관측으로 조정이 필요합니다.