当podman从systemd启动时,为什么conmon在另一个cgroup中?


11

给定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 中看不到shor 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
# ...

我期望看到shand sleep子项处于我的baz.service状态,因为我听说过redhat的人说podman使用传统的fork-exec模型。

如果podman做了fork和exec,那么我shsleepprocess不会是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 chain。

  • podman与Docker客户端服务器模型有何不同?
  • Podman的常识与Docker的容器有何不同?

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

因此,也许docker就像:

  • dockerd守护程序
  • 码头工人cli
  • 容器化容器运行时

而podman就像:

  • 播客
  • 普通容器运行时

因此,也许podman使用传统的fork exec模型,但不是叉臂cli派生和执行,而是常见的过程。

我感到困惑。


在podman邮件列表上有关于此问题的讨论:lists.podman.io/archives/list/podman@lists.podman.io/thread/…–
mbigras

Answers:


8

背后的整个想法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(以前是该cgroup)中删除。因此,启动容器时,cgroup的最终布局如下所示:podman run保留在的cgroup中baz.service,由systemd,创建,conmon进程放置在其自己的cgroup中,而容器化进程放置在其自己的cgroup中:

$ 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死后产生。

希望这可以帮助。


1
“为新容器创建新的cgroup并将进程移到那里”我不明白为什么podman而不是systemd在做这项工作。您能否添加一个解释,说明为什么我们使用conmon来保存stdout和stderr而不是systemd?通过学习systemd,我认为systemd的目的是管理进程并执行诸如捕获stdout / stderr之类的任务,弄清退出状态,处理重启。
mbigras

2
Podman之所以管理cgroup是因为它拥有容器,并且必须确保容器可以正常运行,而不管您拥有什么初始化系统。Systemd因为拥有服务而管理cgroup,因为它拥有服务(尽管systemd支持某些类型的委派,但是默认情况下服务不应该管理cgroup-请参阅systemd.io/CGROUP_DELEGATION)。如果要podman重用systemd为该服务创建的cgroup,则podman方面必须有支持,并且我目前看不到任何支持(尽管我可能会犯错)。
丹妮拉·基弗

1
至于stdout/ stderr流-再次podman拥有容器并捕获容器化过程的流。systemd拥有服务并获取该服务的主要过程(在你的情况下,流systemd实际上捕获stdout/ stderr中的podman run过程)。该工程完全按照它应该工作,因为conmon捕获容器的流,podman run高度重视conmonsystemd捕获的流podman run如此,最后,从容器中的所有日志中获取的拍摄,systemd和你看到他们在systemctl status baz.service
丹妮拉·基弗
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.