본문 바로가기

Programming/Spring Boot

Spring Boot Graceful Shutdown 설정: 배포 중 요청을 안전하게 종료하는 방법

추천캐릭터 2026. 7. 28. 13:00
728x90

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일

728x90