Node.js 컨테이너, Graceful Shutdown, 똑똑하게 구현하는 법

by DD
4개월 전
조회수 8

Node.js 컨테이너에서 SIGTERM 신호를 제대로 처리하지 못하는 근본 원인을 분석하며, PID 1 프로세스의 특별한 보호 메커니즘을 지적함

dumb-init 도입을 통해 컨테이너 환경에서 시그널 전달 및 좀비 프로세스 정리 문제를 해결하고, 안정적인 종료(Graceful Shutdown)를 보장함

이벤트 루프(Event Loop)에 남아있는 비동기 작업으로 인해 타임아웃 후에도 프로세스가 종료되지 않는 문제를 파악하고, 종료 훅(Shutdown Hook)과 타임아웃 설정을 통해 해결

애플리케이션과 인프라의 책임 분담을 통해, Kubernetes 종료 정책(Kubernetes Termination Policy)에 맞춰 graceful shutdown을 구현하고, 과도한 코드 복잡도를 지양함

PID 1 프로세스와 시그널 처리의 함정

본문에서는 Node.js 컨테이너에서 SIGTERM 시그널(Signal)을 처리하지 못하는 원인으로, 리눅스 커널의 PID 1 프로세스 보호 메커니즘을 지목한다.

PID 1 프로세스: 시그널 핸들러가 없으면 시그널을 무시하는 특성으로 인해, dumb-init(dumb-init)과 같은 전문 init 시스템 도입 필요

NestJS 셧다운 훅(Shutdown Hook): `app.enableShutdownHooks()`를 통해 시그널 핸들러 등록 가능하지만, 좀비 프로세스(Zombie Process) 정리는 별도 처리 필요

dumb-init: 컨테이너 환경에서 PID 1로 실행되며, 시그널 전달과 좀비 프로세스 정리를 담당하여 안정적인 종료(Graceful Shutdown)를 지원

결론적으로, 컨테이너 환경에서 Node.js 애플리케이션의 안정적인 종료(Graceful Shutdown)를 위해서는 PID 1 프로세스에 대한 이해와 적절한 init 시스템 선택이 필수적이다.

이벤트 루프(Event Loop)와 타임아웃의 딜레마

글에서는 Node.js 이벤트 루프(Event Loop)의 동작 방식이 graceful shutdown 구현에 미치는 영향을 분석한다.

Promise.race: 타임아웃(Timeout)이 먼저 완료되어 훅 함수가 종료되어도, 이벤트 루프(Event Loop)에 비동기 작업이 남아있으면 프로세스 종료 지연

AbortController 미채택: AbortController(AbortController)를 사용한 강제 중단은 코드 복잡도 증가 및 데이터 정합성(Data Consistency) 문제 발생 가능성

최종 해결책: 셧다운 훅(Shutdown Hook)과 타임아웃 설정, 그리고 Kubernetes 종료 정책(Kubernetes Termination Policy)을 활용하여 애플리케이션과 인프라의 책임을 분담

결과적으로, graceful shutdown 구현 시 이벤트 루프(Event Loop)에 대한 정확한 이해와 함께, 애플리케이션과 인프라 간의 적절한 역할 분담이 중요하다.

dumb-init 도입과 Dockerfile 설정

본문은 dumb-init(dumb-init)을 활용한 Dockerfile 설정을 제시하며, 컨테이너 환경에서의 시그널 처리 방법을 설명한다.

dumb-init 설치: `apt-get update && apt-get install -y dumb-init` 명령어를 통해 dumb-init 설치

ENTRYPOINT 설정: `ENTRYPOINT ["/usr/bin/dumb-init", "--"]`을 사용하여 dumb-init을 PID 1 프로세스로 실행

CMD 설정: `CMD ["node", "dist/main"]`으로 Node.js 애플리케이션 실행

dumb-init을 통해 시그널 전달 문제를 해결하고, 안정적인 종료(Graceful Shutdown)를 보장할 수 있다. 또한, npm start 대신 node dist/main을 직접 실행하여 시그널 전달을 방해하는 요소를 제거한다.

Kubernetes Grace Period 설정과 애플리케이션 타임아웃

글에서는 Kubernetes 환경에서의 graceful shutdown 구현을 위해, 애플리케이션 타임아웃과 Kubernetes Grace Period(Grace Period) 설정을 동기화하는 방법을 제시한다.

애플리케이션 타임아웃: 셧다운 훅(Shutdown Hook) 내에서 `Promise.race`를 사용하여 최대 2분으로 설정

Kubernetes Grace Period: `terminationGracePeriodSeconds`를 애플리케이션 타임아웃보다 길게 설정하여, Pod 종료 시점(Pod Termination)을 제어

설정 동기화: 앱 타임아웃(2분) < K8s Grace Period(3분) 설정을 통해, 애플리케이션이 작업을 마무리할 시간을 충분히 확보

결론적으로, 애플리케이션과 인프라 설정을 적절히 조절하여, 안정적인 종료(Graceful Shutdown)를 구현하고, 데이터 손실(Data Loss)을 방지할 수 있다.

Node.js 컨테이너, 왜 깔끔하게 안 죽을까? (feat. Graceful shutdown)