"Graceful Shutdown을 안전한게 구성하려면 어떻게 해야 하나요?" 라는 질문을 받았을 때, 머릿속에서는 SIGTERM, preStop 같은 키워드가 둥둥 떠다녔지만, 정작 논리 정연하게 설명하지 못했다. 안다고 착각했던 지식을 진짜 내 것으로 만들기 위해, 그리고 서비스의 무중단 배포 방법을 알기 위해 Graceful Shutdown의 원리와 설정 방법을 확실히 정리해 본다.
Graceful Shutdown은 말 그대로 "우아한 종료"다. 애플리케이션이 종료 신호를 받았을 때, 하던 작업(진행 중인 API 요청, DB트랜잭션 등)을 강제로 끊지 않고 안전하게 마무리한 뒤 종료 하는 것을 말한다. 이게 안되면 배포(Rolling Update) 때마다 사용자는 "500 Internal Server Error"를 겪게 된다.
쿠버네티스가 Pod를 종료할 때의 순서를 이해하는 것이 핵심이다.
문제점: 트래픽 차단(Endpoint 갱신)이 전파되는 속도보다 SIGTERM을 받고 앱이 종료되는 속도가 더 빠를 때 발생한다. 로드밸런서는 아직 Pod가 살아있다고 생각해서 요청을 보내는데, Pod는 이미 문을 닫아버린 상황이 생기는 것이다.
이를 방지하기 위해 두 가지 설정이 필수적이다.
① 애플리케이션 레벨: SIGTERM 핸들링
애플리케이션 코드는 SIGTERM 신호를 받으면 즉시 꺼지지 않고, 새로운 요청은 거절하되(Readiness Probe Fail), 이미 들어온 요청은 다 처리할 때까지 기다려야 한다.
② 인프라 레벨: preStop Hook (핵심)
애플리케이션이 종료 준비를 하기도 전에 트래픽이 들어오는 것을 막기 위해, SIGTERM을 받기 전 잠시 대기하는 시간을 번다
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
이렇게 sleep 10을 주면, kubelet은 종료 신호를 보내기 전에 10초를 기다린다. 이 10초 동안 로드밸런서(service/ingress)의 Endpoint 갱신이 완료되어 트래픽이 더 이상 이 Pod로 오지 않게 된다. 그 후 안전하게 종료 절차를 밟는다.
안전한 종료를 위해서는 다음 3박자가 맞아야한다.
단순히 "설정값을 넣는다"가 아니라, "트래픽이 끊기는 시점과 앱이 꺼지는 시점의 차이(Race Condition)를 없애는 것"이 본질임을 잊지 말자.