,

无根 Systemd 在无根 Podman 中

作者: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

基本镜像包升级和安装不应该令人惊讶。--setcapsshadow-utils 上是一个针对长期存在的错误的 Fedora 特定解决方法。接下来,我们添加我们的用户并设置嵌套的 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 这样的 init 系统。

在容器中运行 Systemd 会使事情变得复杂 - 主要是因为额外的服务及其非平凡的配置。  但是;在这种情况中,它是必需的,因为我们希望容器执行多项操作。具体来说,Systemd 是必要的,以便正确处理(可能)大量 Podman 子进程在存在状态之间进出,以及它们子树向上/向下传递的信号处理。我甚至没有提到将使用 Podman 套接字的应用程序,它不可避免地也需要 systemd 处理。

换句话说,如果没有 PID 1 进程管理器,我们的容器很快就会变成一个不协调的混乱,不断地测试其自身的即将崩溃。

Leaning tower of Jenga
积木塔

回到 Containerfile 中,将 dnf install 行更新为包含 systemd 很容易。尽管需要一些额外的技巧来使 Podman 套接字服务恢复生机,并在容器启动时保持操作可观察。假设您的主机是基于系统的,并且已安装 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 文件中配置。

Robotic Jenga Stacking
机器人积木堆叠

在这一点上,任何连接到 Podman 套接字的服务的开发都是可能的,并且将与主机的 Podman 设置隔离开。  添加更多用户、systemd-slice 并使它们保持活动状态应该相当简单。  不过,您需要记住从嵌套的 /etc/sub{uid,gid} 中的 podman-user 的范围内排除其他命名空间 UID/GID,以防止冲突。

尽管需要在 --privileged 模式下运行顶层容器,但容器化的 systemd-slice 提供了一些额外的隔离级别。  这种设置当然不如没有 systemd 或额外的权限那样安全。  但是;privileged 选项远不如将容器作为 root 运行那样糟糕。  在这种情况下,由于无根用户命名空间,额外启用的功能的影响非常有限。  所以,对于测试、开发或非关键目的来说,这是一个非常不错的安排。

总的来说,这些容器成功地将其软件、依赖项和运行时环境与主机操作系统隔离开。此外,由于它们已经启用了 systemd,因此应该很容易添加额外的应用程序和服务文件 - 将它们指向嵌套的 Podman 套接字位置。但是,由于本文已经有点长,因此这些都留给读者作为练习。

发表评论

订阅

使用您的电子邮件地址注册,以接收来自本网站的电子邮件更新。


类别


搜索