当前位置: 首页 > 知识库问答 >
问题:

如何在docker中将bash作为PID1复制僵尸进程?

郭均
2023-03-14

我有一个在PID1运行bash的Docker容器,它反过来运行一个长时间运行的(复杂的)服务,该服务有时会产生PID1 bash的僵尸进程。这些僵尸似乎永远不会被收割。

我试图在一个最小的容器中重现这个问题,以便测试缓解措施,例如使用适当的init作为PID1而不是bash。

然而,我一直无法重现僵尸进程。PID1的重击似乎收获了孩子,甚至是从另一个进程继承下来的孩子。

以下是我尝试过的:

docker run -d ubuntu:14.04 bash -c \
  'bash -c "start-stop-daemon --background --start --pidfile /tmp/sleep.pid --exec /bin/sleep -- 30; sleep 300"'

我的期望是,start-stop-daemon将双叉创建一个进程,该进程在PID1处作为bash的父进程,然后执行到sleep 30,当睡眠退出时,我期望该进程仍然是一个僵尸。sleep300模拟长期运行的服务。

但是,bash获取了该进程,我可以通过在bash进程上运行strace(从运行docker的主机上)观察到:

$ sudo strace -p 2051
strace: Process 2051 attached
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 9
wait4(-1,

我正在运行docker 1.11.1-rc1,尽管我对docker 1.9有相同的经验。

$ docker --version
Docker version 1.11.1-rc1, build c90c70c
$ uname -r
4.4.8-boot2docker

鉴于strace显示bash收获(孤儿)孩子,bash是docker容器中合适的PID1吗?还有什么可能导致我在更复杂的容器中看到的僵尸?我如何繁殖?

编辑:

我设法将strace附加到一个显示问题的活动容器上的bash PID1。

Process 20381 attached
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 11185
wait4(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGTERM}], 0, NULL) = 11191
wait4(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGTERM}], 0, NULL) = 11203
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 11155
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 11151
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 11152
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 11154
wait4(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGTERM}], 0, NULL) = 11332
...

不确定所有这些退出进程是什么,但没有一个PID与docker exec$id ps aux | grep defunct中显示的少数已失效的僵尸进程相匹配。

也许诀窍是在实际操作中捕捉它,并查看在仍然是僵尸的进程上wait4()返回的内容。。。

共有3个答案

熊锐进
2023-03-14

要测试您的应用程序是否离开了僵尸,您需要确保bash不是PID 1,而是PID 1的第一个子。

关于如何使用bash在docker容器中获取僵尸进程的另一个问题,我已经演示了如何使用bash创建一个容器,该容器将通过变为PID 1并作为子级执行bash来忽略僵尸进程。以下是可用于生成容器的c代码:

#include <stdlib.h>

int main() {
    int status;
    status  = system("/bin/bash");
}

可以在github存储库中找到为容器生成zombie和dockerfile的代码

在图像中编译模块后,您所需要做的就是用docker run-ti--rm图像 /zombie/ignore启动容器,您将获得第一个子级的bash。要在实践中看到这一点,请检查与另一个问题的链接。

root@1bd66ac87f0a:/zombie# ps -eaf --forest
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 11:17 pts/0    00:00:00 /zombie/ignore
root           7       1  0 11:17 pts/0    00:00:00 sh -c /bin/bash
管峻
2023-03-14

我在尝试在一个容器中创建一个僵尸进程时遇到了相同的问题,该容器的bash为PID 1。事实证明(正如您从wait4()调用中所看到的那样,bash实际上是在一个紧密的循环中等待所有的子项(man wait解释了当任何子项退出时,等待-1将返回)。

这意味着当孤儿被重新出租给bash时,bash将正确地等待它,以防止它成为僵尸。很奇怪,互联网上的所有文献都不这么说。

傅朗
2023-03-14

我还想验证我的jenkins容器奴隶是否可以生成僵尸。

由于我的映像运行scl二进制文件,从而启动java JLNP客户端,因此我在jenkins slave groovy脚本控制台中执行了以下操作:

def process=new ProcessBuilder("bash", '-c', 'sleep 10 </dev/null &>/dev/null & disown').redirectErrorStream(true).start()
println process.inputStream.text
println " ps -ef".execute().text

僵尸已经生成。也就是说,scl以PID 1结束。

然后我看了你的问题,决定试试bash。我的第一次尝试是将入口点更改为:

bash -c "/usr/bin/scl enable rh-ror42 -- /usr/local/bin/run-jnlp-client $1 $2" --

然后查看ps输出,我意识到PID 1不是bash,但实际上PID 1仍然是scl二进制文件。最后将命令更改为:

bash-c"/usr/bin/scl启用rh-ror42-- /usr/local/bin/run-jnlp-client1美元2美元;ls"--

这就是在scl命令之后添加一些随机的第二个命令。瞧,bash变成了PID 1,僵尸不再生成。

看看您的示例,我看到您使用多个命令运行bash-c。因此,在您的测试床上,您运行的是类似于我的最后一个命令的东西。但是在您的工作容器中,您很可能只使用一个命令运行bash-c,而bash似乎变得足够聪明,可以有效地执行exec。可能在生成僵尸的工作容器中,bash实际上并不是与您预期相反的PID 1。

也许您可以在现有的工作容器中ps-ef,验证我的猜测是否正确。

 类似资料:
  • 最近,我正在研究dumb init,如果我正确认识到它正试图: 作为PID1运行,就像一个简单的初始化系统(收获僵尸进程) 信号代理/转发(bash不做) 在这里和这里,他们都提到,能够捕获僵尸进程,所以我试图验证这一点,但无法使其工作。 首先,我写了一个简单的围棋程序,它产生了10个僵尸进程: 为它建立一个形象: 如果我运行

  • 僵尸进程 当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。 一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。 理解了孤儿进程和僵尸进程,我们临时加了守护进程这一小节,守护进程就是后台进程吗?没那么简单。

  • 问题内容: 我在前台启动了我的程序(守护程序),然后用杀死了它,但剩下一个僵尸,无法用杀死它。如何杀死僵尸进程? 如果僵尸是一个死进程(已被杀死),我如何将其从输出中删除? 问题答案: 僵尸已经死了,所以您无法杀死它。要清理僵尸,必须等待其父级等待,因此杀死父级应该可以消除僵尸。(父对象死后,僵尸将被pid 1继承,而pid 1将等待该僵尸并清除其在进程表中的条目。)如果守护程序正在生成成为僵尸的

  • 问题内容: 维基百科说:“一个终止但从未被其父级等待的子进程变成了僵尸进程。” 我运行此程序: 这会创建一个僵尸进程,但我不明白为什么在这里创建了僵尸进程? 该程序的输出是 但是在这种情况下,为什么“子进程终止但没有被其父进程等待”呢? 问题答案: 在您的代码中,创建了僵尸(带有以下箭头的注释): 为什么?因为你从来没有上过。调用时,它将返回有关进程的事后信息,例如其退出代码。不幸的是,当进程退出

  • 我有两份工作。我的主要第一个jenkins项目使用“trigger/call builds on other project”插件触发另一个第二个项目。我的第二个项目是一种服务器,我首先使用触发器和我的主要第一个项目流程启动它。现在我想在我的第一个项目构建完成后停止我的第二个僵尸项目。 我找到了一些参考资料,如下所示:- 如何停止不可阻挡的僵尸工作Jenkins不重启服务器? 但我想停止我的僵尸工

  • 问题内容: 我在Go中有一个应用程序,它可以重新路由二进制文件的STDIN和STDOUT,然后运行它们。简而言之,我正在做: 我注意到,只要在运行命令A时退出命令B的进程,它就会在进程表中变成僵尸进程。 这是一个例子: 如果commandB仍在运行时退出,为什么commandB会变成僵尸?我在Ubuntu 14上运行Go 1.5。 问题答案: 当某个进程退出时,无论正在运行什么其他进程,它 总是