容器中的1号进程
Linux系统中,PID为1的进程扮演了十分重要的角色,在容器兴起后,因为其秉承的原则是「one process per container」或「one thing per container」,这个时候谁来当容器内的1号进程就是一个需要回答的问题。
一般来说,容器内的进程可能有这么几种情况:
- 只有一个进程个,就是1号进程
- 1号进程+由它派生出来的整个进程树
对于情况1还好,只有1个进程,没有那么多幺蛾子。对于情况2,如果1号进程能够准确地将停止信号转发给所有子进程,并能顺利结束整个进程树,这样也还OK。
问题出在:
- 很多人的容器启动命令是用shell脚本启动的( 现在你可以进入业务容器内用 ps 看一下进程树 )
- 很多的shell脚本没有采用Docker所推荐的exec形式
- bash进程实际上是容器内的1号进程, 你的业务进程实际上是bash的子进程
- bash进程不能转发信号
这会导致什么问题呢?
假设:
- 业务进程会向某种注册中心注册( 这不要太常见,服务注册基本是标配 )
- K8S管理着容器的编排调度,那么在K8S的语境下,一个容器是会被随时kill或者重启的,kill无非也是调用底层容器引擎的命令( 不光是K8S,其他容器编排工具也会存在类似问题 )
结果是:想下线一个服务,那么使用K8S kill对应容器,然后返回了。因为你的启动命令是shell包裹的,所以bash是1号进程,它无法转发终止信号给业务进程,所以容器内的业务进程需要等待超时时间结束之后才会被强行停止(与你「进程立刻结束」的期望不符),所以导致想下线的服务迟迟无法从注册中心下线。
有几种方案:
- 使用 Shell exec 的形式,使你的业务进程就是1号进程。PS:可以参考Redis或MySQL等的docker-entrypoint.sh是如何写的
- 使用类似tini或dumb-tini的工具,充当1号进程,它实现了转发功能。PS:dumb-tini还有坑,可以网上搜索。类似的可以看看Jenkins的启动脚本是怎么编写的
其中方案1是最好的。
推荐阅读
- 热闹中的孤独
- Docker应用:容器间通信与Mariadb数据库主从复制
- JS中的各种宽高度定义及其应用
- 我眼中的佛系经纪人
- 《魔法科高中的劣等生》第26卷(Invasion篇)发售
- Android中的AES加密-下
- 放下心中的偶像包袱吧
- C语言字符函数中的isalnum()和iscntrl()你都知道吗
- C语言浮点函数中的modf和fmod详解
- C语言中的时间函数clock()和time()你都了解吗