您是否曾好奇如何在容器中运行 Podman:Podman 中的 Podman,Docker 中的 Podman,甚至 Kubernetes 中的 Podman?
对于从事上游容器技术的人员来说,最常被问及的话题之一就是在容器中运行 Podman。这在历史上大多与 Docker 中的 Docker (DIND) 相关,但现在,人们也希望在 Podman 中运行 Podman (PINP) 或在 Docker 中运行 Podman (PIND)。
但 Podman 可以以多种方式运行,有根和无根。我们最终会看到人们希望运行有根和无根 Podman 的各种组合
- 有根 Podman 在有根 Podman 中
- 无根 Podman 在有根 Podman 中
- 有根 Podman 在无根 Podman 中
- 无根 Podman 在无根 Podman 中
您应该明白了。
这篇博客将尝试涵盖每种组合,首先讨论权限。我们将在第一部分中从 PINP 场景开始。在本系列的第二部分中,我们将涵盖类似的内容,但会在 Kubernetes 的上下文中进行。请务必阅读这两篇文章以获得完整理解。
容器引擎需要权限
为了在容器中运行像 Podman 这样的容器引擎,您首先需要了解的是,您需要相当多的权限。
- 容器需要多个 UID。大多数容器镜像需要不止一个 UID 才能工作。例如,您可能有一个镜像,其中大部分文件由 root 拥有,但有些文件由 apache 用户(UID=60)拥有。
- 容器引擎挂载文件系统并使用系统调用 clone 来创建用户命名空间。
注意:您可能需要更新版本的 Podman。本博客中的示例是使用 Podman 3.2 运行的。
我们的测试镜像
在本博客的示例中,我们将使用 `quay.io/podman/stable` 镜像,该镜像的构建旨在寻找在容器中运行 Podman 的最佳方式。您可以在 github.com 仓库中查看我们如何从 Dockerfile 和 `containers.conf` 镜像构建此镜像。
# stable/Dockerfile
#
# Build a Podman container image from the latest
# stable version of Podman on the Fedoras Updates System.
# https://bodhi.fedoraproject.org/updates/?search=podman
# This image can be used to create a secured container
# that runs safely with privileges within the container.
#
FROM registry.fedoraproject.org/fedora:latest
# Don't include container-selinux and remove
# directories used by yum that are just taking
# up space.
RUN dnf -y update; yum -y reinstall shadow-utils; \
yum -y install podman fuse-overlayfs --exclude container-selinux; \
rm -rf /var/cache /var/log/dnf* /var/log/yum.*
RUN useradd podman; \
echo podman:10000:5000 > /etc/subuid; \
echo podman:10000:5000 > /etc/subgid;
VOLUME /var/lib/containers
VOLUME /home/podman/.local/share/containers
ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/containers.conf
ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/podman-containers.conf /home/podman/.config/containers/containers.conf
RUN chown podman:podman -R /home/podman
# chmod containers.conf and adjust storage.conf to enable Fuse storage.
RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' /etc/containers/storage.conf
RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers /var/lib/shared/vfs-images /var/lib/shared/vfs-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock; touch /var/lib/shared/vfs-images/images.lock; touch /var/lib/shared/vfs-layers/layers.lock
ENV _CONTAINERS_USERNS_CONFIGURED=""
让我们检查一下 Dockerfile。
FROM registry.fedoraproject.org/fedora:latest
# Don't include container-selinux and remove
# directories used by yum that are just taking
# up space.
RUN dnf -y update; yum -y reinstall shadow-utils; \
yum -y install podman fuse-overlayfs --exclude container-selinux; \
rm -rf /var/cache /var/log/dnf* /var/log/yum.*
首先拉取最新的 Fedora,然后更新到最新的软件包。注意它重新安装了 `shadow-utils`,因为 Fedora 镜像上的 `shadow-utils` 安装存在已知问题,即 `newsubuid` 和 `newsubgid` 上的 `filecaps` 未设置。重新安装 `shadow-utils` 可以解决此问题。接下来,安装 Podman 以及 `fuse-overlayfs`。我们不安装 `container-selinux`,因为它在容器内不需要。
RUN useradd podman; \
echo podman:10000:5000 > /etc/subuid; \
echo podman:10000:5000 > /etc/subgid;
接下来,我创建了一个用户 `podman`,并设置了 `/etc/subuid` 和 `/etc/subgid` 文件以使用 5000 个 UID。这用于在容器中设置用户命名空间。5000 是一个任意数字,可能太小。我们选择这个数字是因为它小于分配给无根用户的 65k。如果您只以 root 身份运行容器,65k 将是一个更好的数字。
VOLUME /var/lib/containers
VOLUME /home/podman/.local/share/containers
由于我们可以使用此镜像运行有根和无根容器,我们创建了两个卷。有根 Podman 使用 `/var/lib/containers` 作为其容器存储,无根 Podman 使用 `/home/podman/.local/share/containers`。内核通常会拒绝 overlay over overlay,因此这会创建非 overlay 卷以在容器内使用。
ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/containers.conf
ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/podman-containers.conf /home/podman/.config/containers/containers.conf
我预先配置了两个 `containers.conf` 文件,以确保容器在每种模式下都能更轻松地运行。
此镜像默认配置为使用 fuse-overlayfs。在某些情况下,您可以在有根模式下运行内核的 overlay 文件系统,并且很快就能在无根模式下实现。然而,目前,我们使用 fuse-overlayfs 作为容器内部的容器存储。其他人使用过 VFS 存储驱动,但这效率不高。
--privileged 标志
在容器内运行 Podman 最简单的方法是使用 `--privileged` 标志。
有根 Podman 在有根 Podman 中使用 --privileged
# podman run --privileged quay.io/podman/stable podman run ubi8 echo hello
Resolved "ubi8-minimal" as an alias (/etc/containers/registries.conf.d/shortnames.conf)
Trying to pull registry.access.redhat.com/ubi8:latest...
Getting image source signatures
Copying blob sha256:a591faa84ab05242a17131e396a336da172b0e1ec66d921c9f130b7c4c24586d
Copying blob sha256:76b9354adec626b01ffb0faae4a217cebd616661fd90c4b54ba4415f53392fb8
Copying config sha256:dc080723f596f2407300cca2c19a17accad89edcf39f7b8b33e6472dd41e30f1
Writing manifest to image destination
Storing signatures
hello
为了节省时间,因为我将进行大量实验,我在我的主机上创建了一个目录 `./mycontainers`,我将将其卷挂载到容器中以供使用,而不必每次都拉取镜像。
# podman run --privileged -v ./mycontainers:/var/lib/containers quay.io/podman/stable podman run ubi8 echo hello
hello
无根 Podman 在有根 Podman 中使用 --privileged
`quay.io/podman/stable` 镜像配置了一个 **podman** 用户,您可以使用该用户运行无根容器。
# podman run --user podman --privileged quay.io/podman/stable podman run ubi8 echo hello
Resolved "ubi8" as an alias (/etc/containers/registries.conf.d/shortnames.conf)
...
hello
请注意,在这种情况下,在容器内运行的 Podman 以用户 **podman** 的身份运行。这是因为容器化的 Podman 使用用户命名空间在特权容器内创建了一个受限容器。
在 Docker 中运行无根 Podman 并使用 --privileged
与有根 Podman 类似,您也可以在 Docker 中使用 `--privileged` 选项运行无根 Podman。
# docker run --privileged quay.io/podman/stable podman run ubi8 echo hello
无根 Podman 与 Docker
# docker run --user podman --privileged quay.io/podman/stable podman run ubi8 echo hello
Resolved "ubi8" as an alias (/etc/containers/registries.conf.d/shortnames.conf)
...
hello
我们能否更安全地做到这一点?
请注意,即使我们在上面以 `--privileged` 运行外部容器,内部容器也以锁定模式运行。在容器内运行的无根 Podman 确实是锁定的,并且很难逃逸。鉴于此,我并不喜欢使用 `--privileged` 标志。我认为我们可以从安全角度做得更好。
不带 --privileged 标志运行
让我们看看如何删除 `--privileged` 标志以提高安全性。
有根 Podman 在有根 Podman 中不带 --privileged
# podman run --cap-add=sys_admin,mknod --device=/dev/fuse --security-opt label=disable quay.io/podman/stable podman run ubi8-minimal echo hello
hello
我们可以从有根 Podman 中删除 `--privileged` 标志,但仍然需要禁用一些安全功能才能使容器内的有根 Podman 工作。
- 能力:`--cap-add=sys_admin,mknod` 我们需要添加两个 Linux 能力。
- **CAP_SYS_ADMIN** 是 Podman 作为 root 在容器内挂载所需文件系统所必需的。
- **CAP_MKNOD** 是 Podman 作为 root 在容器内创建 `/dev` 中的设备所必需的。(注意 Docker 默认允许此操作)。
- 设备:`--device /dev/fuse` 标志必须在容器内部使用 fuse-overlayfs。此选项告诉主机上的 Podman 将 `/dev/fuse` 添加到容器中,以便容器化的 Podman 可以使用它。
- 禁用 SELinux:`--security-opt label=disable` 选项告诉主机的 Podman 为容器禁用 SELinux 分离。SELinux 不允许容器化进程挂载在容器内运行所需的所有文件系统。
有根 Podman 在 Docker 中不带 --privileged
# docker run --cap-add=sys_admin --cap-add mknod --device=/dev/fuse --security-opt seccomp=unconfined --security-opt label=disable quay.io/podman/stable podman run ubi8-minimal echo hello
hello
- 请注意,Docker 不支持逗号分隔的 `--cap-add` 命令,因此我必须分别添加 **sys_admin** 和 **mknod**
- 仍然需要 `--device /dev/fuse`,因为容器默认使用 `/dev/fuse`
- Docker 总是将内置卷创建为 root:root 所有,因此我们需要创建一个卷来挂载,以便容器中的 Podman 可以用作存储。
- 和往常一样,我需要禁用 SELinux 分离
- 还需要禁用 `seccomp`,因为 Docker 的 `seccomp` 策略比 Podman 略严格。您可以通过使用 `--seccomp=/usr/share/containers/seccomp.json` 来使用 Podman 安全策略。
# docker run --cap-add=sys_admin --cap-add mknod --device=/dev/fuse --security-opt seccomp=/usr/share/containers/seccomp.json --security-opt label=disable quay.io/podman/stable podman run ubi8-minimal echo hello
hello
无根 Podman 在有根 Podman 中不带 --privileged
在容器内部使用非特权容器,并通过用户命名空间使用非 root 用户运行 Podman。
# podman run --user podman --security-opt label=disable --security-opt unmask=ALL --device /dev/fuse -ti quay.io/podman/stable podman run -ti docker.io/busybox echo hello
hello
- 请注意,与之前有根在有根情况不同,我们不必添加危险的安全能力 **sys_admin** 和 **mknod**
- 在这种情况下,我使用 `--user podman` 运行,这会自动导致容器内的 Podman 在用户命名空间内运行
- 仍然禁用 SELinux,因为它会阻止挂载
- 仍然需要 `--device /dev/fuse` 才能在容器内使用 fuse-overlayfs
Podman-remote 在有根 Podman 中,带有主机泄露的 Podman socket
# podman run -v /run:/run --security-opt label=disable quay.io/podman/stable podman --remote run busybox echo hi
hi
在这种情况下,我们将主机上的 `/run` 目录泄露到容器中。这允许 `podman --remote` 与主机上的 Podman socket 通信并在主机操作系统上启动容器。这通常是人们执行 Docker In Docker 的方式,尤其是 Docker 构建。您也可以通过这种方式执行 Podman 构建,并利用先前拉取到系统的镜像。
但是,请注意,这是极其不安全的。容器内的进程可以完全接管主机。
- 您仍然需要禁用 SELinux 分离,因为 SELinux 会阻止容器进程使用 `/run` 中泄露的 socket。
- 添加 `podman --remote` 标志以告诉 Podman 在远程模式下工作。请注意,您也可以只将 `podman-remote` 可执行文件安装到容器中并使用它。
[ 容器入门?查看此免费课程。部署容器化应用程序:技术概述。 ]
Podman-remote 在 Docker 中,带有主机泄露的 Podman socket
# docker run -v /run:/run --security-opt label=disable quay.io/podman/stable podman --remote run busybox echo hi
hi
同样的例子也适用于 Docker 容器。
此示例显示了一个完全锁定的容器——除了 SELinux 被禁用之外——Podman socket 被泄露到容器中。SELinux 会阻止这种访问,这正是它应该做的。
# /bin/podman run --security-opt=label=disable -v /run/podman:/run/podman quay.io/podman/stable podman --remote run alpine echo hi
hi
无根 Podman 与容器化有根 Podman
$ podman run --privileged quay.io/podman/stable podman run ubi8 echo hello
Resolved "ubi8" as an alias (/etc/containers/registries.conf.d/shortnames.conf)
..
hello
无根 Podman 运行无根 Podman
$ podman run --security-opt label=disable --user podman --device /dev/fuse quay.io/podman/stable podman run alpine echo hello
最后的思考
现在您对 Podman in Podman 选项有了更深的理解,包括有根和无根模式的各种组合。您也对必要的权限以及围绕 `--privileged` 标志的考虑有了更好的认识。
本系列的第二部分探讨了 Podman 和 Kubernetes 的使用。这篇文章涵盖了类似的内容,但置于 Kubernetes 的背景下。
图片来自 Luisella Planeta Leoni,摘自 Pixabay
发表评论