在容器中运行 systemd

我们长期以来一直关注在容器中使用 systemd 的话题。 早在 2014 年,我们的安全工程师 Daniel Walsh 就写了一篇文章 在 Docker 容器中运行 systemd,几年后 - 另一个,被称为 在非特权容器中运行 systemd,他在其中表示情况并没有太大改善。 他特别写道,“不幸的是,即使是两年后,如果你用谷歌搜索“Docker 系统”,首先出现的还是他的同一篇旧文章。 所以是时候改变一些事情了。” 另外,我们已经讨论过 Docker 和 systemd 开发人员之间的冲突.

在容器中运行 systemd

在本文中,我们将展示随着时间的推移发生了什么变化,以及 Podman 如何在这方面帮助我们。

在容器内运行 systemd 有很多原因,例如:

  1. 多服务容器 – 许多人希望将多服务应用程序从虚拟机中拉出来并在容器中运行。 当然,最好将此类应用程序分解为微服务,但并不是每个人都知道如何做到这一点,或者只是没有时间。 因此,将此类应用程序作为 systemd 从单元文件启动的服务来运行是非常有意义的。
  2. Systemd 单元文件 – 在容器内运行的大多数应用程序都是根据以前在虚拟机或物理机上运行的代码构建的。 这些应用程序有一个为这些应用程序编写的单元文件,并了解它们应该如何启动。 因此,最好使用支持的方法启动服务,而不是破解您自己的 init 服务。
  3. Systemd 是一个进程管理器。 它比任何其他工具都能更好地管理服务(关闭、重新启动服务或杀死僵尸进程)。

也就是说,不在容器中运行 systemd 的原因有很多。 主要的一个是 systemd/journald 控制容器的输出,以及诸如 Kubernetes или 开班 期望容器将日志直接写入 stdout 和 stderr。 因此,如果您打算通过上面提到的编排工具来管理容器,您应该认真考虑使用基于 systemd 的容器。 此外,Docker 和 Moby 开发人员经常强烈反对在容器中使用 systemd。

波德曼的到来

我们很高兴地报告,情况终于有了进展。 红帽负责运行容器的团队决定开发 你自己的容器引擎。 他有一个名字 波德曼 并提供与 Docker 相同的命令行界面 (CLI)。 并且几乎所有的 Docker 命令都可以在 Podman 中以同样的方式使用。 我们经常举办研讨会,现在称为 将 Docker 更改为 Podman,第一张幻灯片要求编写:alias docker=podman。

许多这样做。

我和我的 Podman 绝不反对基于 systemd 的容器。 毕竟,Systemd 是最常用的 Linux init 子系统,不允许它在容器中正常工作意味着忽视成千上万的人习惯运行容器。

Podman 知道如何使 systemd 在容器中正常工作。 它需要在 /run 和 /tmp 上安装 tmpfs 之类的东西。 她喜欢启用“容器化”环境,并希望获得对她所在的 cgroup 目录和 /var/log/journald 文件夹的写入权限。

当您启动第一个命令是 init 或 systemd 的容器时,Podman 会自动配置 tmpfs 和 Cgroup,以确保 systemd 启动没有问题。 要阻止此自动启动模式,请使用 --systemd=false 选项。 请注意,Podman 仅在需要运行 systemd 或 init 命令时才使用 systemd 模式。

以下是手册的摘录:

男子 podman 运行
...

–systemd=true|false

在 systemd 模式下运行容器。 默认启用。

如果您在容器内运行 systemd 或 init 命令,Podman 将在以下目录中配置 tmpfs 挂载点:

/run、/run/lock、/tmp、/sys/fs/cgroup/systemd、/var/lib/journal

默认停止信号也是 SIGRTMIN+3。

所有这些都允许 systemd 无需任何修改就可以在封闭的容器中运行。

注意:systemd 尝试写入 cgroup 文件系统。 然而,SELinux 默认阻止容器执行此操作。 要启用写入,请启用container_manage_cgroup布尔参数:

setebool -P container_manage_cgroup true

现在看看使用 Podman 在容器中运行 systemd 的 Dockerfile 是什么样子的:

# cat Dockerfile

FROM fedora

RUN dnf -y install httpd; dnf clean all; systemctl enable httpd

EXPOSE 80

CMD [ "/sbin/init" ]

这就是全部。

现在我们组装容器:

# podman build -t systemd .

我们告诉 SELinux 允许 systemd 修改 Cgroups 配置:

# setsebool -P container_manage_cgroup true

顺便说一句,很多人忘记了这一步。 幸运的是,这只需要完成一次,并且在重新启动系统后会保存设置。

现在我们启动容器:

# podman run -ti -p 80:80 systemd

systemd 239 running in system mode. (+PAM +AUDIT +SELINUX +IMA -APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD +IDN2 -IDN +PCRE2 default-hierarchy=hybrid)

Detected virtualization container-other.

Detected architecture x86-64.

Welcome to Fedora 29 (Container Image)!

Set hostname to <1b51b684bc99>.

Failed to install release agent, ignoring: Read-only file system

File /usr/lib/systemd/system/systemd-journald.service:26 configures an IP firewall (IPAddressDeny=any), but the local system does not support BPF/cgroup based firewalling.

Proceeding WITHOUT firewalling in effect! (This warning is only shown for the first loaded unit using IP firewalling.)

[  OK ] Listening on initctl Compatibility Named Pipe.

[  OK ] Listening on Journal Socket (/dev/log).

[  OK ] Started Forward Password Requests to Wall Directory Watch.

[  OK ] Started Dispatch Password Requests to Console Directory Watch.

[  OK ] Reached target Slices.

…

[  OK ] Started The Apache HTTP Server.

就这样,服务已启动并运行:

$ curl localhost

<html  xml_lang="en" lang="en">

…

</html>

注意:不要在 Docker 上尝试这个! 在那里,您仍然需要用手鼓跳舞才能通过守护进程启动这些类型的容器。 (需要额外的字段和包才能使这一切在 Docker 中无缝工作,或者需要在特权容器中运行。有关详细信息,请参阅 文章.)

关于 Podman 和 systemd 的一些更酷的事情

Podman 在 systemd 单元文件中比 Docker 工作得更好

如果需要在系统启动时启动容器,那么您只需将适当的 Podman 命令插入到 systemd 单元文件中,这将启动服务并对其进行监控。 Podman 使用标准的 fork-exec 模型。 换句话说,容器进程是 Podman 进程的子进程,因此 systemd 可以轻松监控它们。

Docker采用客户端-服务器模型,Docker CLI命令也可以直接放在单元文件中。 然而,一旦 Docker 客户端连接到 Docker 守护进程,它(客户端)就变成了另一个处理 stdin 和 stdout 的进程。 反过来,systemd 不知道 Docker 客户端和在 Docker 守护进程控制下运行的容器之间的连接,因此,在这个模型中,systemd 根本无法监控服务。

通过套接字激活systemd

Podman 正确处理通过套接字的激活。 由于 Podman 使用 fork-exec 模型,因此它可以将套接字转发到其子容器进程。 Docker 无法做到这一点,因为它使用客户端-服务器模型。

Podman 用于与远程客户端与容器进行通信的 varlink 服务实际上是通过套接字激活的。 cockpit-podman 包是用 Node.js 编写的,是 cockpit 项目的一部分,允许人们通过 Web 界面与 Podman 容器进行交互。 运行 cockpit-podman 的 Web 守护进程将消息发送到 systemd 侦听的 varlink 套接字。 然后 Systemd 激活 Podman 程序来接收消息并开始管理容器。 通过套接字激活 systemd 可以消除在实现远程 API 时持续运行的守护进程的需要。

此外,我们正在开发另一个名为 podman-remote 的 Podman 客户端,它实现相同的 Podman CLI,但调用 varlink 来运行容器。 Podman-remote 可以在 SSH 会话之上运行,允许您安全地与不同计算机上的容器交互。 随着时间的推移,我们计划让 podman-remote 支持 MacOS 和 Windows 以及 Linux,以便这些平台上的开发人员可以运行运行 Podman varlink 的 Linux 虚拟机,并获得容器在本地计算机上运行的完整体验。

SD_NOTIFY

Systemd 允许您推迟辅助服务的启动,直到它们需要的容器化服务启动为止。 Podman 可以将 SD_NOTIFY 套接字转发到容器化服务,以便该服务通知 systemd 它已准备好运行。 再说一遍,使用客户端-服务器模型的 Docker 无法做到这一点。

在计划中

我们计划添加命令 podmangeneratesystemdCONTAINERID,该命令将生成一个systemd单元文件来管理指定的特定容器。 对于非特权容器,这应该在根模式和无根模式下都有效。 我们甚至看到了对 OCI 兼容的 systemd-nspawn 运行时的请求。

结论

在容器中运行 systemd 是一种可以理解的需求。 多亏了 Podman,我们终于拥有了一个不与 systemd 冲突、而且易于使用的容器运行时。

来源: habr.com

添加评论