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

如何使用bash在docker容器中获取僵尸进程

岳谦
2023-03-14

最近,我正在研究dumb init,如果我正确认识到它正试图:

  1. 作为PID1运行,就像一个简单的初始化系统(收获僵尸进程)
  2. 信号代理/转发(bash不做)

在这里和这里,他们都提到,bash能够捕获僵尸进程,所以我试图验证这一点,但无法使其工作。

首先,我写了一个简单的围棋程序,它产生了10个僵尸进程:

func main() {
    sigs := make(chan os.Signal, 1)

    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)

    go func() {
        for i := 0; i < 10; i++ {
            sleepCmd := exec.Command("sleep", "1")
            _ = sleepCmd.Start()
        }
    }()

    fmt.Println("awaiting signal")
    sig := <-sigs
    fmt.Println()
    fmt.Printf("received %s, exiting\n", sig.String())
}

为它建立一个形象:

FROM golang:1.15-alpine3.12 as builder

WORKDIR /

COPY . .

RUN go build -o main main.go

FROM alpine:3.12

RUN apk --no-cache --update add dumb-init bash

WORKDIR /
COPY --from=builder /main /
COPY --from=builder /entrypoint.sh /
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/main"]

如果我运行docker run-d

vagrant@vagrant:/vagrant/dumb-init$ ps aux | grep sleep
root      4388  0.0  0.0      0     0 ?        Z    13:54   0:00 [sleep] <defunct>
root      4389  0.0  0.0      0     0 ?        Z    13:54   0:00 [sleep] <defunct>
root      4390  0.0  0.0      0     0 ?        Z    13:54   0:00 [sleep] <defunct>
root      4391  0.0  0.0      0     0 ?        Z    13:54   0:00 [sleep] <defunct>
root      4392  0.0  0.0      0     0 ?        Z    13:54   0:00 [sleep] <defunct>
root      4393  0.0  0.0      0     0 ?        Z    13:54   0:00 [sleep] <defunct>
root      4394  0.0  0.0      0     0 ?        Z    13:54   0:00 [sleep] <defunct>
root      4395  0.0  0.0      0     0 ?        Z    13:54   0:00 [sleep] <defunct>
root      4396  0.0  0.0      0     0 ?        Z    13:54   0:00 [sleep] <defunct>
root      4397  0.0  0.0      0     0 ?        Z    13:54   0:00 [sleep] <defunct>

第二步是验证bash实际上能够收获进程,所以我更新了我的docker映像ENTRYPOINT到entrypoint.sh,它只是用bash包装我的程序:

#!/bin/bash

/clever

如果我在容器中运行ps,僵尸进程仍然挂在那里:

/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 {entrypoint.sh} /bin/bash /entrypoint.sh
    7 root      0:00 /clever
   13 root      0:00 [sleep]
   14 root      0:00 [sleep]
   15 root      0:00 [sleep]
   16 root      0:00 [sleep]
   17 root      0:00 [sleep]
   18 root      0:00 [sleep]
   19 root      0:00 [sleep]
   20 root      0:00 [sleep]
   21 root      0:00 [sleep]
   22 root      0:00 [sleep]
   31 root      0:00 /bin/sh
   39 root      0:00 ps

尝试了一些其他方法,但仍然不知道如何正确地收获僵尸过程。

谢谢你的帮助。


共有1个答案

赫连开畅
2023-03-14

我在c中编写了一个小演示,可以帮助证明bash已经收获了僵尸进程,如果他没有收获,情况会是什么样子。

首先解释僵尸进程的定义。僵尸进程是完成工作并生成退出代码的进程。资源由内核保存,等待父级收集退出代码。

要拥有僵尸,父母需要忽略孩子的出口(不要发出等待并忽略SIGCHLD)。

下面的c代码正在创建两个僵尸进程。一个属于主进程,一个属于第一个孩子。

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include <sys/wait.h>
#include <unistd.h>

int main()
{
    printf("Starting Program!\n");

    int pid = fork();
    if (pid == 0)
    {
        pid = fork(); // Create a child zombie
        if (pid == 0) {
            printf("Zombie process %i of the child process\n", getpid());
            exit(10);
        } else {
            printf("Child process %i is running!\n", getpid());
            sleep(10);  // wait 10s
            printf("Child process %i is exiting!\n", getpid());
            exit(0);
        }
    }
    else if (pid > 0)
    {
        pid = fork();
        if (pid == 0) {
            printf("Zombie process %i from the parent process\n", getpid());
        } else {
            printf("Parent process %i...\n", getpid());
            sleep(5);
            printf("Parent process will crash with segmentation failt!\n");
            int* p = 0;
            p = 10;
        }
    }
    else perror("fork()");
    exit(-1);
}

我还创建了一个docker容器来编译文件和子文件。整个项目可在以下git存储库中获得

运行构建和演示后,控制台中将显示以下打印输出:

root@d2d87f4aafbc:/zombie# ./zombie & ps -eaf --forest
[1] 8
Starting Program!
Parent process 8...
Zombie process 11 from the parent process
Child process 10 is running!
Zombie process 12 of the child process
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 10:43 pts/0    00:00:00 /bin/bash
root           8       1  0 10:43 pts/0    00:00:00 ./zombie
root          10       8  0 10:43 pts/0    00:00:00  \_ ./zombie
root          12      10  0 10:43 pts/0    00:00:00  |   \_ [zombie] <defunct>
root          11       8  0 10:43 pts/0    00:00:00  \_ [zombie] <defunct>
root           9       1  0 10:43 pts/0    00:00:00 ps -eaf --forest
root@d2d87f4aafbc:/zombie# Parent process will crash with segmentation failt!
ps -eaf --forest
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 10:43 pts/0    00:00:00 /bin/bash
root          10       1  0 10:43 pts/0    00:00:00 ./zombie
root          12      10  0 10:43 pts/0    00:00:00  \_ [zombie] <defunct>
root          13       1  0 10:43 pts/0    00:00:00 ps -eaf --forest
[1]+  Exit 255                ./zombie
root@d2d87f4aafbc:/zombie# Child process 10 is exiting!
ps -eaf --forest
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 10:43 pts/0    00:00:00 /bin/bash
root          14       1  0 10:43 pts/0    00:00:00 ps -eaf --forest

主进程(PID 8)创建两个子进程。

  • 将创建僵尸子对象(PID 12)并睡眠10秒的子对象(PID 10)。
  • 一个会变成僵尸的孩子(PID 11)

创建进程后,父进程将Hibernate5s并创建分段错误,留下僵尸。

当主进程死亡时,PID 11被bash继承,并被清理(收获)。PID 10仍然在工作(睡眠是进程的一种工作),他被bash单独留下,因为PID 11没有调用等待,PID 12仍然是僵尸。

5秒钟后,PID 11完成睡眠并退出。Bash收获并继承了PID 12,之后Bash收获了PID 12

另一个c应用程序只是作为子进程执行bash,将其保留为PID 1,他将忽略僵尸。

# docker run -ti --rm test /zombie/ignore
root@b9d49363cb57:/zombie# ./zombie & ps -eaf --forest
[1] 10
Starting Program!
Parent process 10...
Zombie process 13 from the parent process
Child process 12 is running!
Zombie process 14 of the child process
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 11:18 pts/0    00:00:00 /zombie/ignore
root           7       1  0 11:18 pts/0    00:00:00 sh -c /bin/bash
root           8       7  0 11:18 pts/0    00:00:00  \_ /bin/bash
root          10       8  0 11:18 pts/0    00:00:00      \_ ./zombie
root          12      10  0 11:18 pts/0    00:00:00      |   \_ ./zombie
root          14      12  0 11:18 pts/0    00:00:00      |   |   \_ [zombie] <defunct>
root          13      10  0 11:18 pts/0    00:00:00      |   \_ [zombie] <defunct>
root          11       8  0 11:18 pts/0    00:00:00      \_ ps -eaf --forest
root@b9d49363cb57:/zombie# pParent process will crash with segmentation failt!
ps -eaf --forest
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 11:18 pts/0    00:00:00 /zombie/ignore
root           7       1  0 11:18 pts/0    00:00:00 sh -c /bin/bash
root           8       7  0 11:18 pts/0    00:00:00  \_ /bin/bash
root          15       8  0 11:18 pts/0    00:00:00      \_ ps -eaf --forest
root          12       1  0 11:18 pts/0    00:00:00 ./zombie
root          14      12  0 11:18 pts/0    00:00:00  \_ [zombie] <defunct>
root          13       1  0 11:18 pts/0    00:00:00 [zombie] <defunct>
[1]+  Exit 255                ./zombie
root@b9d49363cb57:/zombie# Child process 12 is exiting!
ps -eaf --forest
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 11:18 pts/0    00:00:00 /zombie/ignore
root           7       1  0 11:18 pts/0    00:00:00 sh -c /bin/bash
root           8       7  0 11:18 pts/0    00:00:00  \_ /bin/bash
root          16       8  0 11:18 pts/0    00:00:00      \_ ps -eaf --forest
root          12       1  0 11:18 pts/0    00:00:00 [zombie] <defunct>
root          13       1  0 11:18 pts/0    00:00:00 [zombie] <defunct>
root          14       1  0 11:18 pts/0    00:00:00 [zombie] <defunct>
root@b9d49363cb57:/zombie#

现在,系统中还有3个僵尸,被吊死。

 类似资料:
  • 我有一个在PID1运行bash的Docker容器,它反过来运行一个长时间运行的(复杂的)服务,该服务有时会产生PID1 bash的僵尸进程。这些僵尸似乎永远不会被收割。 我试图在一个最小的容器中重现这个问题,以便测试缓解措施,例如使用适当的init作为PID1而不是bash。 然而,我一直无法重现僵尸进程。PID1的重击似乎收获了孩子,甚至是从另一个进程继承下来的孩子。 以下是我尝试过的: 我的期

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

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

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

  • 由于Docker从引擎版本20.10开始支持cgroup v2,因此它将自动在启用了cgroups v2的发行版上使用它。从容器中获取唯一容器ID的已知解决方案不再有效。 尝试与docker v20.10.8在Debian 11与阿尔卑斯山:最新。 cgroup v1的工作解决方案:如何从容器本身中获取DockerLinux容器信息? 正如docker参考中所述,对于cgroup v2,容器id在

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