Docker 容器停止过程
对于容器来说,init
系统不是必须的,当你通过命令 docker stop mycontainer
来停止容器时,docker CLI 会将 TERM
信号发送给 mycontainer 的 PID
为 1 的进程。
- 如果 PID 1 是 init 进程 – 那么 PID 1 会将 TERM 信号转发给子进程,然后子进程开始关闭,最后容器终止。
- 如果没有 init 进程 – 那么容器中的应用进程(Dockerfile 中的
ENTRYPOINT
或CMD
指定的应用)就是 PID 1,应用进程直接负责响应TERM
信号。这时又分为两种情况:
应用不处理 SIGTERM – 如果应用没有监听 SIGTERM
信号,或者应用中没有实现处理 SIGTERM
信号的逻辑,应用就不会停止,容器也不会终止。
容器停止时间很长 – 运行命令 docker stop mycontainer
之后,Docker 会等待 10s
,如果 10s
后容器还没有终止,Docker 就会绕过容器应用直接向内核发送 SIGKILL
,内核会强行杀死应用,从而终止容器。
Dockerfile中ENTRYPOINT/CMD 的几种写法, 会影响 pid 1 进程的产生:
写法1:
“shell” format 的 ENTRYPOINT/CMD, 不带方括号:
ENTRYPOINT top -b
#PID 1 是 /bin/sh -c shell top -b
#另外有个 pid 7 是 top -b
写法2:
“shell” format 的 ENTRYPOINT/CMD, 不带方括号, 但这次ENTRYPOINT后紧跟了一个 exec :
ENTRYPOINT exec top -b
#PID 1 是 top -b
写法3:
“exec” form 的 ENTRYPOINT/CMD, 方括号括着, 每个部分都是json字符串.
ENTRYPOINT [“top”,”-b”]
pid 1 进程就是 top -b
所以推荐使用”exec” form的命令, 而不是 “shell” 形式的命令.
init 进程调整方案
方案1: 自行确保 pid 1 是我们的java程序.
上面的 Dockerfile 可以确保 java 程序作为 pid 1进程.
方案评价: 有时候不太容易将我们的主程序调整为 pid 1 进程, 另外虽然 docker 容器推荐是单进程, 但实际情形往往不是这么理想. 本方案仅仅适合单进程容器.
方案2: 适合于 Docker 1.13 以上.
Docker 1.13以上的docker run 命令新增了 –init 参数, 加了该参数后, docker 会启用 tini 作为 init (pid 1) 进程, 该 tini 进程能够将终止信号转发给其子进程, 同时能reap 子进程, 不会出现因孤儿进程导致的线程句柄无法回收情形.
方案3(推荐): 在docker镜像中强制 tini 作为 init(pid 1) 进程, 该方案使用范围广, ENTRYPOINT 可以是任意sh脚本文件.
# Add Tini
ENV TINI_VERSION v0.18.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /usr/local/bin/tini
RUN chmod +x /usr/local/bin/tini
ENTRYPOINT ["/usr/local/bin/tini", "--", "/docker-entrypoint.sh"]
参考文献:
https://xie.infoq.cn/article/11d413217d5186feed013122e
https://bbs.huaweicloud.com/blogs/158125