Как пожинать процесс зомби в контейнере докеров с помощью bash

Недавно я изучаю dumb-init. и если я правильно понял, он пытается:

  1. работает как PID1, действуя как простая система инициализации (процессы-зомби)
  2. сигнальный прокси/переадресация (чего не делает bash)

В обоих здесь и здесь все упоминали, что bash способен пожинать зомби процесс, поэтому я пытаюсь проверить это, но не смог заставить его работать.

Прежде всего, я написал простую программу на Go, которая порождает 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 <image>, он работает, как и ожидалось, я вижу 10 процессов зомби в ps:

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>

2-й шаг — убедиться, что bash действительно способен обрабатывать процесс, поэтому я обновляю свой образ докера 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

Пробовал несколько других способов, но так и не смог понять, как правильно пожинать процесс зомби.

Спасибо за помощь.


person Chris Hung    schedule 01.03.2021    source источник
comment
Каковы идентификаторы родительских процессов дочерних процессов? В вашем последнем листинге ps процесс запуска все еще выполняется (pid 7), и я ожидаю, что он будет родителем процессов sleep (pid 13, 14, ...), что означает, что корневой процесс bash (pid 1 ) не пожнет их.   -  person David Maze    schedule 01.03.2021
comment
Чтобы иметь настоящего зомби, родитель не должен wait за ребенка и игнорировать SIGCHLD. Как только вы убьете родителя, зомби-процессы будут перемещены в PID 1 в качестве нового родителя.   -  person jordanvrtanoski    schedule 01.03.2021
comment
ааа, так что я должен убить процесс pid7, но при этом все дети также будут собраны (я думаю, это как-то связано с Go GC?)   -  person Chris Hung    schedule 02.03.2021


Ответы (1)


Я написал небольшую демонстрацию в c, которая может помочь продемонстрировать, что bash пожинает плоды зомби-процессов, и как это выглядело бы, если бы он этого не сделал.

Сначала объясним определение зомби-процесса. Процесс-зомби — это процесс, завершивший работу и сгенерировавший код выхода. Ресурсы сохраняются ядром в ожидании, пока родитель соберет код выхода.

Чтобы иметь зомби, родитель должен игнорировать выход ребенка (не выдавать wait и игнорировать 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);
}

Я также создал контейнер докеров, который будет компилировать файл и дочерний элемент. Весь проект доступен в следующем 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 10), который создаст дочерний элемент-зомби (PID 12) и будет спать в течение 10 секунд.
  • Ребенок, который станет зомби (PID 11).

После создания процессов родительский процесс будет спать в течение 5 секунд и создаст ошибку сегментации, оставив зомби.

Когда основной процесс умирает, PID 11 наследуется bash и очищается (собирается). PID 10 все еще работает (сон - это своего рода работа для процесса) его оставляет в покое bash, так как PID 11 не вызывал wait, 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 висящих зомби.

person jordanvrtanoski    schedule 02.03.2021
comment
спасибо за отличный пример - person Chris Hung; 03.03.2021