# Go 언어에서 Graceful Shutdown을 구현하는 실용적 패턴

> Clean Markdown view of GeekNews topic #20723. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=20723](https://news.hada.io/topic?id=20723)
- GeekNews Markdown: [https://news.hada.io/topic/20723.md](https://news.hada.io/topic/20723.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2025-05-06T10:11:15+09:00
- Updated: 2025-05-06T10:11:15+09:00
- Original source: [victoriametrics.com](https://victoriametrics.com/blog/go-graceful-shutdown/index.html)
- Points: 3
- Comments: 1

## Topic Body

- **우아한 종료(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 포함  
  
### 참고 문헌 및 관련 리소스  
  
- [Go로 작성된 기타 인프라 가이드](https://victoriametrics.com/categories/go-@-victoriametrics)  
- Graceful Shutdown 외에도 [defer 활용법](https://victoriametrics.com/blog/defer-in-go), [Go 배열/슬라이스/맵 동작 원리](https://victoriametrics.com/blog/go-array) 등도 제공됨

## Comments



### Comment 38208

- Author: neo
- Created: 2025-05-06T10:11:15+09:00
- Points: 1

###### [Hacker News 의견](https://news.ycombinator.com/item?id=43889610) 
- Kubernetes에서 로드밸런서 타겟 IP 업데이트가 오래 걸리는 경우가 있음. 90%의 문제는 트래픽이 실제로 드레인되는지 확인하는 것임
  - 글로벌 preStop 훅에 15초 대기 시간을 추가하여 HTTP 503 비율을 크게 개선함
  - 로드밸런서 등록 취소와 SIGTERM 전달 사이에 시간을 만들어 애플리케이션 처리 간소화함

- `log.Fatal` 사용 시 `defer` 안의 내용이 실행되지 않음
  - `log.Fatal`은 `os.Exit`를 호출하여 즉시 종료함
  - `panic`을 사용하면 `defer` 내용이 실행됨

- Prometheus `/metrics` 엔드포인트가 주기적으로 스크랩될 때, 마지막 스크랩과 프로세스 종료 사이에 기록된 메트릭이 전파되지 않을 수 있음
  - 서비스 종료 시 마지막 몇 초의 로그를 잃을 수 있음
  - 로그 파일이 사이드카 프로세스에 의해 감시될 때 경합 조건이 발생할 수 있음

- 분산 시스템이 클라이언트의 정상 종료에 의존하면 시스템이 심각하게 고장날 수 있음

- 새로운 서비스 인스턴스가 이전 인스턴스로부터 소켓을 받을 때 연결을 끊지 않고 애플리케이션을 재시작하는 방법에 대한 설명이 부족함
  - systemd에서는 구현이 비교적 간단함
  - nginx는 20년 넘게 이를 지원함
  - Kubernetes와 Docker는 이를 지원하지 않음

- liveness에 대한 논의가 부족함
  - 동일한 엔드포인트를 liveness/readiness에 사용하는 앱이 여러 번 보였음

- 프로그램이 ctrl c와 같은 명령을 깨끗하게 처리하지 못하면 잘못 작성된 것임

- Elixir는 프로세스를 작은 VM 프로세스로 설계하여 정상 종료 루틴을 의도적으로 만들 필요가 없게 함

- 프로젝트에서 정상 종료를 처리하기 위한 작은 라이브러리를 만듦
  - 다양한 시작 및 종료 메커니즘을 가진 서비스를 통합하기 위한 API를 제공함

- readiness probe 업데이트 후 몇 초 기다려 시스템이 새로운 요청을 보내지 않도록 함
  - 종료 중인 pod는 준비 상태가 아님
  - 서비스는 엔드포인트를 종료 중으로 표시함
  - SIGTERM 후에도 작은 창이 있을 수 있지만, 이는 큰 문제가 아님
  - 새로운 연결을 받지 않고 기존 연결을 정상적으로 종료하는 것이 중요함
