Spring Boot Graceful Shutdown 설정: 배포 중 요청을 안전하게 종료하는 방법
배포 순간 일부 요청만 502나 503으로 실패한다면 종료 방식을 확인해야 합니다. Graceful Shutdown은 새 요청을 막고 처리 중인 요청에 완료 시간을 준 뒤 종료하는 방식으로, 롤링 배포의 기본 설정에 가깝습니다.
즉시 종료와 정상 종료의 차이
| 구분 | 즉시 종료 | Graceful Shutdown |
|---|---|---|
| 새 요청 | 연결이 갑자기 끊길 수 있음 | 더 이상 받지 않음 |
| 진행 중 요청 | 중간 종료 가능 | 제한 시간 안에서 완료 |
| 리소스 정리 | 보장하기 어려움 | Spring 컨텍스트 종료 과정 수행 |
| 배포 중 오류 | 502·연결 초기화 가능성 증가 | 사용자 오류 감소 |
| 종료 시간 | 빠름 | 설정한 유예 시간만큼 길어질 수 있음 |
제한 시간이 끝나면 남은 작업은 종료될 수 있으므로 요청 타임아웃과 함께 설계해야 합니다.
Spring Boot 4.1의 기본 동작
Spring Boot 4.1은 Tomcat, Jetty, Reactor Netty의 정상 종료를 기본 활성화합니다. 유예 시간은 spring.lifecycle.timeout-per-shutdown-phase로 지정합니다.
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 25s
현재는 기본값이지만 설정에 명시하면 운영 의도를 확인하기 쉽습니다. 오래된 버전은 해당 문서를 확인해야 합니다.
정상 종료가 시작되는 조건
Linux 서비스와 컨테이너는 보통 SIGTERM으로 정상 종료를 시작합니다.
kill -15 <PID>
반면 다음 명령은 프로세스를 즉시 강제 종료합니다.
kill -9 <PID>
SIGKILL은 애플리케이션이 처리할 수 없어 정상 종료와 리소스 정리가 실행되지 않습니다. 배포 스크립트에서 kill -9를 기본값으로 사용하면 안 됩니다. IDE 종료는 동작이 다를 수 있으므로 systemd나 컨테이너에서 검증합니다.
Actuator Readiness와 함께 사용하기
서버 종료 전에 로드밸런서가 새 트래픽을 보내지 않아야 합니다. Actuator는 다음 상태를 제공합니다.
| 상태 | 의미 | Kubernetes 동작 |
|---|---|---|
| Liveness | 프로세스가 복구 불가능하게 고장 났는가 | 실패하면 컨테이너 재시작 |
| Readiness | 지금 요청을 받을 준비가 됐는가 | 실패하면 서비스 트래픽에서 제외 |
Actuator 의존성을 추가합니다.
implementation 'org.springframework.boot:spring-boot-starter-actuator'
메인 서버 포트에 간단한 경로를 함께 노출할 수 있습니다.
management:
endpoint:
health:
probes:
add-additional-paths: true
이 설정은 메인 포트에 /livez와 /readyz를 추가합니다. 별도 관리 포트만 검사하면 애플리케이션 포트 장애를 놓칠 수 있습니다.
Kubernetes 설정 예제
Kubernetes의 기본 Pod 종료 유예 시간은 30초입니다. preStop도 여기에 포함되므로 전체 시간을 더 길게 잡아야 합니다.
spec:
terminationGracePeriodSeconds: 35
containers:
- name: api
image: example/api:1.0.0
ports:
- containerPort: 8080
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
readinessProbe:
httpGet:
path: /readyz
port: 8080
periodSeconds: 5
livenessProbe:
httpGet:
path: /livez
port: 8080
periodSeconds: 10
예시는 경로 제외에 5초, Spring에 최대 25초를 주고 35초 안에 여유를 둡니다. 실제 값은 가장 긴 요청과 로드밸런서 반영 시간을 측정해 정합니다. preStop과 전체 유예 시간을 같게 두면 Spring이 종료할 시간이 남지 않습니다.
실무에서 자주 하는 실수
- 정상 종료만 켜면 무중단 배포가 된다고 생각합니다. 로드밸런서, readiness와 여러 인스턴스가 필요합니다.
- 가장 긴 요청보다 종료 제한을 짧게 둡니다.
- liveness에 공유 DB를 넣어 DB 장애 때 모든 Pod를 재시작합니다.
- 배포 스크립트에서
kill -9를 사용합니다. - 오래 걸리는 작업을 HTTP 요청 안에서 처리합니다. 큐 기반 작업으로 분리하는 편이 안전합니다.
핵심 요약
- Graceful Shutdown은 새 요청을 막고 진행 중 요청에 완료 시간을 줍니다.
- Spring Boot 4.1에서는 지원 웹 서버의 정상 종료가 기본 활성화돼 있습니다.
spring.lifecycle.timeout-per-shutdown-phase로 유예 시간을 설정합니다.- 운영 종료 신호는 SIGTERM을 사용하고
kill -9는 피합니다. - Kubernetes의 전체 종료 시간은
preStop + Spring 종료 시간 + 여유 시간보다 길어야 합니다. - readiness는 트래픽 제외, liveness는 재시작 판단에 사용합니다.
마무리
로드밸런서에서 인스턴스를 제외하고 진행 중 요청을 끝낸 뒤 프로세스를 종료해야 합니다. 테스트 API에 지연을 넣고 배포 중 요청이 성공하는지 확인해 보세요.
배포 중 발생하는 게이트웨이 오류 진단은 502 Bad Gateway 원인 분석, 버전별 운영 기준은 Spring Boot 지원 기간과 버전 선택, 4.x의 주요 변경점은 Spring Boot 4.0 핵심 변경점에서 이어서 볼 수 있습니다.
공식 자료:
최종 확인: 2026년 7월 27일
'Programming > Spring Boot' 카테고리의 다른 글
| Spring Boot 테스트 슬라이스 선택법: @WebMvcTest·@DataJpaTest·@SpringBootTest 차이 (0) | 2026.08.03 |
|---|---|
| Spring Boot @ConfigurationProperties 검증: 잘못된 운영 설정을 시작 단계에서 차단하기 (0) | 2026.07.31 |
| Spring Boot LTS는 없을까? 4.1·4.0·3.5 지원 기간과 버전 선택 (0) | 2026.07.14 |
| Spring Boot JPA N+1 문제 해결하기 (0) | 2026.06.30 |
| Spring Boot에서 REST API 응답 구조를 일관되게 설계하는 방법 (0) | 2026.06.25 |