Docker系列(四)-容器关闭讨论
我主要参考了这篇博客: Trapping signals in Docker containers, 推荐大家 有任何问题应该先参考官方stop文档与官方kill文档.
stop与kill操作
stop操作比较简单, 是向容器中的主进程发送SIGTERM信号, 在规定时间如果没有关闭 则发送SIGKILL信号.
docker的kill操作比是类似Linux的kill操作, 可以发送各种信号到容器中. 比如SIGHUP一般是指定应用重载:
docker kill --signal=SIGHUP my_container
为什么要讨论stop或是kill操作呢?
官方文档的说明中有这么一句The main process inside the container
被发送信号,
主进程是docker容器中PID为1的进程, 那其他非主进程怎么办呢?
我们先来创建这样一个容器
1 | // main.js |
1 |
|
1 | FROM node:10.17.0-alpine |
现在让我们启动容器之后, exec进入容器, 查看一下进程.
1 | docker exec -ti my-kill /bin/sh |
可以看下kill之后的两种结果:
1 | # 使用了`ENTRYPOINT ["node", "main.js"]` |
1 | # 使用了`ENTRYPOINT ["/run.sh"]` |
如果你直接使用
ENTRYPOINT ["node", "main.js"]
时, 应用会收到SIGTERM信号并 会由程序自己关闭.
当你发送DockerStop命令时
docker stop 37594c3a1250
, 应用会等待10s, 而后 无声的退出, 这表明了一件事,node main.js
这个程序收到的是SIGKILL, 它意味着程序必须被强制关闭, 也就是说程序无法做任何事, 只能直接关闭.
结合官方文档来理解, 该现象出现的原因是stop发送信号时, 只会向主进程发送, 在这里也就是进程号为1的这个脚本进程. 但不幸的是, 这个脚本没有将信号传递给 它的子进程, 导致子进程无法收到SIGTERM信号, 最终被发SIGKILL关闭.
如何优雅的关闭容器
为什么出现子进程没收到SIGTERM信号呢? 原因是主进程没有传递过去? 既然如此, 我们 想要优雅的关闭程序, 那么你所需要的就是主进程收到了信号后传递给子进程. 有两种方法分享给大家:
shell脚本优化
既然简单的shell脚本无法实现, 我们可以考虑把为shell脚本增加传递信号的功能:
下面脚本截取自program.sh(只能处理单进程):
1 |
|
使用supervisor或是S6这样的进程管理工具
这里是S6官网.
这些工具有个特点就是能够管理子进程, 像是发送信号这样的功能是完全可以做到的.
总结
通过上面的例子和文档, 我想表明的是, Docker设计的初衷是每个容器仅运行一个进程, 这样Docker可以很好的管理它. 这篇文章的目的不是想要推荐大家去写奇怪的shell脚本, 也不是在Docker中运行多进程, 只是为了表明Docker有运行多进程的能力, 但是请不要假定Docker会帮你管理所有子进程.
请在合理的位置使用多进程, 或是尝试下kubernetes, 它的pods可以由多个container组成.