본문 바로가기
1. K8s Core & Architecture/1.3. 파드(Pod)와 워크로드 아키텍처

파드의 Graceful Termination: SIGTERM과 SIGKILL 사이의 30초

by K8s Architect 2026. 4. 10.

파드의 Graceful Termination: SIGTERM과 SIGKILL 사이의 30초

클라우드 네이티브 환경에서 파드(Pod)는 영원하지 않습니다. 스케일 다운, 롤링 업데이트, 노드 유지보수 등 다양한 이유로 하루에도 수십, 수백 번씩 파드가 생성되고 소멸합니다.

하지만 파드가 갑자기 죽어버리면 어떻게 될까요? 클라이언트가 다운로드를 받고 있던 세션이 끊어지거나, 데이터베이스에 저장 중이던 트랜잭션 데이터가 유실되는 끔찍한 장애가 발생할 수 있습니다. 쿠버네티스는 이러한 참사를 막고 애플리케이션이 안전하게 마무리 작업을 할 수 있도록 우아한 종료(Graceful Termination)라는 정교한 생명주기 메커니즘을 제공합니다.

이 가이드에서는 파드에 삭제 명령이 내려지는 순간부터 컨테이너가 완전히 소멸하기까지의 숨 막히는 30초의 과정을 단계별로 완벽하게 해부합니다.

1. 종료의 시작: 상태 변경과 네트워크 단절

사용자가 kubectl delete pod를 입력하거나 컨트롤러(Deployment 등)에 의해 파드 삭제가 트리거되면, 쿠버네티스 컨트롤 플레인에서는 즉각적으로 두 가지 작업이 병렬로 실행됩니다.

  • 상태 변경: API 서버는 해당 파드의 상태를 Terminating으로 변경합니다. 이 순간부터 겉보기에는 파드가 죽어가는 것처럼 보입니다.
  • 엔드포인트 제거 (네트워크 단절): Endpoint 컨트롤러는 파드의 IP 주소를 서비스(Service)의 가용 엔드포인트 목록에서 즉각 삭제합니다. 이 변경 사항은 각 워커 노드의 kube-proxy로 전파되어 iptables나 IPVS 라우팅 룰을 업데이트합니다. 이 작업이 완료된 순간부터 새로운 클라이언트의 트래픽은 더 이상 종료 중인 파드로 라우팅되지 않습니다.

하지만 주의할 점은, 분산 시스템의 특성상 네트워크 룰이 모든 워커 노드에 완벽히 동기화되기까지 미세한 지연 시간(네트워크 전파 시간)이 발생할 수 있다는 것입니다.

2. 골든 타임의 시작: preStop 훅(Hook)

파드의 명세서에 preStop 라이프사이클 훅이 정의되어 있다면, Kubelet은 종료 시그널(SIGTERM)을 메인 프로세스에 보내기 전에 이 훅을 가장 먼저 실행합니다.

  • 역할: 주로 네트워크 전파 지연으로 인해 아직 들어오고 있는 잔여 트래픽을 처리하기 위해 잠시 대기(sleep)하거나, 데이터베이스의 메모리 버퍼를 디스크로 안전하게 동기화(Flush)하는 셸 스크립트를 실행하는 데 사용됩니다.
  • 주의점: preStop 훅이 실행되는 시간도 전체 유예 시간(기본 30초)에 포함됩니다. 만약 preStop 스크립트가 무한 루프에 빠지거나 설정된 유예 시간 이상 걸리더라도, 총 시간이 지나면 가차 없이 다음 단계(강제 종료)로 넘어갑니다.

3. 애플리케이션의 마무리: SIGTERM 시그널 전송

preStop 훅이 완료되었거나 아예 설정되어 있지 않다면, Kubelet은 컨테이너 내부의 메인 프로세스(PID 1)에 SIGTERM (Signal Termination) 시그널을 전송합니다.

SIGTERM은 운영체제가 프로세스에게 "이제 곧 시스템이 너를 종료할 것이니, 하던 일을 멈추고 스스로 종료할 준비를 하라"라고 보내는 부드러운 권고 시그널입니다.
잘 만들어진 클라우드 네이티브 애플리케이션(예: Spring Boot의 Graceful Shutdown, Node.js의 프로세스 시그널 리스너 등)은 이 시그널을 포착(Catch)하여 다음과 같은 방어 로직을 수행합니다.

  1. 더 이상 외부와 새로운 네트워크 커넥션을 맺지 않음.
  2. 현재 진행 중인 사용자 요청(In-flight request)이 완전히 끝날 때까지 대기.
  3. 열려있는 데이터베이스 커넥션 풀을 안전하게 반환 및 정리.
  4. 모든 정리가 끝나면 스스로 프로세스 종료(Exit Code 0 반환).

4. 유예 시간 (terminationGracePeriodSeconds)

쿠버네티스는 SIGTERM을 보낸 후 파드가 스스로 종료될 때까지 무작정 기다려주지 않습니다. 명세서에 정의된 terminationGracePeriodSeconds (기본값: 30초)라는 엄격한 타이머를 작동시킵니다.

이 30초는 preStop 훅의 실행 시간과 SIGTERM 이후 애플리케이션이 마무리 작업을 하는 시간을 모두 합친 '총 허용 유예 시간'입니다.
만약 파드가 이 시간 안에 모든 작업을 마치고 스스로 안전하게 종료된다면, 프로세스는 거기서 깔끔하게 끝이 나고 Kubelet은 파드를 클러스터에서 완전히 삭제하여 상황을 종료합니다.

5. 최후의 심판: SIGKILL과 강제 종료

만약 애플리케이션의 로직에 결함이 있어 SIGTERM을 무시하거나, 진행 중인 트랜잭션이 너무 거대하여 30초의 유예 시간이 모두 소진될 때까지 컨테이너 프로세스가 끈질기게 살아있다면 어떻게 될까요?

Kubelet은 유예 시간 타이머가 0이 되는 순간, 어떠한 타협도 없이 리눅스 커널을 통해 SIGKILL 시그널을 날립니다.
SIGTERM이 권고라면 SIGKILL은 즉결 처형입니다. 프로세스는 이 시그널을 프로그램 코드로 무시하거나 훅을 걸어 방어할 수 없으며, 메모리에 어떤 중요한 데이터가 남아있든 상관없이 커널에 의해 메모리에서 강제로 즉시 삭제당합니다.

6. 무중단 배포를 위한 아키텍처 베스트 프랙티스

파드의 우아한 종료 메커니즘을 완벽하게 활용하여 무중단(Zero-Downtime) 배포 아키텍처를 달성하려면 다음의 인프라 규칙들을 반드시 준수해야 합니다.

  1. 애플리케이션의 SIGTERM 수신 처리: 개발하는 애플리케이션 코드가 리눅스의 SIGTERM 시그널을 정상적으로 수신하고 프로세스를 안전하게 닫는 방어 로직을 반드시 프레임워크 단에 구현해야 합니다.
  2. 적절한 유예 시간 설정: 무거운 배치(Batch) 작업이나 다운로드 처리 시간이 긴 API 파드라면, 기본 30초의 유예 시간이 턱없이 부족할 수 있습니다. 파드 명세서에서 terminationGracePeriodSeconds: 60 또는 그 이상으로 비즈니스 로직에 맞는 충분한 시간을 부여해야 합니다.
  3. preStop 훅을 활용한 트래픽 유실 방지: Nginx나 Spring Boot 서버를 운영할 때, 쿠버네티스의 네트워크 룰(iptables) 업데이트 지연으로 인한 502 Bad Gateway 에러를 막기 위해 preStop 훅에 sleep 5와 같은 짧은 대기 명령을 추가하는 것이 좋습니다. 이를 통해 아직 라우팅되고 있는 마지막 트래픽까지 안전하게 받아낼 시간을 벌 수 있습니다.
  4. Dockerfile의 진입점(Entrypoint) 확인: 도커 이미지를 빌드할 때 ENTRYPOINT ["java", "-jar", "app.jar"] (Exec 폼) 형태로 작성해야 내부의 애플리케이션이 1번 프로세스(PID 1)가 되어 SIGTERM을 직접 받을 수 있습니다. 만약 ENTRYPOINT java -jar app.jar (Shell 폼)으로 작성하면 리눅스 셸(/bin/sh)이 PID 1이 되어버립니다. 이 경우 메인 애플리케이션은 시그널을 전달받지 못한 채 유예 시간을 다 허비하고, 결국 SIGKILL로 강제 종료되는 치명적인 안티 패턴이 발생하므로 각별히 주의해야 합니다.

파드의 라이프사이클을 철저하게 통제하는 이 30초의 과학을 온전히 이해할 때, 비로소 클러스터 환경의 잦은 인프라 롤링 업데이트에도 결코 흔들리지 않는 진정한 의미의 견고한 서비스를 구축할 수 있습니다.