作者: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
基本镜像包升级和安装不应该令人惊讶。--setcaps
在 shadow-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 进程管理器,我们的容器很快就会变成一个不协调的混乱,不断地测试其自身的即将崩溃。
回到 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
文件中配置。
在这一点上,任何连接到 Podman 套接字的服务的开发都是可能的,并且将与主机的 Podman 设置隔离开。 添加更多用户、systemd-slice 并使它们保持活动状态应该相当简单。 不过,您需要记住从嵌套的 /etc/sub{uid,gid}
中的 podman-user 的范围内排除其他命名空间 UID/GID
,以防止冲突。
尽管需要在 --privileged
模式下运行顶层容器,但容器化的 systemd-slice 提供了一些额外的隔离级别。 这种设置当然不如没有 systemd 或额外的权限那样安全。 但是;privileged 选项远不如将容器作为 root 运行那样糟糕。 在这种情况下,由于无根用户命名空间,额外启用的功能的影响非常有限。 所以,对于测试、开发或非关键目的来说,这是一个非常不错的安排。
总的来说,这些容器成功地将其软件、依赖项和运行时环境与主机操作系统隔离开。此外,由于它们已经启用了 systemd,因此应该很容易添加额外的应用程序和服务文件 - 将它们指向嵌套的 Podman 套接字位置。但是,由于本文已经有点长,因此这些都留给读者作为练习。
发表评论