본문 바로가기
AI Journey/클라우드

[Docker] 컨테이너 생명주기(lifecycle)와 PID 1 프로세스 아키텍처

by 보눔비스타 2025. 12. 29.

Docker 컨테이너는 독자적인 운영체제가 아니라, 호스트 OS 커널 위에서 격리된 '프로세스 트리(Process Tree)'일 뿐이다. 이 트리의 최상단에 위치한 PID 1 프로세스는 컨테이너의 생존을 결정하는 절대적인 기준이 된다.

 

다음 명령을 예시로 들어보자. 

 

# Case A: Batch Job (일회성 작업) - 즉시 종료
docker run ubuntu:latest echo "Hello world..."
Hello world...
# 결과: 프로세스가 할 일을 마치고 Exit Code 0을 반환하며 종료됨 -> 컨테이너 중지

# Case B: Blocking Process (지속 실행) - 영구 실행
docker run -d nginx:latest tail -f /dev/null
# 결과: 프로세스가 파일의 끝을 기다리며(Blocking) 종료되지 않음 -> 컨테이너 실행 유지 (`Up`)

 

PID 1의 역할과 컨테이너 수명 결정 로직

일반적인 리눅스 OS와 Docker 컨테이너의 결정적인 차이는 "PID 1(시스템의 첫 번째 프로세스)이 무엇인가?"와 "커널(Kernel)을 누가 가지고 있는가?" 이 두 가지에 있다.
이 차이가 시스템의 목적과 생명주기를 완전히 다르게 만든다.

 

가장 결정적인 차이는 프로세스 트리의 뿌리인 PID 1번 프로세스가 맡은 역할이다.

A. 일반 리눅스 OS (관리자 모델)

  • PID 1 = Systemd (또는 Init)
  • 역할: 부팅이 되면 커널은 가장 먼저 Systemd라는관리자 프로그램을 실행시킨다.
  • 구조: 사용자가 실행하려는 Nginx, DB, SSH 등은 모두 이 Systemd의 자식이나 손자 프로세스로 실행된다.
  • 특징: Nginx가 죽어도 Systemd는 살아있다. 관리자가 살아있으니 OS는 종료되지 않고, Systemd가 Nginx를 다시 살려내거나 로그를 남긴다. 즉, 특정 앱이 죽는다고 시스템이 죽지 않는다.

B. Docker 컨테이너 (작업자 모델)

  • PID 1 = 실행할 애플리케이션 그 자체 (예: Nginx, Python, Java)
  • 역할: 관리자가 없다. 사용자가 docker run 명령어로 실행시킨 그 프로그램이 곧바로 PID 1이 된다.
  • 구조: Systemd 같은 중간 관리자가 없는 상태다.
  • 특징: Nginx(PID 1)가 죽으면, 시스템을 지탱할 관리자가 없다. 따라서 컨테이너라는 격리 공간 자체가 즉시 셧다운된다. 즉, 앱이 죽으면 시스템(컨테이너)도 죽는다.

1. PID 1  프로세스 의존성(Process Dependency)

리눅스 커널에서 PID 1은 좀비 프로세스를 수거하고 시스템의 시그널을 처리하는 특수한 책임을 진다. 도커 컨테이너 런타임은 다음과 같은 로직으로 수명 주기를 관리한다.

  1. Start: 컨테이너 시작 시, Dockerfile의 ENTRYPOINTCMD, 또는 docker run 뒤에 붙은 명령어(echo, nginx 등)를 PID 1로 할당하여 실행한다.
  2. Monitor: 도커 데몬은 이 PID 1 프로세스의 상태를 지속적으로 모니터링한다.
  3. Terminate:
    • Case A (echo): echo는 문자열 출력이라는 작업을 완료하면 exit(0) 시스템 콜을 호출하여 정상 종료된다. Docker는 PID 1이 소멸된 것을 감지하고, 해당 컨테이너의 상태를 Exited로 변경하며 할당된 리소스(네트워크, 네임스페이스)를 회수한다.
    • Case B (Daemon/Service): Nginx나 MySQL 같은 데몬은 작업을 마치는 개념이 아니라, 무한 루프(Event Loop)를 돌며 요청을 대기(Listen)한다. 프로세스가 끝나지 않으므로 컨테이너는 Up 상태를 유지한다.

2. Blocking Process Strategy (tail -f /dev/null)

위에 예로 든 tail -f /dev/null 명령어는 컨테이너를 인위적으로 살려두기 위한 대표적인 'Block I/O 패턴'이다.

  • 동작 원리:
    • /dev/null: 아무 내용도 없는 특수 문자 장치 파일이다.
    • tail -f: 파일의 끝(End of File)을 계속 추적(Follow)하며 새로운 데이터가 들어오기를 기다린다.
    • 결과: /dev/null에는 영원히 데이터가 추가되지 않으므로, tail 프로세스는 CPU를 거의 쓰지 않는 'Sleep/Wait' 상태로 전환되어 무한 대기한다.
  • 이 기법은 실제 서비스를 띄우기 전 컨테이너 내부 쉘에 접속(exec)하여 디버깅을 하거나, 파드(Pod)를 유지시키기 위한 Sidecar Pattern 등에서 기술적 우회책으로 사용된다.

 

결론: 컨테이너가 살아서 유지되려면 PID 1 프로세스가 포그라운드(Foreground)에서 블로킹(Blocking) 되거나, 이벤트 루프(Event Loop)를 도는 상태여야만 한다. 단순히 백그라운드로 실행 후 종료되는 스크립트는 컨테이너를 유지시킬 수 없다.