我读过这篇文章:https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/
设置一些上下文:文章是关于容器中僵尸的问题,它试图说服我们这是一个真正的问题。
一般来说,我的感情很复杂。为什么这有关系 ?毕竟,即使万一在变装主机操作系统中的僵尸能够释放/杀死这个僵尸。我们知道容器中的进程是从主机操作系统正常进程的角度来看的(并且通常在容器中的进程是具有一些命名空间和cgroup的正常进程)。
此外,我们还可以找到信息,为了避免僵尸问题,我们应该使用bash -c ...
。为什么?也许,更好的选择是使用--init
?
有人可以尝试解释这些事吗?
答案 0 :(得分:8)
有关init进程为您提供的简短但有用的解释,请查看tini
,这是Docker在您指定--init
使用Tini有几个好处:
- 它可以保护您免受意外创建僵尸进程的软件的攻击,这些软件可能(随着时间推移!)使您的整个系统陷入困境 (并使其无法使用)。
- 确保默认信号处理程序适用于您在Docker镜像中运行的软件。例如,使用Tini,SIGTERM正常 即使您没有明确安装信号,也会终止您的流程 处理程序。
这些问题都会影响容器。容器中的进程仍然是主机上的进程,因此它占用主机上的PID。无论你在容器中运行什么是PID 1,这意味着它必须安装一个信号处理程序来获取该信号。
Bash恰好包含了一个进程收割器,因此在bash -c
下运行命令可以防止僵尸。除非你trap
,否则Bash默认不会将信号处理为PID 1。
要理解的第一件事是init
进程并没有神奇地移除僵尸。 A(普通)init
旨在收获僵尸,当无法等待它们的父进程退出并且僵尸徘徊时。然后,init进程成为僵尸父进程,可以清除它们。
接下来是Docker容器是它们自己的PID命名空间中的一组进程。当容器停止时,该cgroup被清理。在stop
上删除容器中的所有僵尸。他们无法联系到主持人init
。
第三是Docker容器的使用方式。大多数运行一个主要过程,没有别的。如果产生了另一个进程,它通常是该主进程的子进程。所以在父母退出之前,僵尸将存在。然后看第2点。
在容器中运行Node.js,Go或Java app服务器往往不会过分依赖于分叉或产生进程。
运行类似于Jenkins工作程序的东西会产生大量涉及shell的临时作业,这会导致更糟糕的情况。
init进程可以提供的另一个角色是安装信号处理程序,以便从主机发送的信号可以传递到容器进程。 PID 1有点特殊,因为它需要进程监听接收信号。
如果您可以在PID 1进程中安装SIGINT
和SIGTERM
信号处理程序,那么init进程在此处不会添加太多内容。
应在init进程下运行多个进程。当Docker启动时,init管理它们应该如何启动。容器实际上需要什么才能运行"对于它所代表的服务。当容器停止时,应该如何将其传递到每个进程。您可能需要更传统的init系统,s6 via s6-overlay为多进程管理提供了许多有用的容器功能。
特别是当过程是孩子或更远的孩子时。 CI工作者(如Jenkins)示例是第一个想到Java生成命令或生成其他命令的shell的例子。
sleep
就是一个简单的例子。 docker run busybox sleep 60
无法用ctrl-c中断或停止,它将在默认的10秒docker stop
超时后被终止。 docker run --init busybox sleep 60
按预期工作。
tini
是非常小的开销并且被广泛使用,所以为什么不在大多数时间使用--init
?
有关详细信息,请参阅this github comment,其中回答了"为什么?"来自蒂尼的创造者的问题。
答案 1 :(得分:1)
我在" Use of Supervisor in docker"
中引用了该文章自2016年9月和docker 1.12以来,docker run --init
正在通过添加init
进程帮助对抗僵尸进程。
通常解决following issue
我们无法使用
docker start
,因为我们需要传递端口映射和env变量等内容。所以我们使用docker run
但是当upstart向SIGINT
客户端进程发送docker run
时,容器不会死,只有客户端才会死。然后当upstart开始备份时,它已经运行,并且端口映射失败。
在执行脚本中生成子进程时,Docker似乎挂起了。
基本上,您希望docker容器终止所有子进程,以便清理所述子进程使用的资源(端口,文件处理程序......)。