본문으로 건너뛰기
개발 머꼬
개발 노트Docker
hohyeon.dev18

docker stop이 10초를 기다렸다가 컨테이너를 강제 종료한 이유

  • #DevOps
  • #Docker
  • #Engineering Note

문제 발생

배포 때마다 컨테이너 종료가 정확히 10초씩 걸렸고, 처리 중이던 요청이 끊겼습니다. 앱에는 graceful shutdown 코드가 분명히 있었습니다.

CMD node server.js
$ time docker stop app
app
real    0m10.3s

원인 분석

애플리케이션이 SIGTERM을 못 받았습니다. docker stop은 먼저 SIGTERM을 보내고, 유예 시간(기본 10초) 안에 끝나지 않으면 SIGKILL로 죽입니다. 10초가 정확히 걸렸다는 것은 아무도 SIGTERM에 반응하지 않았다는 뜻입니다.

원인은 CMD의 형식입니다. Docker 문서가 명시합니다 — ENTRYPOINT의 shell form은 CMDdocker run 명령행 인자를 무시하며, ENTRYPOINT/bin/sh -c의 하위 명령으로 시작하는데 이 방식은 시그널을 전달하지 않습니다.

즉 PID 1은 sh이고 우리 앱은 그 자식입니다. Docker가 PID 1에 보낸 SIGTERM은 sh가 받고 끝납니다. 문서의 권고도 분명합니다 — ENTRYPOINT는 exec form이 선호되는 형식입니다.

해결 방안

  1. exec form으로 바꿉니다. 대괄호 JSON 배열 형식입니다.
ENTRYPOINT ["node", "server.js"]

이제 앱이 PID 1이 되어 SIGTERM을 직접 받습니다.

  1. 앱에서 SIGTERM을 처리합니다. 형식만 바꾸고 핸들러가 없으면 여전히 즉시 죽습니다.
process.on("SIGTERM", async () => {
  server.close();
  await pool.end();
  process.exit(0);
});
  1. ENTRYPOINT + CMD 조합을 이해하고 씁니다. 문서 설명대로 컨테이너가 항상 같은 실행 파일을 돌린다면 ENTRYPOINTCMD를 함께 쓰는 것을 검토하고, 이때 CMD는 기본 인자가 됩니다 — 그리고 exec form일 때만 docker run의 인자로 덮어쓸 수 있습니다.
ENTRYPOINT ["node"]
CMD ["server.js"]
  1. 환경변수 확장이 필요해 shell이 필요하면 명시적으로 씁니다. exec form은 셸을 거치지 않아 $PORT 같은 확장이 되지 않습니다. 정말 필요하면 ENTRYPOINT ["/bin/sh", "-c", "exec node server.js"]처럼 exec를 붙여 셸이 자기 자리를 앱에 넘기게 합니다.

  2. 좀비 프로세스가 문제라면 init을 씁니다. PID 1은 고아 프로세스를 거두는 책임도 집니다. 자식 프로세스를 만드는 앱이라면 docker run --init이나 tini를 검토합니다.

  3. 실제로 측정해서 확인합니다. time docker stop이 유예 시간과 정확히 같으면 시그널이 안 닿고 있다는 신호입니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.