当 podman 用 systemd 启动时,为什么 conmon 位于不同的 cgroup 中?

当 podman 用 systemd 启动时,为什么 conmon 位于不同的 cgroup 中?

给定 podman 安装在 Linux 系统上和名为 baz.service 的 systemd 单元:

# /etc/systemd/system/baz.service
[Service]
ExecStart=/usr/bin/podman run --rm --tty --name baz alpine sh -c 'while true; do date; sleep 1; done'
ExecStop=/usr/bin/podman stop baz

baz.service 启动了:

# systemctl daemon-reload
# systemctl start baz.service

然后,当我检查设备的状态时,我在 /system.slice/baz.service cgroup 中看不到sh或进程sleep

# systemctl status baz
● baz.service
   Loaded: loaded (/etc/systemd/system/baz.service; static; vendor preset: enabl
   Active: active (running) since Sat 2019-08-10 05:50:18 UTC; 14s ago
 Main PID: 16910 (podman)
    Tasks: 9
   Memory: 7.3M
      CPU: 68ms
   CGroup: /system.slice/baz.service
           └─16910 /usr/bin/podman run --rm --tty --name baz alpine sh -c while
# ...

我本来希望在 baz.service 状态中看到shsleep子项,因为我听 Redhat 的人说 podman 使用传统的 fork-exec 模型。

如果 podman 执行了 fork 和 exec,那么我的shsleep进程不是 podman 的子进程,并且与原始 podman 进程位于同一个 cgroup 中吗?

我期望能够使用 systemd 和 podman 来管理我的容器,而无需孩子们转到不同的父母并逃离我的 baz.service ssystemd 单元。

查看 的输出,ps我可以看到shsleep实际上是名为 的不同进程的子进程conmon。我不确定 conmon 从哪里来,或者它是如何启动的,但 systemd 没有捕获它。

# ps -Heo user,pid,ppid,comm
# ...
root     17254     1   podman
root     17331     1   conmon
root     17345 17331     sh
root     17380 17345       sleep

从输出中可以清楚地看出,我的 baz.service 单元没有管理 conmon -> sh -> sleep 链。

  • podman 与 docker 客户端服务器模型有何不同?
  • podman的conmon和docker的containerd有什么不同?

也许它们都是容器运行时,而dockerd守护进程是人们想要摆脱的东西。

所以也许 docker 是这样的:

  • dockerd 守护进程
  • docker 命令行
  • Containerd 容器运行时

podman 就像:

  • podman 命令行界面
  • 公共容器运行时

因此,也许 podman 使用传统的 fork exec 模型,但不是 podman cli 进行 fork 和 exec,而是通用进程。

我感到很困惑。

答案1

背后的整个想法podman是摆脱具有超级强大监督者的集中式架构(例如dockerd),其中集中式守护进程是单点故障。甚至还有一个关于此的标签 - ”#nobigfatdaemons”。

如何避免容器集中管理?您删除单个主守护进程(再次,dockerd)并独立启动容器(归根结底,容器只是进程,因此您不需要守护进程来生成它们)。

但是,您仍然需要方法

  • 收集容器的日志——必须有人持有stdout容器stderr
  • 收集容器的退出代码 - 必须有人wait(2)收集容器的 PID 1;

为此,每个 podman 容器仍然由一个名为conmon(来自“容器监视器”)的小守护程序进行监督。与 Docker 守护进程的区别在于,这个守护进程尽可能小(检查源代码的大小),并且它是按容器生成的。如果conmon一个容器崩溃,系统的其余部分不受影响。

接下来,容器是如何生成的?

考虑到用户可能希望在后台运行容器,就像 Docker 一样,进程podman run会分叉两次然后才执行conmon

$ strace -fe trace=fork,vfork,clone,execve -qq podman run alpine
execve("/usr/bin/podman", ["podman", "run", "alpine"], 0x7ffeceb01518 /* 30 vars */) = 0
...
[pid  8480] clone(child_stack=0x7fac6bffeef0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tid=[8484], tls=0x7fac6bfff700, child_tidptr=0x7fac6bfff9d0) = 8484
...
[pid  8484] clone(child_stack=NULL, flags=CLONE_VM|CLONE_VFORK|SIGCHLD <unfinished ...>
[pid  8491] execve("/usr/bin/conmon", ... <unfinished ...>
[pid  8484] <... clone resumed>)        = 8491

podman run和之间的中间进程conmon(即 的直接父进程conmon- 在上面的示例中,PID 为 8484)将退出并由conmon重新设置父进程init,从而成为自我管理的守护进程。之后,conmon还分叉运行时(例如runc),最后,运行时执行容器的入口点(例如/bin/sh)。

当容器正在运行时,podman run不再需要它并且可能会退出,但在您的情况下,它保持在线状态,因为您没有要求它与容器分离。

接下来,podman使用 cgroup 来限制容器。这意味着它为新容器创建新的 cgroup 并将进程移至其中。根据 cgroup 的规则,进程一次只能是一个 cgroup 的成员,将进程添加到某个 cgroup 会将其从同一层次结构中的其他 cgroup(之前所在的位置)中删除。所以,当容器启动时,cgroups的最终布局如下所示:保留在由创建的podman runcgroups中,进程放置在自己的cgroup中,容器化进程放置在自己的cgroup中:baz.servicesystemdconmon

$ ps axf
<...>
 1660 ?        Ssl    0:01 /usr/bin/podman run --rm --tty --name baz alpine sh -c while true; do date; sleep 1; done
 1741 ?        Ssl    0:00 /usr/bin/conmon -s -c 2f56e37a0c5ca6f4282cc4c0f4c8e5c899e697303f15c5dc38b2f31d56967ed6 <...>
 1753 pts/0    Ss+    0:02  \_ sh -c while true; do date; sleep 1; done
13043 pts/0    S+     0:00      \_ sleep 1
<...>

$ cd /sys/fs/cgroup/memory/machine.slice
$ ls -d1 libpod*
libpod-2f56e37a0c5ca6f4282cc4c0f4c8e5c899e697303f15c5dc38b2f31d56967ed6.scope
libpod-conmon-2f56e37a0c5ca6f4282cc4c0f4c8e5c899e697303f15c5dc38b2f31d56967ed6.scope

$ cat libpod-2f56e37a0c5ca6f4282cc4c0f4c8e5c899e697303f15c5dc38b2f31d56967ed6.scope/cgroup.procs 
1753
13075

$ cat libpod-conmon-2f56e37a0c5ca6f4282cc4c0f4c8e5c899e697303f15c5dc38b2f31d56967ed6.scope/cgroup.procs 
1741

注意:上面的PID 13075实际上是一个sleep 1进程,是在PID 13043死亡后产生的。

希望这可以帮助。

答案2

podman with--cgroups split将以更加适合 systemd 的方式创建 cgroup。 (类似于 systemd-nspawn 的做法,使用“服务”cgroup 以及用于主管和容器进程的子 cgroup)

示例 - 我的 rwhod 容器:

 CGroup: /machine.slice/rwhod.service
         ├─container
         │ ├─ 998 /dev/init -- /container/tool/run
         │ ├─1040 /usr/bin/python3 -u /container/tool/run
         │ └─1706 /usr/sbin/rwhod -i mgmt0 -S -D
         └─supervisor
           └─995 /opt/podman/libexec/podman/conmon --api-version 1 -c ddf3e27960378fd57b2ebd15d7beb7474506f612e7329acb014c5f89cd652562 >

作为这个日志讨论的一部分,我专门为此目的将该方法添加到了 podman。https://github.com/containers/podman/issues/6400

相关内容