본문 바로가기

Server Infra/Kubernetes

Container Process 를 잘 종료시켜 보자

728x90
CMD ['sh', '-c', 'while true; do echo "Waiting for fortune service to come up...”’]

우리가 컨테이너를 생성하고 내부 프로세스를 실행시킬때 보통 위와 같은 형태로 Dockerfile을 만들것 같다. 하지만 이건 생각보다 큰 문제를 발생시킨다.

 

우선 Process의 SIGTERM과 SIGKILL에 대해서 먼저 알 필요가 있다. 이건 사실 리눅스의 kill 명령어를 자세히 보았다면 알 수 있다.

	 1       HUP (hang up)
     2       INT (interrupt)
     3       QUIT (quit)
     6       ABRT (abort)
     9       KILL (non-catchable, non-ignorable kill)
     14      ALRM (alarm clock)
     15      TERM (software termination signal)

위가 보통 자주 사용하는 kill -n 의 옵션인데 

SIGKILL은 kill-9를

SIGTERM은 kill -15를 의미한다.

 

이 둘은 각각 프로세스를 강제 종료(SIGKILL)하거나 프로세스에게 종료 신호(SIGTERM)을 보내는 역할을 한다. 자 그러면 컨테이너는 종료시 어떻게 되느냐...보통 아래와 같이 수행된다(대다수의 컨테이너 오케스트레이션은 아래와 같은 프로세스 후에 종료가 이루어진다.

여기서 저 SIGTERM을 잘 받아 넘기면 문제가 없다. 

위 그림에서 보는것처럼 일반적인 리눅스라면 첫번째 도식과 같이 init이 이루어지고 sshd를 통해 실행된 nginx를 중지하기위한 SIGTERM을 유연하게 넘겨준다.예를들어서 ssh session을 끊어서 해당 pid가 사라졌다면 pid1인 init이 pid16인 nginx를 자식으로 거두어 처리하도록 되어있다.

리눅스의 Process는 init 프로세스와 함께 시작됩니다. Container의 프로세스를 sh 커멘드를 통해 실행시키는 경우 init(1), sh(2), main_process(3) 형태로 PID가 부여되며 부모 프로세스가 자식 프로세스를 reaping하여 관리합니다. 따라서, 아래와 같이 의도치 않은 프로세스의 종료로 좀비 프로세스가 발생하면 고아 상태의 자식 프로세스를 거두는 것(adopt)으로 좀비 프로세스를 방지하거나 정리합니다.

 

하지만 Container는 이 자식 프로세스를 거두지 못한다. 왜냐하면 저건 init 프로세스가 하는 일인데 container는 application의 수행이 pid1을 가져가기 때문...

 

예를들어서

docker run ( on the host machine)
- /bin/sh (PID 1, inside container)
    - java –jar spring-boot-sample.jar(PID 2, inside container)

위와같이 sh를 통해 java를 실행시키면 sh가 pid1을 가져가고 java가 pid2를 가져간다.

 

/bin/sh 통해 프로세스를 실행시키는 경우 사실상 메인 프로세스인 2번 프로세스에 signal을 보내는 것이 불가능에 가깝다. 쉘 신호는 하위 프로세스에 전달되지 않기 때문에 Container 종료하기 위해 SIGKILL을 보내야한다. 따라서 spring-boot-sample은 정상적으로 트랜젝션을 안전하게 종료하지 못하고 강제로 꺼질수 있다.

 

그렇다면 이렇게 하면 어떨까?

docker run ( on the host machine)
- java –jar spring-boot-sample.jar(PID 1, inside container)

이런경우 만약 spring의 아래의 설정이 없다면 문제가 발생할 것이다.

server:
  shutdown: graceful

 

이처럼 Container에서 동작하는 어플리케이션은 SIGTERM을 받을수 있도록 별도의 처리를 해 주어야 한다. 하지만 그렇게 동작 시키지 못하는 경우가 있다면 어떻게 할까?

 

위와 같은 상황에서 PID1TERM 신호에 대해 handler 등록 되지 않은 경우 자식 프로세스의 문제로 인해 좀비 프로세스가 생기며 SIGKILL이 발생하고 프로세스가 정상적으로 정리되지 않고 종료 될 수 있어 이를 해결하기 위해 dump-init을 통해 모든 signalsignal handler로 등록하고 signal을 프로세스 세션으로 전달할 수 있다..(signal propagation)

docker run ( on the host machine)
- dumb-init (PID 1, inside container)
  - java –jar spring-boot-sample.jar(PID 2, inside container)

 

이렇게 하면 dumb-init이 SIGTERM 신호를 받고 java에 시그널은 안전하게 전달할 수 있다. 이걸 Dockerfile로 만든다면 아래와 같다.

# Builder
# ....

# Runner
FROM scratch
RUN apt install -y dump-init

ENTRYPOINT ["/usr/bin/dumb-init","--","/app/run.sh"]

 

728x90