본문으로 건너뛰기
서버가 종료되기 전 마지막 요청을 처리하는 모습

# Graceful Shutdown: 배포 중에도 단 하나의 요청도 놓치지 않는 법

Table of Contents

배포할 때마다 “잠깐 에러 날 거예요”라고 말하시나요?

롤링 업데이트(Rolling Update)를 한다고 설정했는데, 막상 배포를 누르면 1~2초 정도 502 Bad GatewayConnection Refused 에러가 발생하는 경우가 많습니다. 개발자들은 보통 “배포 중이라 어쩔 수 없어”라고 넘기지만, 사용자 경험(UX) 관점에서는 치명적입니다. 결제 버튼을 눌렀는데 흰 화면이 뜬다면? 사용자는 돈만 날린 줄 알고 불안해할 겁니다.

이 찰나의 에러를 잡는 기술이 바로 **Graceful Shutdown(우아한 종료)**입니다. 하던 일은 마저 끝내고, 새로운 일은 정중히 거절하면서 퇴근하는 것. 서버에게도 이런 매너가 필요합니다.

파드(Pod)가 죽을 때 벌어지는 일

Kubernetes가 파드를 종료시킬 때, 두 가지 신호를 보냅니다.

  1. SIGTERM: “이제 곧 문 닫을 거니까 정리해~” (경고)
  2. (30초 후) SIGKILL: “아직도 안 끝났어? 강제 종료!” (사형 선고)

문제는 우리가 짠 애플리케이션 코드가 SIGTERM을 받고도 “어? 알았어” 하고 즉시 프로세스를 꺼버린다는 데 있습니다. 아직 처리 중인 요청이 있는데 그냥 꺼지면? 클라이언트는 연결이 끊겨 에러를 보게 됩니다. 더 큰 문제는, 쿠버네티스의 로드밸런서(Service)가 해당 파드로 트래픽을 보내지 않도록 목록에서 빼는 데도 시간이 걸린다는 점입니다. 파드는 이미 죽었는데, 로드밸런서는 계속 그쪽으로 트래픽을 보내는 타이밍 불일치가 발생합니다.

완벽한 종료를 위한 3단계 레시피

1. 애플리케이션에서 SIGTERM 처리하기

코드는 SIGTERM 신호를 받으면 두 가지를 해야 합니다.

  1. 새로운 요청을 받지 않도록 리스너(Listener)를 닫습니다. (Keep-Alive 연결도 종료)
  2. 현재 처리 중인 요청이 다 끝날 때까지 기다립니다. (Connection Draining)

Node.js (Express) 예시:

const server = app.listen(3000);

process.on('SIGTERM', () => {
  console.log('SIGTERM received: closing HTTP server');
  server.close(() => {
    console.log('HTTP server closed');
    // DB 연결 종료 등 리소스 정리
    process.exit(0);
  });
});

2. Kubernetes preStop Hook 활용하기

애플리케이션이 아무리 잘 처신해도, 쿠버네티스 네트워크 설정이 업데이트되는 딜레이를 못 이길 때가 있습니다. 이때 preStop 훅을 써서 “종료 신호를 보내기 전에 잠깐만 기다려줘”라고 할 수 있습니다.

lifecycle:
  preStop:
    exec:
      command: ["/bin/sh", "-c", "sleep 10"]

이렇게 하면 파드가 Terminating 상태가 되고 나서, 실제로 SIGTERM을 앱에 보내기 전에 10초간 대기합니다. 이 시간 동안 로드밸런서 설정이 전파되어 더 이상 새 트래픽이 들어오지 않게 됩니다.

3. Docker 설정 확인 (PID 1 문제)

도커 컨테이너 안에서 앱을 실행할 때 npm start로 실행하면, npm이 PID 1번 프로세스가 되고 실제 앱은 자식 프로세스가 됩니다. 이 경우 SIGTERM 신호가 앱까지 전달되지 않을 수 있습니다. Dockerfile에서는 CMD ["node", "app.js"] 처럼 실행하거나, tini 같은 init 프로세스를 사용하는 것이 좋습니다.

마치며

“배포는 고객이 모르게 해야 한다.” 이것이 진정한 데브옵스의 철학입니다. Graceful Shutdown 설정은 한 번만 제대로 해두면, 낮이나 밤이나 언제든 마음 편하게 배포할 수 있는 자유를 줍니다.

지금 당장 운영 중인 서버의 로그를 확인해보세요. 배포할 때마다 끊겨버린 요청들의 비명소리가 들리지 않나요? 이제 그들에게 “안전하게 퇴근하는 법”을 가르쳐줄 때입니다.

이 글 공유하기:
My avatar

글을 마치며

이 글이 도움이 되었기를 바랍니다. 궁금한 점이나 의견이 있다면 댓글로 남겨주세요.

더 많은 기술 인사이트와 개발 경험을 공유하고 있으니, 다른 포스트도 확인해보세요.

유럽살며 여행하며 코딩하는 노마드의 여정을 함께 나누며, 함께 성장하는 개발자 커뮤니티를 만들어가요! 🚀


관련 포스트

# 분산 시스템 Rate Limiting 완벽 구현 가이드: Redis + Token Bucket으로 API 남용 막기

게시:

프로덕션 환경에서 API 남용과 DDoS 공격을 방어하는 분산 Rate Limiting 시스템 구축 가이드입니다. Redis와 Token Bucket 알고리즘을 활용한 실전 구현, Lua 스크립트로 Race Condition 해결, 그리고 2025년 최신 모범 사례까지 상세히 다룹니다.

읽기