容器中的1号进程

Linux系统中,PID为1的进程扮演了十分重要的角色,在容器兴起后,因为其秉承的原则是「one process per container」或「one thing per container」,这个时候谁来当容器内的1号进程就是一个需要回答的问题。
一般来说,容器内的进程可能有这么几种情况:

  1. 只有一个进程个,就是1号进程
  2. 1号进程+由它派生出来的整个进程树
对Docker 停止一个容器而言, 其发送stop命令时, 本质上就是发送一个SIGTERM信号给容器内的1号进程, 然后1号进程接收信号后退出, 如果有子进程,那么也需要一并退出.
对于情况1还好,只有1个进程,没有那么多幺蛾子。对于情况2,如果1号进程能够准确地将停止信号转发给所有子进程,并能顺利结束整个进程树,这样也还OK。
问题出在:
  1. 很多人的容器启动命令是用shell脚本启动的( 现在你可以进入业务容器内用 ps 看一下进程树 )
  2. 很多的shell脚本没有采用Docker所推荐的exec形式
这就导致了:
  1. bash进程实际上是容器内的1号进程, 你的业务进程实际上是bash的子进程
  2. bash进程不能转发信号
这就导致了:业务进程没法收到停止的信号,直接等待Docker在过了超时时间后强行将整个容器停掉。
这会导致什么问题呢?
假设:
  1. 业务进程会向某种注册中心注册( 这不要太常见,服务注册基本是标配 )
  2. K8S管理着容器的编排调度,那么在K8S的语境下,一个容器是会被随时kill或者重启的,kill无非也是调用底层容器引擎的命令( 不光是K8S,其他容器编排工具也会存在类似问题 )
    结果是:想下线一个服务,那么使用K8S kill对应容器,然后返回了。因为你的启动命令是shell包裹的,所以bash是1号进程,它无法转发终止信号给业务进程,所以容器内的业务进程需要等待超时时间结束之后才会被强行停止(与你「进程立刻结束」的期望不符),所以导致想下线的服务迟迟无法从注册中心下线。
【容器中的1号进程】怎么解决呢?
有几种方案:
  1. 使用 Shell exec 的形式,使你的业务进程就是1号进程。PS:可以参考Redis或MySQL等的docker-entrypoint.sh是如何写的
  2. 使用类似tini或dumb-tini的工具,充当1号进程,它实现了转发功能。PS:dumb-tini还有坑,可以网上搜索。类似的可以看看Jenkins的启动脚本是怎么编写的
    其中方案1是最好的。

    推荐阅读