原因

这几天在生产环境发现有几个容器一直不能正常的stop,或者rm 掉,而且查看docker daemon 日志里面会出现很多 msg="Container 5054f failed to exit within 10 seconds of signal 15 - using the force" 这样的报错,使用的命令为journalctl -xe -u docker 然后在短暂的时间内 docker ps查看到的容器还在运行中,过了一会没有了我们在创建的时候会提示这个容器已经存在(如果建立同样名称的容器)

docker stop 主流程

1,docker 通过 containerd 向容器主进程发送 SIGTERM(终止进程)信号后等待一段时间后(默认是10s,可以通过-t 参数来修改),如果从containerd 收到了容器退出消息,那么容器退出成功。 2,如果超过等待的时间之后,还是没收到容器退出的消息,那么docker 将使用docker kill方式试图终止容器。

但是对于容器来说,init 系统进程并不是必须的,所以当我们停止容器的时候,docker 通过 containerd 向容器Pid 为 1 的进程发送 SIGTERM信号并不一定会被采纳。其实可以分为以下两种情况来说明:

1,如果 PID==1 的进程是 init 进程:

那么 PID==1 会将 SIGTERM 信号转发给子进程,然后子进程开始关闭,最后容器终止

2,如果PID==1 的进程不是 init 进程:

那么容器中的应用进程(Dockerfile 中的 ENTRYPOINT 或 CMD 指令指定的应用)的 PId 就是 1,应用进程直接负责响应 SIGTERM 信号。这个时候又分为两种情况

1,应用不处理 SIGTERM 信号:

 应用没有监听 SIGTERM 信号,或者应用中没有事先处理 SIGTERM 信号的逻辑,应用就不会停止,容器也不会正常终止,会被 调用 docker kill 方式杀死(我们的程序目前就是这种)

2,容器停止时间很长:

运行命令 docker stop 之后,docker 会默认等待 10S(默认值,可以修改 docker stop -t 指令),如果 10s后容器还没有终止,docker 就会绕过容器应用直接向内核发送 SIGKILL,内核强行杀死应用,从而终止容器。

docker kill主流程

1,docker 引擎通过containerd 使用 SIGKILL 发向容器主进程,等待一段时间后,如果从containerd收到容器退出消息,那么容器kill成功 2,在上一步中如果等待超时,Docker引擎将跳过 containerd 自己亲自动手通过kill系统调用向容器主进程发送 SIGKILL 信号。如果此时 kill 系统调用返回主进程不存在,那么 Docker Kill 成功。否则引擎将一直死等到 containerd 通过引擎,容器退出.

docker 中 PID 进程不能处理 SIGTERM 信号的危害

上面我们讲到如果容器内的 PID 进程不能处理 SIGTERM 信号的时候,docker 会等 10S(默认时间),然后调用 kill 去杀死容器的进程,其实这样会造成下面两个问题

1,进程不能正常终止

Linux 内核中其实会对 PID 1 进程发送特殊的信号量。一般情况下,当给一个进程发送信号时,内核会先检查是否有用户定义的处理函数,如果没有,就会回退到默认行为。例如使用 SIGTERM 直接杀死进程。然而,如果进程的 PID 是 1,那么内核就会特殊对待它。如果没有注册用户处理函数,内核不会回退到默认行为,什么也不做,换句话说,如果你的进程没有处理信号的函数,给他发送 SIGTERM 会一点效果也没有,这个我们在上面讲过了。

常见的使用是 docker run my-container script. 给 docker run 进程发送SIGTERM 信号会杀掉 docker run 进程,但是容器还在后台运行。

2,孤儿僵尸进程不能正常回收

当进程退出时,它会变成僵尸进程,直到它的父进程调用 wait() ( 或其变种 ) 的系统调用。process table 里面会把它的标记为 defunct 状态。一般情况下,父进程应该立即调用 wait(), 以防僵尸进程时间过长。

如果父进程在子进程之前退出,子进程会变成孤儿进程, 它的父进程会变成 PID 1。因此,init 进程就要对这些进程负责,并在适当的时候调用 wait() 方法。