作者:Chris Evich
假设您正在测试一个需要与无根 Podman 套接字交互的服务。该服务可能对容器、卷或镜像具有破坏性,因此您不想冒主机安装损坏的风险。如何在无根容器内完成这一切?答案有点复杂,但在这篇文章中,我将尝试逐步引导您解决每个挑战。
Podman 内部的嵌套 Podman 并不是什么新鲜事。在主机端,我们必须运行 --privileged
并从主机提供容器存储(-v container-storage:/home/<name>/.local/share/containers:Z
)。这是为了避免出现糟糕的层叠覆盖(overlay-in-overlay)情况。无论如何,这相当简单且不足为奇,如下面的 Containerfile 所示
FROM registry.fedoraproject.org/fedora:latest RUN dnf upgrade -y && \ dnf install -y podman && \ rpm --setcaps shadow-utils 2>/dev/null && \ dnf clean all RUN groupadd -g 1000 fred && \ useradd -u 1000 -g 1000 fred && \ echo -e "fred:1:999\nfred:1001:64535" | \ tee /etc/subuid > /etc/subgid VOLUME /home/fred/.local/share/containers
基础镜像包的升级和安装应该不足为奇。shadow-utils 上的 --setcaps
是 Fedora 特有的一个长期存在的 bug 的解决方法。接下来,我们添加用户并设置嵌套的 subuid/subgid 映射。这是必需的,这样嵌套容器就不会占用与命名空间 UID 0
(root 用户)和 UID 1000
(Fred)重叠的 ID。如果发生这种情况,嵌套容器中的用户可能会对外部容器的文件/目录拥有意外的访问权限。因此,这种 subuid/gid
设置非常重要。
由于 Fred 是我们唯一的嵌套用户和组命名空间,我们可以简单地覆盖 /etc/sub{uid,gid}
文件中的默认内容。如果存在其他具有已知 ID 的用户,也应该将它们从 Fred 的命名空间范围中排除,以避免意外冲突。最后,该镜像请求主机上的一个卷来提供上述容器存储。
在此容器镜像内部运行嵌套容器很简单,不足为奇
$ podman build -t piptest . ...cut... $ podman run -it --rm --privileged --user fred --hostname outer piptest [fred@outer /]$ podman run -it --rm --privileged --hostname inner fedora:latest ...cut... Writing manifest to image destination Storing signatures [root@inner /]# echo "no magic here" no magic here [root@inner /]# exit exit [fred@outer /]$ exit exit
接下来是棘手的部分。由于 Podman 没有守护进程,因此需要一个正在运行的 Podman 进程来处理 API 套接字请求。虽然我们可以运行 podman system service -t0
作为容器的命令,但这不允许我们同时从应用程序中使用套接字。对于容器,任何时候您考虑需要多个高级进程时,除非它们完全微不足道(这些不是),否则您会需要 Systemd 这样的初始化系统。
在容器内运行 Systemd 会使事情复杂化——主要是由于额外的服务及其非平凡的配置。然而,在此用例中,它是必需的,因为我们希望容器执行多项任务。具体来说,Systemd 需要正确处理(可能)大量 Podman 子进程的出现和消失,以及其子树的信号处理。我甚至还没有提到将使用 Podman 套接字的应用程序,它也必然需要 Systemd 处理。
换句话说,如果没有 PID 1 进程管理器,我们的容器将很快变成一个不协调的混乱局面,不断地考验自身的崩溃边缘。
回到 Containerfile
,更新 dnf install
行以包含 systemd
很容易。尽管需要一些额外的魔术才能使 Podman 套接字服务生效,并在容器启动时保持操作可观察。假设您的主机是基于 systemd 的,并且已安装 Podman,Podman systemd 文件可以简单地复制到容器构建上下文中,例如
$ cp /lib/systemd/system/podman.s* ./
首先需要更改的是对 podman.service
文件的小幅更新——使其以警告级别记录到控制台(Fedora 上的默认级别是 info
级别)
[Unit] Description=Podman API Service Requires=podman.socket After=podman.socket Documentation=man:podman-system-service(1) StartLimitIntervalSec=0 [Service] Delegate=true Type=exec KillMode=process Environment=LOGGING="--log-level=warn" ExecStart=/usr/bin/podman $LOGGING system service StandardOutput=journal+console StandardError=inherit [Install] WantedBy=default.target
其次,让 systemd 管理 Fred 主目录中监听的 podman.sock
文件,这样更容易交互,并节省一些输入
[Unit] Description=Podman API Socket Documentation=man:podman-system-service(1) [Socket] ListenStream=%h/podman.sock SocketMode=0660 [Install] WantedBy=sockets.target
套接字、服务的安装以及 Fred 的 systemd-slice 设置都在 Containerfile
中进行,到目前为止,它看起来像这样
FROM registry.fedoraproject.org/fedora:latest RUN dnf upgrade -y && \ dnf install -y podman systemd && \ rpm --setcaps shadow-utils 2>/dev/null && \ dnf clean all RUN useradd -u 1000 fred && \ echo -e "fred:1:999\npodman:1001:64535" | tee /etc/subuid > /etc/subgid VOLUME /home/fred/.local/share/containers ADD /podman.service /podman.socket /home/fred/.config/systemd/user/ RUN cd /home/fred/.config/systemd/user/ && \ mkdir sockets.target.wants && \ ln -s ../podman.socket ./sockets.target.wants/ && \ mkdir -p /var/lib/systemd/linger && \ touch /var/lib/systemd/linger/fred && \ chown -R 1000:1000 /home/fred ENTRYPOINT /sbin/init
让用户切片服务随容器启动的关键是创建文件 /var/lib/systemd/linger/fred
,这等同于运行:loginctl enable-linger fred
。然而,在容器镜像构建环境中,systemd 和 dbus 都不可用,因此只是简单地创建了该文件。
最后的 Containerfile
步骤修复了 Fred 的文件所有权,并指示容器应将 init (systemd) 作为 PID 1
启动。然而,由于容器现在将作为(命名空间)root 用户启动,我们需要预先创建容器存储卷,如下所示;否则,所有权将不正确——无疑会产生大量“权限拒绝”错误。
$ podman volume create -o o=uid=1000,gid=1000 freds-containers
如果您想查看或操作该卷的内容,请务必在命令前加上 podman unshare
以进入您的用户命名空间。否则,就是这样,所有技术细节都已揭示,魔术也已公开。剩下要做的就是启动容器并证明嵌套的、远程的、无根的 Podman 连接是功能性的
$ podman build -t piptest . ...cut... $ podman run -dt --rm --privileged --hostname outer \ -v freds-containers:/home/fred/.local/share/containers \ --systemd true piptest 0af2a31de3467d0ddb2b540072c0864f7738d5de5745aa5b3596d70f4a5f7a04 $ podman exec -itl bash [root@outer /]# ls -la /home/fred/podman.sock srw-rw----. 1 fred fred 0 Feb 9 17:06 /home/fred/podman.sock [root@outer /]# export CONTAINER_HOST=unix:///home/fred/podman.sock [root@outer /]# podman --remote info --format={{.Store.GraphRoot}} /home/fred/.local/share/containers/storage [root@outer /]# podman --remote run -it --rm --hostname inner fedora:latest ...cut... Writing manifest to image destination Storing signatures [root@inner /]# echo "Hello from $HOSTNAME" Hello from inner [root@inner /]# exit exit [fred@outer /]$ exit exit $ podman stop -l 0af2a31de3467d0ddb2b540072c0864f7738d5de5745aa5b3596d70f4a5f7a04
在这里,您可以看到容器已构建,然后以 --privileged
模式运行(嵌套的无根容器必需),并带有预先创建的容器存储卷。然后作为命名空间中的 root 用户执行进入,在那里我们连接到 Fred 的 Podman 套接字并打印出容器存储位置。尽管使用 root 只是为了方便,但您可以作为任何配置/命名空间用户连接到套接字,并具有适当的权限。如上所示,位置和权限可以在 podman.socket
文件中配置。
此时,任何连接到 Podman 套接字的服务开发都是可能的,并且将与主机的 Podman 设置隔离。添加更多用户、systemd-slice 并使其持久化应该相当简单。不过,您需要记住将其他命名空间的 UID/GID
从嵌套的 /etc/sub{uid,gid}
中排除,以防止冲突。
尽管需要在 --privileged
模式下运行顶级容器,但容器化的 systemd-slice 提供了一定程度的额外隔离。这种设置当然不如没有 systemd 或额外权限那么安全。然而,privileged 选项远不如容器以 root 身份运行那么糟糕。在这种情况下,由于无根用户命名空间,额外启用的功能影响有限。因此,对于测试、开发或非关键目的,这是一个完全可以接受的安排。
总的来说,这些容器成功地将其软件、依赖项和运行时环境与主机操作系统隔离。此外,由于它们已经启用了 systemd,因此应该很容易添加额外的应用程序和服务文件——将它们指向嵌套的 Podman 套接字位置。然而,由于本文已经有点长,这些都留给读者作为练习。
发表评论