Docker와 PID 1(좀비 프로세스)

songtofu·2022년 4월 20일
0

Docker

목록 보기
5/6

1. 배경

Linux 신호는 컨테이너 내부의 프로세스 수명 주기를 제어하는 주요 방법이다. 앱의 수명 주기를 앱이 포함된 컨테이너와 긴밀하게 연결하려면 앱이 Linux 신호를 올바르게 처리하도록 해야 합니다.

프로세스 식별자(PID)는 Linux커널이 각 프로세스에 제공하는 고유한 식별자이다. PID는 namespace입니다. 즉, 컨테이너에는 호스트 시스템의 PID가 매핑되는 고유한 PID세트가 있습니다(??)
Linux 커널을 시작할 때 실행된 첫 번째 프로세스에는 PID 1이 있다. 정상적인 운영체제의 경우 이 프로세스는 init 시스템(ex. systemd 또는 SysV)이다. 마찬가지로 컨테이너에서 실행된 첫 번째 프로세스는 PID 1을 얻는다.

Docker와 Kubernetes는 신호를 사용하여 컨테이너 내부의 프로세스와 통신하며, 특히 컨테이너를 종료하기 위해 사용됩니다. Docker와 Kubernetes는 모두 컨테이너 내부에 PID 1이 있는 프로세스에만 신호를 보낼 수 있습니다.

2. 문제

2.1) Linux 커널이 신호를 처리하는 방법

Linux 커널이 신호를 처리하는 방법은 PID 1을 가진 프로세스와 그렇지 않은 프로세스에서 차이가 있다.
신호 핸들러가 이 프로세스에서 자동으로 등록되지 않으므로 SIGTERM 또는 SIGINT 같은 신호는 기본적으로 아무런 영향을 미치지 않는다. 기본적으로, 단계적 종료를 방지하는 SIGKILL을 사용하여 프로세스를 강제 종료해야한다. 앱에 따라 SIGKILL을 사용하면 모니터링 시스템에 사용자 표시 오류, 쓰기 중단(데이터 저장용), 원치 않는 알림이 발생 할 수 있다.

2.2) 기본 init 시스템이 분리된 프로세스를 처리하는 방법

systemd와 같은 기본 init 시스템은 분리된 좀비 프로세스를 제거하는 데에도 사용된다. 분리된 프로세스(상위 요소가 사라진 프로세스)는 PID 1이 있는 프로세스에 다시 첨부된다. PID 1은 프로세스가 사라질 때 다시 거둬야 한다. 정상적인 init시스템은 그렇게 작동한다. 그러나 컨테이너에서는 PID 1을 갖고 있는 프로세스가 이러한 책임을 갖게 된다. 이 프로세스에서 이런 제거를 제대로 하지 못하면 메모리나 다른 리소스가 부족해질 수 있다.

3. 해결

3.1) PID 1으로 실행하고 신호 핸들러로 등록

  • 첫 번째 문제만 해결
  • 앱이 제어된 방식(흔한 경우)으로 하위 프로세스를 생성하면 두 번째 문제를 방지할 수 있다.
  • 이 솔루션을 구현하는 가장 쉬운 방법은 Dockerfile에서 CMD 또는 ENTRYPOINT 안내를 사용하여 프로세스를 실행하는 것
FROM debian:9

RUN apt-get update && \
    apt-get install -y nginx

EXPOSE 80

CMD [ "nginx", "-g", "daemon off;" ]

경고 : nginx 프로세스는 자체 신호 핸들러를 등록합니다. 대부분의 경우, 이 솔루션을 사용하면 앱 코드에서 같은 작업을 수행해야 합니다.

  • 때로는 프로세스가 제대로 실행될 수 있도록 컨테이너에서 환경을 준비해야 할 수 있다. 이 경우 컨테이너를 시작할 떄 셀 스크립트를 실행하는 것이 가자 ㅇ좋다. 이 셸 스크립트는 환경을 주닙하고 기본 프로세스를 실행하는 작업을 담당한다. 하지만 이 방법을 사용하는 경우 셸 스크립트는 프로세스가 아닌 PID 1을 가지므로 기본 exec 명령어를 사용하여 셸 스크립트에서 프로세스를 실행해야한다. exec 명령어로 스크립트를 원하는 프로그램으로 바꾼다. 그 다음 프로세스에서 PID 1을 상속한다.

3.2) Kubernetes에서 프로세스 네임스페이스 공유 사용 설정 (??)

pod에 프로세스 네임스페이스 공유를 사용 설정하면 Kubernetes는 해당 pod의 모든 컨테이너에 단일 프로세스 네임스페이스를 사용합니다. Kubernetes pod 인프라 컨테이너가 PID 1이 되고 분리된 프로세스는 자동으로 다시 수거(reap)됩니다

3.3) 특수한 init 시스템 사용

기본적인 Linux 환경에서와 마찬가지로 init 시스템을 사용하여 이러한 문제를 처리할 수도 있다. 하지만 systemd 또는 SysV등의 일반 init 시스템은 단지 이 용도로 사용하기에는 너무 복잡하고 크기 때문에 컨테이너용으로 특별히 제작된 tini와 같은 init시스템을 사용하는 것이 좋다.

특수한 init 시스템을 사용하는 경우 init프로세스는 PID 1을 가지며 다음을 수행한다.

  • 올바른 신호 핸들러를 등록
  • 앱에서 신호가 작동하는지 확인
  • 최종 모든 좀비 프로세스를 수거

docker run 명령어의 --init 옵션을 사용하면 Docker 자체에서 이 솔루션을 사용할 수 있습니다. Kubernetes에서 이 솔루션을 사용하려면 컨테이너 이미지에 init 시스템을 설치하고 컨테이너의 진입점으로 사용해야 합니다.

3.3.1) dumb-init 소개

  • Linux 컨테이너에서 사용하기 위한 최소 초기화 시스템인 dumb-init
  • 서버 프로세스를 직접 실행하는 대신 Dockerfile에서 CMD ["dumb-init", "python", "my_server.py"]. 이렇게 하면 다음과 같은 프로세스 트리가 생성
  • dumb-init는 잡을 수 있는 모든 신호에 대해 신호 처리기를 등록하고 해당 신호를 프로세스를 기반으로 하는 세션으로 전달
  • Python 프로세스가 더 이상 PID 1로 실행되지 않기 때문에 dumb-init가 와 같은 신호를 전달할 때 TERM다른 핸들러를 등록하지 않은 경우 커널은 여전히 기본 동작(프로세스 종료)을 적용
  • 일반 초기화 시스템을 사용하면 이러한 문제도 해결되지만 복잡성과 리소스 사용량이 증가합니다. dumb-init는 일을 올바르게 수행하는 더 간단한 방법입니다. 프로세스를 유일한 자식으로 생성하고 이에 대한 신호를 프록시합니다. dump-init는 프로세스가 죽을 때까지 실제로 죽지 않으므로 적절한 정리를 수행할 수 있습니다.
  • dumb-init는 추가 종속성이 없는 정적으로 연결된 바이너리로 배포됩니다. 간단한 초기화 시스템으로 사용하는 것이 이상적이며 일반적으로 모든 컨테이너에 추가할 수 있습니다. 기본적으로 모든 Docker 컨테이너에서 사용하는 것이 좋습니다. dumb-init는 신호 처리를 향상시킬 뿐만 아니라 고아 좀비 프로세스를 수확하는 것과 같은 init 시스템의 다른 기능도 처리합니다.

출처

profile
읽으면 머리에 안들어와서 직접 쓰는 중. 잘못된 부분 지적 대환영

0개의 댓글