-
우아한 종료(graceful shutdown) 는 애플리케이션이 종료 신호를 받은 뒤 새 요청을 막고, 현재 요청을 마치고, 리소스를 정리하는 절차로 구성
- Go에서는 os/signal 패키지를 사용해 SIGINT, SIGTERM 같은 종료 신호를 직접 처리할 수 있으며, signal.NotifyContext를 이용해 context 기반 종료 제어도 가능함
- HTTP 서버 종료 시에는 Server.Shutdown() 호출 전 readiness probe 실패를 통해 트래픽을 차단하고, 몇 초 대기한 뒤 shutdown을 수행하는 것이 안정적임
-
모든 핸들러는 context 종료 신호를 감지하고 종료 가능해야 하며, BaseContext 또는 middleware를 통해 이를 통합적으로 처리할 수 있음
- 종료 신호 수신 후 데이터베이스, 메시지 브로커, 캐시 등 외부 리소스는 의도적으로 정리해야 하며, defer로 등록하면 종료 순서 관리가 쉬움
Graceful Shutdown이란?
- 우아한 종료는 애플리케이션이 종료될 때 새로운 요청 차단, 진행 중 요청 완료 대기, 리소스 정리를 거치는 프로세스임
- 이 글은 주로 HTTP 서버와 컨테이너 환경을 다루지만, 모든 애플리케이션에 적용 가능한 개념임
1. 종료 신호 처리
- Unix 계열 시스템에서는 SIGTERM, SIGINT, SIGHUP 등이 종료 신호로 사용됨
- Go 런타임은 SIGTERM, SIGINT 수신 시 기본적으로 애플리케이션을 종료하지만, os/signal.Notify로 직접 처리 가능함
-
버퍼링된 채널(용량 1) 을 사용하면 초기화 중 신호 유실 방지 가능
- Go 1.16 이후에는 signal.NotifyContext를 사용해 context 기반 신호 제어가 간편해짐
2. 종료 시간 인식
- Kubernetes에서는 기본적으로 30초의 종료 유예 기간이 주어짐 (terminationGracePeriodSeconds)
- 안전하게 종료하려면 20% 여유를 두고 25초 이내에 종료 작업을 완료하는 것이 바람직함
3. 새 요청 받기 중단
-
http.Server.Shutdown()은 새 연결을 차단하고 기존 요청이 완료될 때까지 대기함
- Kubernetes 환경에서는 readiness probe를 먼저 실패하게 만들어 트래픽 유입을 차단한 뒤 약간 대기 후 shutdown 수행
- readiness 핸들러에서는 전역 변수로 종료 상태를 판단하여 HTTP 503 반환하도록 구성 가능함
4. 요청 처리 마무리
- 종료를 위한 context에 적절한 timeout 설정 필요 (context.WithTimeout)
- shutdown context가 만료되면 남은 연결은 강제 종료됨
- 모든 핸들러는 context.Context를 활용해 종료 신호를 감지하고 중단 가능하도록 설계해야 함
- 이를 위해 middleware나 BaseContext를 통해 모든 요청에 종료 context를 주입할 수 있음
5. 리소스 정리
- 종료 신호를 받았다고 바로 리소스를 닫으면 처리 중인 핸들러에 문제 발생 가능
- shutdown이 완료된 뒤에 데이터베이스 연결, 메시지 브로커, 캐시 등을 정리해야 함
- Go의 defer를 활용하면 초기화 역순으로 종료 루틴 실행 가능하여 의존성 관리가 쉬움
- 메모리, 파일 디스크립터 등 OS가 자동으로 정리하는 리소스 외에도 데이터 flush, 트랜잭션 rollback 등 명시적 종료가 필요한 리소스 존재함
전체 예제 요약
-
signal.NotifyContext로 종료 신호 수신
-
/healthz readiness 엔드포인트 구현
-
BaseContext로 모든 요청에 종료 context 주입
- readiness 5초 대기 후 shutdown 수행
-
server.Shutdown 호출 실패 시 강제 종료 fallback 포함
참고 문헌 및 관련 리소스