컨테이너 용어 사전
런타임init · tini · 좀비 프로세스 · SIGTERM

PID 1

컨테이너 안 프로세스는 PID 1이 된다. 핸들러가 없으면 SIGTERM이 무시된다.

컨테이너 안에서 우리 프로세스는 PID 1 이 된다. 리눅스에서 PID 1 은 특별하다.

  • 일반 프로세스 — 시그널 핸들러가 없으면 기본 동작이 적용된다

    • SIGTERM → 종료
  • PID 1 — 기본 동작이 적용되지 않는다

    • 핸들러를 등록하지 않으면 SIGTERM 을 '무시' 한다
    • docker stop 이 안 먹고 10초 뒤 SIGKILL 로 강제 종료
$ docker stop web
# 10초를 기다렸다가 강제 종료된다 — 종료 처리가 로그에 안 남는다

셋 중 하나로 푼다

# ① exec form — 애플리케이션이 직접 PID 1 이 된다
ENTRYPOINT ["java", "-jar", "/app.jar"]

# ② init 프로세스를 넣는다
ENTRYPOINT ["/usr/bin/tini", "--", "java", "-jar", "/app.jar"]
# ③ 런타임 옵션
docker run --init myapp

좀비 프로세스도 PID 1 의 일이다

자식 프로세스가 끝나면 부모가 wait 으로 거둬야 한다 거두지 않으면 좀비로 남는다

원래는 PID 1(init)이 고아가 된 프로세스를 입양해 거둬 준다 그런데 컨테이너의 PID 1 은 우리 애플리케이션이고, 대개 남의 자식을 거두는 코드가 없다

  • 좀비가 쌓인다 → cgroup 의 pids 한도 도달 → fork 실패
  • 증상: "resource temporarily unavailable" · 스레드 생성 실패

자식 프로세스를 만드는 컨테이너(셸 스크립트 · 감시 프로세스)는 tini 같은 최소 init 을 반드시 넣는다

종료 절차

docker stop

  • ① SIGTERM 을 PID 1 에게 보낸다
  • ② 유예 시간을 기다린다 (기본 10초, -t 로 조정)
  • ③ 여전히 살아 있으면 SIGKILL

쿠버네티스도 같다 — terminationGracePeriodSeconds (기본 30초)

  • 유예 시간이 처리 시간보다 짧으면 결국 잘린다
  • 가장 긴 요청의 처리 시간 + 여유로 잡는다

시그널 자체를 바꿀 수도 있다

STOPSIGNAL SIGQUIT

nginx 는 SIGTERM 이 '즉시 종료' 이고 SIGQUIT 가 '우아한 종료' 다 그대로 두면 배포마다 진행 중 요청이 끊긴다

애플리케이션이 어떤 시그널을 어떻게 처리하는지 확인하고 맞춘다

시그널이 실제로 갔는지 확인한다

# 종료에 걸린 시간을 잰다 — 유예 시간을 꽉 채우면 시그널이 안 간 것이다
time docker stop web

# 애플리케이션에 종료 로그가 남는지
docker logs web | tail -20

docker stop 이 항상 10초 걸린다면 SIGTERM 이 무시되고 있다는 뜻이다 정상이면 애플리케이션이 정리를 마치고 바로 끝난다

이 한 번의 확인으로 '무중단이라고 믿는 배포' 를 검증할 수 있다

graceful shutdown 의 전체 순서

  • ① 헬스체크를 실패로 바꾼다 (준비 상태 해제)

  • ② 로드밸런서가 이 인스턴스를 뺄 때까지 기다린다 ← 자주 빠뜨린다

  • ③ 새 요청 수용 중단

  • ④ 처리 중인 요청 완료 대기

  • ⑤ 커넥션 풀·큐 컨슈머 정리

  • ⑥ 종료

  • ② 를 빠뜨리면 LB 가 아직 모르는 상태에서 닫아

  • 그 사이 요청이 전부 실패한다

  • 유예 시간 ≥ ② 대기 + ④ 최장 처리 시간 + 여유

면접 함정

  • "SIGTERM을 보내면 종료된다" → PID 1은 핸들러가 없으면 무시한다.
  • "좀비는 메모리를 먹는다" → PID를 먹는다. pids 한도에 걸려 fork가 실패한다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 실행과 운영 — PID 1·볼륨·네트워크·로그