使用 docker stop 关闭容器时, 只有 init(pid 1)进程能收到中断信号, 如果容器的pid 1 进程是 sh 进程, 它不具备转发结束信号到它的子进程的能力, 所以我们真正的java程序得不到中断信号, 也就不能实现优雅关闭. 解决思路是: 让pid 1 进程具备转发终止信号, 或者将 java 程序配成 pid 1 进程.需要说明的是, docker stop 默认是等待10秒钟, 这个时间有点太短了, 可以加 -t 参数, 比如 -t 30 等待30秒钟.上面的 Dockerfile 的pid 1是一个 sh 命令,并不能实现优雅关闭, 需要再改进.
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 子进程, 不会出现因孤儿进程导致的线程句柄无法回收情形.详见: https://github.com/krallin/tini
方案3(推荐): 在docker镜像中强制 tini 作为 init(pid 1) 进程, 该方案使用范围广, ENTRYPOINT 可以是任意sh脚本文件.
改造之前的 Dockerfile
ENTRYPOINT ["/docker-entrypoint.sh"]
改造后的 Dockerfile
# Add Tini
ENV TINI_VERSION v0.18.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
ENTRYPOINT ["/usr/local/bin/tini", "--", "/docker-entrypoint.sh"]
tini 文档:https://github.com/krallin/tini。有关 docker run --init 参数的说明:http://stackoverflow.com/a/39593409/6309