第25节 Buildah / podman 以及在 rootless 模式下工作原理


❤️💕💕记录sealosopen in new window开源项目的学习过程。k8s,docker和云原生的学习open in new window。Myblog:http://nsddd.topopen in new window


[TOC]

什么是 Buildah

Buildah 是一款基于 Linux 的开源工具,用于构建兼容开发容器计划(OCI)的容器open in new window;这意味着,这些容器同样也与 Dockeropen in new windowKubernetesopen in new window 兼容。借助 Buildah,您可以使用熟悉的工具从现有基础镜像创建高效的容器镜像,或者使用空白镜像从头开始构建。

Buildah是一个开源的命令行工具,用于构建、创建和管理 Docker 镜像。它可以帮助用户创建镜像,并不依赖于 Docker 引擎,而是直接使用容器系统和格式。

Buildah 的应用场景包括:

  1. 开发人员和 DevOps 工程师可以使用 Buildah 在不使用 Docker 引擎的情况下快速创建镜像。
  2. Buildah 可以在不需要网络连接的情况下进行本地镜像构建,这对于没有网络连接的环境很有用。
  3. Buildah 可以方便地与其他容器系统结合使用,如 Podman,以实现统一的容器管理。
  4. Buildah 可以通过脚本进行批量构建,从而提高镜像构建效率。

这款命令行工具有以下特点:

  • 在构建容器时可以使用或不使用 Dockerfile(一种文本文件,含有用户在组装镜像时可以调用的所有命令)。
  • 既可以从头开始创建容器镜像,也可以在现有容器镜像的基础上来创建。
  • 不在镜像本身中包含构建工具,不仅可以缩减所构建镜像的大小,提升安全,而且会因为使用更少的资源而减轻传输负担。
  • 与 Dockerfile 兼容,从而能从 Docker 轻松转换。
  • 创建特定于用户的镜像,因此能够按照创建镜像的人来分门别类。

为什么选择Buildah

Buildah 可以带来出众的灵活性,让您无需 Dockerfile 就能构建镜像,更轻松地将其他脚本语言整合进构建过程;也能兼顾效率,使用镜像外的构建工具来创建镜像。因此,这款工具不仅能够加快创新速度,还能让新的概念化为现实。用户可以快速创建容器镜像并以之为基础进行构建,只需必要的工具和流程来准备就绪和运行便可。

Buildah 还能提供以下助力:

  • 检查、验证和修改镜像
  • 将容器和镜像从本地存储推送到公共或私有的镜像仓库或存储库
  • 将镜像推送到 Docker Hub 或从中提取
  • 移除本地存储的容器镜像
  • 挂载或卸载有效容器的根文件系统
  • 将容器根文件系统的更新内容用作新镜像的文件系统层

Podman

Podman 是一个开源的命令行工具,用于管理容器。它可以运行、创建、删除和管理容器,并且不需要任何守护进程。Podman 使用了容器系统和格式,并且不依赖于 Docker 引擎。

Podman 的主要功能包括:

  1. 容器运行:Podman 可以运行容器,并且支持在容器内执行命令。
  2. 容器创建:Podman 可以从镜像创建容器,并且支持对容器进行配置。
  3. 容器管理:Podman 可以管理容器,包括查询容器状态、启动和停止容器等。
  4. 镜像管理:Podman 可以管理镜像,包括拉取镜像、查询镜像信息、删除镜像等。

Podman 的使用场景包括:

  1. 用于开发、测试和部署容器化应用。
  2. 用于在本地进行容器管理,不需要网络连接。
  3. 用于实现对容器的简单管理,并且不需要使用 Docker 引擎。

Buildah and Podman

Buildah 和 Podmanopen in new window 是相辅相成的开源项目和命令行工具,都可操作和构建 OCI 镜像和容器。Buildah 诞生在前,而 Podman 使用了与 Buildah 相同的代码来进行构建。不过,相较于 Podman,Buildah 的命令要细致得多,能够对镜像进行更精细的控制,并可创建更加细化的镜像层。Podman 的"build"命令使用 Buildah 功能的一个子集。

Buildah 专门用于构建容器镜像,复制 Dockerfile 中除守护进程套接字组件之外的所有命令,而 Podman 则擅长维护和修改容器中镜像所需的工作。使用 Podman 时,您可以创建容器(使用 Buildah 来提供容器镜像),然后在生产环境中使用熟悉的命令行接口(CLI)命令来运行、维护和修改您创建的容器(如果您能够在 Docker CLI 中运行某个命令,那么也能在 Podman CLI 中运行同样的命令)。

Podman 和 Buildah 两者的另一区别在于:Buildah 的容器主要是为临时目的而创建的,以便将内容传输到所创建的容器镜像;而使用 Podman 时,用户创建的是将要在更长时间里使用和维护的传统容器。Buildah 的容器主要满足短期用途,而 Podman 的容器则是为长期运行而生。

还有一点,Buildah 和 Podman 不共享容器的内部表述内容,因此一个容器只能在其中一个工具内看到。不过,两者却会共享容器镜像的内部表述内容,在其中一者创建、修改或提取的容器镜像也可在另一者查看和使用。

官方说法:

Buildah 和 Podman 是两个互补的开源项目,可在大多数 Linux 平台上使用,这两个项目都位于 GitHub.comopen in new window上, 此处为 Buildahopen in new window此处为Podmanopen in new window 。Buildah 和 Podman 都是命令行工具,适用于开放容器计划 (OCI) 图像和容器。这两个项目在专业上有所不同。

Buildah 专注于构建 OCI 图像。Buildah 的命令复制了 Dockerfile 中的所有命令。这允许在不需要任何 root 权限的情况下使用和不使用 Dockerfile 构建图像。Buildah 的最终目标是提供一个较低级别的 coreutils 接口来构建图像。在没有 Dockerfiles 的情况下构建图像的灵活性允许将其他脚本语言集成到构建过程中。Buildah 遵循简单的 fork-exec 模型,不作为守护进程运行,但它基于 golang 中的综合 API,可以供应给其他工具。

Podman 专注于帮助您维护和修改 OCI 镜像的所有命令和功能,例如拉取和标记。它还允许您创建、运行和维护从这些图像创建的那些容器。为了通过 Dockerfiles 构建容器镜像,Podman 使用 Buildah 的 golang API,并且可以独立于 Buildah 安装。

Podman 和 Buildah 之间的主要区别在于它们的容器概念。Podman 允许用户创建“传统容器”,这些容器的目的是长期存在。虽然 Buildah 容器实际上只是为了允许将内容添加回容器镜像而创建的。一种简单的理解方式 :

  • buildah run是命令模拟 Dockerfile 中的 RUN 命令,
  • podman run 命令模拟docker run功能中的命令。

⚠️ 由于这一点以及它们的底层存储差异,您无法从 Buildah 中看到 Podman 容器,反之亦然。

简而言之,Buildah 是一种创建 OCI 镜像的高效方式,而 Podman 允许您使用熟悉的容器 CLI 命令在生产环境中管理和维护这些镜像和容器。有关详细信息,请参阅《 容器工具指南》open in new window

Buildah 项目提供了一个命令行工具,可用于创建 OCI 或传统 Docker 镜像格式的镜像,然后从该镜像构建工作容器。

可以挂载和修改容器,然后可以根据更新后的容器保存镜像。

项目之间的一些命令重叠

  • 构建 podman buildbuildah build 命令有很大的重叠,因为 Podman 从 Buildah 借用了大量的 podman build 实现。
  • 运行 buildah runpodman run 命令相似但不同。如上所述,Podman 和 Buildah 具有不同的容器概念。一种简单的理解方式是 buildah run 命令模拟 Dockerfile 中的 RUN 命令,而 podman run 命令模拟功能中的 docker run 命令。由于 Buildah 和 Podman 的容器概念有些不同,您无法从 Buildah 中看到 Podman 容器,反之亦然。
  • pull, push 这些命令在两者之间基本相同,都可以使用。
  • 由于 containers 的不同,commit Commit 的工作方式不同。您不能从 Buildah 提交 Podman 容器,也不能从 Podman 提交 Buildah 容器。
  • tag, rmi, images 这些命令在两者之间基本相同,都可以使用。
  • rm 这条命令表面上看起来是等价的,但是由于两个项目的容器底层存储的不同,它们是不同的。(鉴于此,无法使用 Podman 命令删除 Buildah 容器,也无法使用 Buildah 命令删除 Podman 容器。)
  • mount 两者的挂载命令相似,您可以挂载容器镜像并修改其中的内容,这些内容将在您提交时保存到镜像中。

评价:

Podman 和 Docker 是两个用于管理容器的工具,它们都具有高度的互操作性。Podman 可以代替部分 Docker 的功能,并且在一些方面具有优势,⚠️ 例如不需要运行为 root(docker现在也不需要了)

然而,Docker 目前仍然是容器生态系统中最广泛使用的工具,并且拥有大量的生态系统,包括丰富的镜像库和插件。因此,使用者可以根据自己的需求和喜好选择使用 Podman 或 Docker。

简而言之,Buildah 是一种创建 OCI 镜像的有效方式,而 Podman 允许您使用熟悉的容器 cli 命令在生产环境中管理和维护这些镜像和容器。

总的来说,Podman 和 Docker 都是优秀的容器管理工具,具体选择哪种工具取决于使用者的需求和喜好。

podman rootless

Podman 可以在普通用户身份下运行,而不需要 root 权限。这意味着,用户可以在自己的帐户下管理容器,而不需要使用特权模式。

这是因为 Podman 使用了 user namespaces 来隔离容器,使容器内的进程在系统层面看起来像是 root,但实际上并不具有 root 权限。这使得用户可以在不提升权限的情况下管理容器,并且避免了某些与使用特权模式相关的安全风险。

因此,Podman 是一个更安全的容器管理工具,特别是对于那些不想使用特权模式的用户来说。此外,使用 Podman 可以简化容器管理的流程,并且不需要额外的配置,从而提高生产效率。

builah 教程

CentOS:open in new window

Buildah is available in the default Extras repos for CentOS 7 and in the AppStream repo for CentOS 8 and Stream, however the available version often lags the upstream release.

sudo yum -y install buildah

需要注意的是,以 root 身份运行,因为您需要以 root 身份才能安装 Buildah 软件包:

sudo -s

containers/image项目提供了复制(推送、拉取)、检查和签署容器镜像的机制。该containers/storage项目提供了用于存储文件系统层、容器映像和容器的机制。Buildah 是一个利用这些底层项目的 CLI,因此允许您构建、移动和管理容器镜像和容器。

Buildah 适用于许多 Linux 发行版,但目前在 Windows 或 Mac 平台上不受支持。Buildah 主要专注于构建 OCI 镜像,而Podmanopen in new window提供了更广泛的命令和功能集,可帮助您维护、修改和运行 OCI 镜像和容器。有关项目之间差异的更多信息,请参阅主 README.md 中的Buildah 和 Podman 关系open in new window部分。

rootless user

如果您计划以没有 root 权限的用户身份运行 Buildah,即“无根用户”,系统管理员可能需要事先做一些额外的配置。Podman GitHub 站点上列出了为此所需的设置open in new windowBuildah 具有与 Podman 对无根用户相同的设置和配置要求。

安装后验证

安装 Buildah 后我们可以看到没有安装图像。该buildah images命令将列出所有图像:

buildah images

我们还可以运行看到没有工作容器:

buildah containers

当您从现有镜像构建工作容器时,Buildah 默认将“-working-container”附加到镜像名称以构建容器名称。Buildah CLI 可以方便地返回新容器的名称。您可以通过使用标准 shell 分配将返回值分配给 shell 变量来利用这一点:

container=$(buildah from fedora)

不需要将容器的名称分配给 shell 变量。运行 buildah from fedora就够了。它只是有助于以后简化命令。要查看我们存储在 shell 变量中的容器的名称:

echo $container

我们可以用这个新容器做什么?让我们尝试运行 bash:

buildah run $container bash

请注意,我们得到了一个新的 shell 提示符,因为我们在容器内运行了一个 bash shell。应该注意的buildah run是,它主要用于在构建过程中调试和运行命令。像 Podman 这样功能更全的引擎或像CRI-Oopen in new window这样的容器运行时接口服务更适合在生产环境中启动容器。

一定要exit离开容器,让我们尝试运行其他东西:

buildah run $container java

哎呀。未安装 Java。返回了一条包含类似以下内容的消息。

runc create failed: unable to start start container process: exec: "java": executable file not found in $PATH

让我们尝试使用以下方法将它安装在容器中:

buildah run $container -- dnf -y install java

语法基本上告诉 Buildah:在这一点之后--没有更多的命令选项。buildah run此点之后的选项用于在容器内启动的命令。如果我们指定的命令包含不适用于 Buildah 的命令行选项,则它是必需的。

现在运行buildah run $container java会显示已经安装了Java。它将返回标准 JavaUsage输出。

从 scratch 开始构建镜像

使用构建 OCI 兼容容器镜像的优势之一 buildah 是您可以轻松地从头开始构建容器镜像,从而从镜像中排除不必要的包。大多数用于生产的最终容器镜像可能不需要像dnf.

让我们从头开始构建一个容器和图像。特殊的“图像”名称“scratch”告诉 Buildah 创建一个空容器。容器有少量关于容器的元数据,但没有真正的 Linux 内容。

newcontainer=$(buildah from scratch)

您可以通过运行以下命令来查看这个新的空容器:

buildah containers

您应该看到类似于以下内容的输出:

image-20230208163025720

它的容器名称默认为 working-container 并且存储在$newcontainer变量中。请注意图像名称 (IMAGE NAME) 是“scratch”。这是一个特殊值,表示工作容器不是基于图像。当我们运行时:

buildah images

我们没有看到列出的“scratch”图像。没有相应的划痕图像。基于“scratch”的容器从无到有。

那么这个容器实际上有什么作用吗?让我们来看看。

buildah run $newcontainer

没有。这真的是空的。包安装程序dnf甚至不在这个容器内。它本质上是内核之上的一个空层。那么可以用它做什么呢?幸运的是有一个buildah mount命令。

scratchmnt=$(buildah mount $newcontainer)

注意:如果尝试以无根模式挂载,该命令将失败。只能在您拥有的挂载命名空间中挂载容器。通过执行buildah unshare命令创建并进入用户命名空间和挂载命名空间。有关更多信息,请参阅 buildah-mount(1) 手册页。

$ buildah unshare
# scratchmnt=$(buildah mount $newcontainer)

无特权模式:

如果您已安装 Podman 并将其设置为在无 root 权限模式下使用,则无需进行进一步配置,就能在非特权环境中使用 Buildah。如果需要为 Buildah 启用无 root 权限模式,请运行以下命令:

sudo usermod --add-subuids 200000-201000 --add-subgids 200000-201000 $USER
  • 使用 sudo 以超级用户的身份运行命令,以获得足够的权限来修改用户信息。
  • 使用 usermod 命令修改用户信息。
  • 使用 --add-subuids 选项向用户添加子用户 ID 范围,从 200000 到 201000。
  • 使用 --add-subgids 选项向用户添加子用户组 ID 范围,从 200000 到 201000。
  • 使用 $USER 变量指定要修改的用户名。

命令将为当前用户启用无 root 权限模式。运行该命令后,注销然后重新登录即可启用更改。

以上命令会在主机上定义一系列本地 UID,分配给容器中的用户的 UID 将映射到这些 UID。请注意,为不同用户定义的范围不得重叠。同样重要的是,这些范围不能重复使用任何现有本地用户或组的 UID。默认情况下,在 SLES 15 中使用 useradd 命令添加用户会自动分配 subUID 和 subGID 范围。

⚠️ 在无 root 权限模式下,Buildah 命令必须在用户的已修改用户名称空间中执行。要进入此用户名称空间,请运行 buildah unshare 命令。否则 buildah mount 命令将会失败。


尝试运行bash:

container=$(buildah from fedora)  # 是 os base image

echo $container # 输出这个镜像的信息

buildah run $container bash  # 运行这个镜像

通过回$scratchmnt显我们可以看到覆盖挂载点open in new window的路径,它被用作容器的根文件系统。

# echo $scratchmnt
/var/lib/containers/storage/overlay/b78d0e11957d15b5d1fe776293bd40a36c28825fb6cf76f407b4d0a95b2a200d/merged

/var/lib/containers/storage请注意,如果您以 root 身份开始,覆盖挂载点位于下面的某个位置;.local/share/containers/storage如果您处于无根模式,则覆盖挂载点位于您的主目录的目录下。(参见上文containers/storage或有关更多信息,请参见容器/存储open in new window。)

现在我们有了一个新的空容器,我们可以安装或删除软件包或简单地将内容复制到该容器中。所以让我们安装bashcoreutils以便我们可以运行 bash 脚本。这很容易成为nginx您的容器所需的其他包。

**注意:**下面示例中的版本 (35) 与运行此示例的 Linux 平台 Fedora 版本相关。如果您在主机上运行 dnf 来填充容器,您指定的版本必须对主机有效,否则 dnf 将抛出错误。即如果你要在 RHEL 平台上运行它,你需要指定--releasever 8.1或类似而不是--releasever 35. 如果您希望容器是特定的 Linux 平台,scratch请将示例的第一行更改为您想要的平台,即# newcontainer=$(buildah from fedora),然后您可以为该 Linux 平台指定合适的版本号。

# dnf install --installroot $scratchmnt --releasever 35 bash coreutils --setopt install_weak_deps=false -y

镜像

获取镜像:

构建容器镜像的第一步是获取基础镜像,这是通过 Dockerfile 中的 FROM 语句完成的。Buildah 以类似的方式处理这个。

[]$ sudo buildah from fedora
Getting image source signatures
Copying blob 7778c55b9bda done
Copying config e0d7a1685f done
Writing manifest to image destination
Storing signatures
fedora-working-container

Fedora Linux(第七版以前为Fedora Core)是较具知名度的Linux发行包open in new window之一,由Fedora项目open in new window社群开发、红帽公司open in new window赞助,目标是创建一套新颖、多功能并且自由(开放源代码open in new window)的操作系统。Fedora是商业化的Red Hat Enterprise Linuxopen in new window发行版的上游open in new window源码。

该命令将拉取 Fedora 的基础镜像并存储在主机上。通过执行以下操作可以检查主机上可用的镜像。

[~]$ sudo buildah images
REPOSITORY                      TAG        IMAGE ID       CREATED        SIZE
docker.io/library/fedora        latest     e0d7a1685fed   12 hours ago   190 MB
docker.io/sealerio/kubernetes   v1.22.15   bb75382891e7   4 weeks ago    963 MB

在拉取基础镜像后,有一个该镜像的运行容器实例,这是一个“工作容器”:

[~]$ sudo buildah containers
CONTAINER ID  BUILDER  IMAGE ID     IMAGE NAME                       CONTAINER NAME
0e5b23a293db     *                  scratch                          working-container
883af373c2a5     *     e0d7a1685fed docker.io/library/fedora:latest  fedora-working-container

Buildah 还提供了一个非常有用的命令来停止和删除当前正在运行的所有容器。

$ sudo buildah rm --all

buildah mount

buildah-mount - 挂载工作容器的根文件系统。

buildah mount [container ...]

描述:

将指定容器的根文件系统挂载到可从主机访问的位置,并返回其位置。

如果在没有任何参数的情况下调用 mount 命令,该工具将列出所有当前装载的容器。

在无根模式下运行时,装载在不同的命名空间中运行,以便在使用 vfs 以外的驱动程序时可能无法从主机访问装载的卷。为了能够访问挂载的文件系统,您可能需要单独创建挂载命名空间作为 buildah unshare 的一部分。在使用 buildah unshare 创建的环境中,您可以使用 buildah mount 并访问挂载的文件系统。

如果在无根模式下运行,则需要先执行 buildah 取消共享以使用挂载点。

$ buildah unshare
# buildah mount working-container
/var/lib/containers/storage/overlay/f8cac5cce73e5102ab321cc5b57c0824035b5cb82b6822e3c86ebaff69fefa9c/merged
# cp foobar  /var/lib/containers/storage/overlay/f8cac5cce73e5102ab321cc5b57c0824035b5cb82b6822e3c86ebaff69fefa9c/merged
# buildah unmount working-container
# exit
$ buildah commit  working-container newimage

buildah umount

buildah-umount - 卸载指定工作容器上的根文件系统。

buildah umount [options] [container ...]

# options : --all, -a --全部, -a 将卸载所有当前装载的容器。

💡简单的一个案例如下:

buildah umount containerID 
buildah umount containerID1 containerID2 containerID3
buildah umount --all 

buildah unshare

buildah-unshare -在修改的用户名称空间内运行命令。

buildah unshare [options] [--] [command] # [选项] [--] [命令]

在新用户命名空间中启动进程(默认为 $SHELL )。配置用户名称空间,以便调用用户的UID 和主 GID 分别显示为 UID 0GID 0 。在 newuidmap(1)和newgidmap(1)帮助器的帮助下,/etc/subuid和/etc/subgid中与该用户和组匹配的任何范围也会被映射为它们自己。

buildah unshare 对于排除非特权操作的故障以及手动清除与映像和容器相关的存储和其他数据非常有用。

如果要使用 buildah mount 命令,此命令也很有用。如果一个非特权用户想要挂载和使用一个容器,那么他们需要执行 buildah unshare。对于无权限用户,执行 buildah mount 将失败,除非该用户在 buildah unshare 会话中运行。

参数选择:

--mount, -m [VARIABLE=]containerNameOrID

在运行命令时挂载 containerNameOrID 容器,并将环境变量VARIABLE设置为挂载点的路径。如果未指定 VARIABLE,则默认为containerNameOrID,这可能不是环境变量的有效名称。

💡简单的一个案例如下:

# mnt=$(buildah mount $ctr)
# su sealer 
$ mnt=$(buildah mount $ctr)
cannot mount using driver overlay in rootless mode. You need to run it in a `buildah unshare` session

如果您还没有执行,问题是mnt=$(buildah mount $ctr)拒绝使用覆盖驱动程序安装图像buildah unshare

当您在无根模式下执行buildah诸如bud和之类的命令时,该命令会进入用户和挂载命名空间。它可以很好地挂载容器文件系统并运行命令。命令完成后退出,导致名称空间被销毁。使用命令执行此操作时,挂载的文件系统从未出现在主机挂载命名空间中。

Buildah现在会检查这种情况,并向用户报告他们必须首先发出Buildah取消共享。

Buildah now checks for this situation and reports to the user that they have to issue a buildah unshare first.

Buildah 有一个特殊的命令 buildah unshare,它允许您进入用户命名空间。如果你不使用任何命令执行它,它会在用户命名空间中启动一个 shell,你的 shell 看起来就像是以 root 身份运行的,并且主目录的所有内容看起来都像是 root 拥有的。

如果您查看 /usr 中的所有者或文件,它会将它们列为 nfsnobody(或没有人)拥有。这是因为您的用户 ID (UID) 现在是用户命名空间内的 root,而真正的 root (UID=0) 未映射到用户命名空间。内核将所有未映射到用户名称空间的 UID 所拥有的文件表示为 NFSNOBODY 用户。

当您退出 shell 时,您将退出用户命名空间,您将回到您的正常 UID,并且主目录将再次归您的 UID 所有。

example:

buildah unshare id buildah 取消共享ID

buildah unshare pwd buildah 取消共享密码

buildah unshare cat /proc/self/uid_map /proc/self/gid_map

构建时取消 共享目录/进程/自身/uid_map /进程/自身/gid_map

buildah unshare rm -fr $HOME/.local/share/containers/storage /run/user/`id -u`/run
buildah unshare --mount containerID sh -c 'cat ${containerID}/etc/os-release'

如果你想使用buildah和mount命令,那么你可以创建一个脚本,如下所示:

cat buildah-script.sh << _EOF
#!/bin/sh
ctr=$(buildah from scratch)
mnt=$(buildah mount $ctr)
dnf -y install --installroot=$mnt PACKAGES
dnf -y clean all --installroot=$mnt
buildah config --entrypoint="/bin/PACKAGE" --env "FOO=BAR" $ctr
buildah commit $ctr imagename
buildah unmount $ctr
_EOF

Then execute it with:

buildah unshare buildah-script.sh

Buildah 和 Podman 有一个特殊的命令 : unshare 。此命令在不创建容器或与容器交互的情况下创建并进入用户命名空间。探索这种模式以充分理解用户命名空间在做什么实际上是相当有趣的。执行 buildah unshare 命令将在用户命名空间中以 root 身份运行的命名空间中运行 shell 命令。现在您可以运行任何命令,包括上面描述的 buildah 命令。由于这些命令已经在命名空间中, buildah mount 命令将像在 rootful 模式下一样工作。一切都发生在命名空间内,用户得到他们所期望的。

现在用户可以执行上面列出的命令并创建一个 shell 脚本。然后使用 buildah unshare 命令直接执行此 shell 脚本。

cat > buildahimage.sh << EOF
ctr=$(buildah from scratch)
mnt=$buildah mount $ctr)
dnf -y --installroot $mnt httpd 
buildah config --entrypoint .... $ctr
buildah commit $ctr IMAGE
buildah push IMAGE REGISTRY
EOF

chmod +x ./buildahimage.sh
buildah unshare ./buildimage.sh

无根 Buildah 的工作原理:在非特权环境中构建容器

Buildah 是用于构建开放容器计划 (OCI) 容器映像的工具和库。

我们都知道容器镜像的特征:

  • rootfs 文件(etc/usr/bin)
  • 容器镜像的第二部分是描述 rootfs 内容的 JSON 文件。它包含运行容器的命令、入口点、运行容器所需的环境变量、容器的工作目录等字段。

基本上,这个 JSON 文件允许容器镜像的开发人员描述容器镜像的预期使用方式。此 JSON 文件中的字段已在 OCI 图像格式规范中标准化。

然后将 rootfs 和 JSON 文件打包在一起以创建存储在容器注册表中的映像包。要创建分层映像,您需要在 rootfs 中安装更多软件并修改 JSON 文件。

在很多年前,Docker 推出了 Dockerfile,这是一种用于构建镜像的脚本化语言,不得不说设计的很成功。但是它也有很多的缺点:

  • Dockerfile 鼓励在容器镜像中包含用于构建容器的工具。容器镜像不需要包含 yum/dnf/apt,但大多数包含其中之一及其所有依赖项。
  • 每行都会创建一个图层。因此, secret 可能会被错误地添加到容器镜像中。如果您在 Dockerfile 的一行中创建一个 secret并在下一行中将其删除,则该 secret 仍在图像中。

Note that umociopen in new window is an alternative to docker build that allows you to build container images without Dockerfile.

我们使用 buildah 能做什么?

我们使用 Buildah 的目标是构建一个简单的工具,它可以在磁盘上创建一个 rootfs 目录并允许其他工具填充该目录,然后创建 JSON 文件。

最后,Buildah 将创建 OCI 映像并将其推送到容器注册表,供任何容器引擎使用,例如 Dockeropen in new window, Podman, CRI-Oopen in new window, or another Buildah

Buildah 还支持 Dockerfile,因为我们知道大多数构建容器的人都创建了 Dockerfile。

直接使用 buildah

很多人直接使用 Buildah。 Buildah 的一个很酷的功能是您可以直接在 Bash 中编写容器构建脚本。

下面的示例创建了一个名为 myapp.sh 的 Bash 脚本,它使用 Buildah 来拉下 Fedora 镜像,然后在一台机器上使用 dnf 和 make 将软件安装到容器镜像 rootfs, $mnt 。然后它使用 buildah config 将一些字段添加到 JSON 文件,并将容器提交到容器镜像 myapp。最后,它将容器镜像推送到容器注册表 quay.io。 (它可以将它推送到任何容器注册表。)现在这个 OCI 镜像可以被任何容器引擎或 Kubernetes 使用。

cat myapp.sh
#!/bin/bash
ctr=$(buildah from fedora)
mnt=$(buildah mount $ctr)
dnf -y install --installroot $mnt httpd
make install DESTDIR=$mnt myapp
rm -rf $mnt/var/cache $mnt/var/log/*
buildah config --command /usr/bin/myapp -env foo=bar --working-dir=/root $ctr
buildah commit $ctr myapp
buildah push myapp http://quay.io/username/myapp

要创建非常小的镜像,您可以将上面脚本中的 fedora 替换为 scratch,Buildah 将构建一个容器镜像,该容器镜像内仅包含 httpd 包的要求。不需要 Python 或 DNF。

🤔 现在我们能更好的理解了 buildah 和 podman 区别:

借助 Buildah,我们拥有了一个用于构建容器镜像的低级工具。 Buildah 还为其他工具提供了一个库来构建容器镜像。 Podman 旨在取代 Docker 命令行界面 (CLI)。 Docker CLI 命令之一是 docker build。我们需要 podman build 来支持使用 Dockerfiles 构建容器镜像。 Podman 在 Buildah 库中出售,以允许它进行 podman 构建。每次进行 podman 构建时,您都在执行 Buildah 代码来构建容器镜像。如果您只打算使用 Dockerfiles 构建容器镜像,我们建议您只使用 Podman;根本不需要 Buildah。

buildah problems whit rootless

buildah 配置后支持 rootless ,默认下

bash-4.2$ buildah
Error during unshare(CLONE_NEWUSER): Invalid argument
User namespaces are not enabled in /proc/sys/user/max_user_namespaces.
ERRO error parsing PID "": strconv.Atoi: parsing "": invalid syntax 
ERRO (unable to determine exit status)  

Buildah 在无根模式下运行良好。它以与 Podman 相同的方式使用用户命名空间。如果你执行:

buildah bud --tag myapp -f Dockerfile .
buildah push myapp http://quay.io/username/myapp

在您的主目录中,一切正常。但是,如果你在普通用户执行上面描述的脚本,它就会失败!

问题在于,在无根模式下运行 buildah mount 命令时,buildah 命令必须将其自身放入用户命名空间并创建一个新的挂载命名空间。当不在用户命名空间中运行时,无根用户不允许挂载文件系统。

Podman 和 Buildah 等无根容器引擎在执行时会自动创建自己的用户命名空间和挂载命名空间。当容器引擎进程退出时,用户和挂载命名空间消失,用户进程回到主机挂载命名空间。此时,运行工具时创建的挂载不再对主机上的其他进程可见或无法使用。

当 Buildah 可执行文件退出时,用户命名空间和挂载命名空间消失,因此挂载点不再存在。这意味着在 buildah mount 之后尝试写入 $mnt 的命令将失败,因为 $mnt 不再被挂载。

我们怎样才能使脚本在无根模式下工作?

fuse-overlayfs

FUSE 中用于无根容器的 overlay+shiftfs 的实现。

启用支持以构建非特权容器

希望使用 Buildah 构建非特权容器的用户需要在首次运行 podman 之前完成额外的设置步骤。

注意:如果您正在构建专用于运行非特权容器的系统,请在添加任何用户之前按照以下步骤操作。这样您就不必编辑 /etc/subuid/etc/subgid :useradd 会为您做这些,您只需要以root 身份运行 touch /etc/subgidtouch /etc/subuid

最后,创建 /etc/subuid/etc/subgid 以包含到每个能够运行容器的用户的容器化 UID/GID 对的映射。 以下示例适用于 root 用户(和 systemd 系统单元)和示例用户 buildah

echo "buildah:100000:65536" >> /etc/subuid
echo "buildah:100000:65536" >> /etc/subgid 

如果您在应用上述更改之前确实运行了 podman,则在尝试以非特权用户身份拉取图像时会出现错误。运行 podman system migrate 来修复它。

如果一切顺利,那么在注销并重新登录 buildah images 后应该不会导致错误

⚠️ 注意:如果您看到访问 /run/user/0 时出现错误,那么您可能已经使用 su 成为您用于测试的用户 — 您应该以这样的用户身份登录,因为没有 --login 标志的 su 不会设置 XDG_RUNTIME_DIR 和其他环境变量来修正值。

# mount --make-shared /

要永久设置它,编辑/etc/fstabopen in new window并将共享选项添加到所需的挂载并重新启动。它将产生如下条目:

# <设备> <目录> <类型> <选项> <转储> <fsck> 
UUID=0a3407de-014b-458b-b5c1-848e92a327a3 /     ext4   defaults,shared   0      1

Podman

在允许没有 root 权限的用户运行 Podman 之前,管理员必须安装或构建 Podman 并完成以下配置。

install

centos:

Podman 在 CentOS 7 的默认 Extras 存储库和 CentOS 8 和 Stream 的 AppStream 存储库中可用。

sudo yum -y install podman

ubuntu:

sudo apt-get -y update
sudo apt-get -y install podman

构建

必须的:

sudo yum install -y \
  btrfs-progs-devel \
  conmon \
  containernetworking-plugins \
  containers-common \
  crun \
  device-mapper-devel \
  git \
  glib2-devel \
  glibc-devel \
  glibc-static \
  go \
  golang-github-cpuguy83-md2man \
  gpgme-devel \
  iptables \
  libassuan-devel \
  libgpg-error-devel \
  libseccomp-devel \
  libselinux-devel \
  make \
  pkgconfig

crun 安装失败,从源码安装:

测试过部分机器,Ubuntu:apt install crun -y ,CentOs:yum install -y crun。也会出现没有包的现象,两种方式,1. 编译安装; 2. 修改源

git clone https://github.com/containers/crun && cd crun

Tumbleweed 要求您将 libseccomp 的头文件位置指定为编译器标志。

# ./autogen.sh
# ./configure CFLAGS='-I/usr/include/libseccomp'
# make

root 模式 和 rootless 模式:

除非你也在构建 Python 绑定,否则 python 只需要 libocispec 在构建时生成 C 解析器,之后不会使用它。

$ ./autogen.sh
$ ./configure
$ make

要安装到默认前缀 ( /usr/local):

sudo make install

**⚠️ 发现错误,参考 https://github.com/containers/crun/issues/359 **

查看镜像

发现一个错误:

[root@iZuf68xky083mr0yy6q37lZ ~]# podman image list
Error: Could not get runtime: default OCI runtime "runc" not found: invalid argument

📜 对上面的解释:

cgroup V2 Linux 内核功能允许用户限制无根容器可以使用的资源量。如果运行 Podman 的 Linux 发行版启用了 cgroup V2,那么您可能需要更改默认的 OCI 运行时。一些旧版本runc不能与 cgroup V2 一起使用,您可能必须切换到替代的 OCI 运行时crun

--runtime也可以使用以下选项在命令行中打开对 cgroup V2 的替代 OCI 运行时支持:

podman --runtime crun

containers.conf或者对于所有命令,通过在系统级别或用户级别open in new window将文件中“默认 OCI 运行时”的值从runtime = "runc"更改为runtime = "crun".

安装 slirp4netns

slirp4netns包为非特权open in new window网络命名空间提供用户模式网络,必须安装在机器上才能让 Podman 在无根环境中运行。大多数 Linux 发行版都可以通过它们的软件包分发软件(例如yumdnfaptzypper等)获得该软件包。如果该软件包不可用,您可以slirp4netnsGitHubopen in new window构建和安装。

确保fuse-overlayfs已安装

在无根环境下使用 Podman 时,建议使用fuse-overlayfs而不是 VFS 文件系统。为此,您fuse-overlayfs需要$PATH.

您的发行版可能已经在fuse-overlayfs包中提供了它,但请注意您至少需要0.7.6版。这尤其需要在 Ubuntu 发行版上检查,因为fuse-overlayfs默认情况下通常不会安装 0.7.6 版本在 20.04 之前的 Ubuntu 版本上本机不可用。

fuse-overlayfs项目可从GitHubopen in new window获得,并提供了轻松构建静态fuse-overlayfs可执行文件的说明。

如果在安装之前使用 Podman fuse-overlayfs,可能需要调整storage.conf文件(请参阅下面的“用户配置文件”)以更改 to 下的选项driver并将选项指向可执行文件的路径:

[storage] "overlay" mount_program [storage.options.overlay] fuse-overlayfs

[storage]
  driver = "overlay"

  (...)

[storage.options.overlay]

  (...)

  mount_program = "/usr/bin/fuse-overlayfs"

启用用户命名空间 (centos 7/8 测试)

文件中指定了系统允许的用户命名空间数/proc/sys/user/max_user_namespaces。在大多数 Linux 平台上,这是默认预设的,无需调整。但是,在 RHEL7 机器上,具有 root 权限的用户可能需要使用以下命令将其设置为合理的值: sysctl user.max_user_namespaces=15000

# cat /proc/sys/user/max_user_namespaces
0

# sysctl user.max_user_namespaces=15000
user.max_user_namespaces = 15000

# cat /proc/sys/user/max_user_namespaces
15000

Ubuntu 20.04 test:

root@cubmaster01:~/workspces# cat /proc/sys/user/max_user_namespaces
15251

/etc/subuid/etc/subgid配置

Rootless Podman 要求运行它的用户在文件/etc/subuid/etc/subgid. shadow-utils或包在不同的newuid发行版上提供这些文件,它们必须安装在系统上。添加或更新这些文件中的条目需要根权限。

对于将被允许创建容器的每个用户,使用如下所示的字段更新/etc/subuid/etc/subgid为用户更新。请注意,每个用户的值必须是唯一的。如果存在重叠,则用户有可能使用另一个用户的命名空间,他们可能会破坏它。

$ cat /etc/subuid
sealos:100000:65536
sealer:165536:65536
root:200000:1001
buildah:100000:65536

$ cat /etc/subgid
sealos:100000:65536
sealer:165536:65536
root:200000:1001
buildah:100000:65536

这个文件的格式是USERNAME:UID:RANGE

  • /etc/passwd中或输出中列出的用户名getpwentopen in new window
  • 为用户分配的初始 UID。
  • 为用户分配的 UID 范围的大小。

这意味着为用户sealer分配了 UID 100000-165535 以及/etc/passwd文件中的标准 UID。注意:网络安装目前不支持此功能;这些文件必须在主机本地可用。无法使用 LDAP 或 Active Directory 进行配置。

如果更新/etc/subuid/etc/subgid,则需要停止该用户拥有的所有正在运行的容器,并终止该用户在系统上运行的暂停进程。这可以通过使用命令自动完成,该podman system migrateopen in new window命令将为用户停止所有容器并终止暂停进程。

usermod该程序可用于将 UID 和 GID 分配给用户,而不是直接更新文件:

usermod --add-subuids 100000-165535 --add-subgids 100000-165535 johndoe
grep johndoe /etc/subuid /etc/subgid
/etc/subuid:johndoe:100000:65536
/etc/subgid:johndoe:100000:65536

启动非特权 ping

在非特权容器中运行的用户可能无法使用该ping容器中的实用程序。

如果需要,管理员必须验证用户的 UID 是否在/proc/sys/net/ipv4/ping_group_range文件范围内。

要更改其值,管理员可以使用类似于以下的调用:sysctl -w "net.ipv4.ping_group_range=0 2000000"

为了使更改持久化,管理员将需要添加一个.conf文件扩展名,/etc/sysctl.d其中包含net.ipv4.ping_group_range=0 $MAX_GID,其中$MAX_GID是运行容器的用户的最高可分配 GID。

用户操作

在无根环境中运行 Podman 所需的大部分工作都落在机器管理员的肩上。

一旦管理员在机器上完成设置,然后在/etc/subuid和中为用户完成配置/etc/subgid,用户就可以开始使用他们希望使用的任何 Podman 命令。

用户配置文件

root 的 Podman 配置文件位于/usr/share/containers/etc/containers. 在无根环境中,它们位于${XDG_CONFIG_HOME}/containers(通常~/.config/containers)并由每个用户拥有。

三个主要配置文件是containers.confopen in new windowstorage.confopen in new windowregistries.confopen in new window。用户可以随意修改这些文件。

容器配置文件

Podman 读取

  1. /usr/share/containers/containers.conf
  2. /etc/containers/containers.conf
  3. $HOME/.config/containers/containers.conf

如果它们按该顺序存在。每个文件都可以覆盖特定字段的前一个文件。

存储配置文件

因为storage.conf订单是

  1. /etc/containers/storage.conf
  2. $HOME/.config/containers/storage.conf

在无根 Podman 中,某些字段/etc/containers/storage.conf被忽略。这些字段是:

graphroot=""
 container storage graph dir (default: "/var/lib/containers/storage")
 Default directory to store all writable content created by container storage programs.

runroot=""
 container storage run dir (default: "/run/containers/storage")
 Default directory to store all temporary writable content created by container storage programs.

在无根 Podman 中,这些字段默认为

graphroot="$HOME/.local/share/containers/storage"
runroot="$XDG_RUNTIME_DIR/containers"

$XDG_RUNTIME_DIRopen in new window在大多数系统上默认为/run/user/$UID.

注册配置文件

注册表配置按此顺序读入

  1. /etc/containers/registries.conf
  2. /etc/containers/registries.d/*
  3. HOME/.config/containers/registries.conf

主目录中的文件应该用于根据个人需要配置无根 Podman。默认情况下不创建这些文件。用户可以复制/usr/share/containers/etc/containers修改文件。

授权文件

podman login和命令使用的默认授权文件podman logout位于${XDG_RUNTIME_DIR}/containers/auth.json.

使用卷

Rootless Podman 不是,也永远不会是 root;它不是setuid二进制文件,运行时不会获得任何特权。相反,Podman 使用用户命名空间来转移它在主机上(通过newuidmapnewgidmap可执行文件)访问的一组用户的 UID 和 GID,以及 Podman 创建的容器中您自己的用户。

如果您的容器以 root 用户运行,那么root容器中实际上是您在主机上的用户。UID/GID 1 是用户在/etc/subuid/etc/subgid等中的映射中指定的第一个 UID/GID。如果您以无根用户身份将主机中的目录挂载到容器中,并在容器中以根用户身份在该目录中创建文件,则您会看到它实际上由您的用户在主机上拥有。

💡简单的一个案例如下:

> whoami
john

# a folder which is empty
host> ls /home/john/folder
host> podman run -v /home/john/folder:/container/volume mycontainer /bin/bash

# Now I'm in the container
root@container> whoami
root
root@container> touch /container/volume/test
root@container> ls -l /container/volume
total 0
-rw-r--r-- 1 root root 0 May 20 21:47 test
root@container> exit

# I check again
host> ls -l /home/john/folder
total 0
-rw-r--r-- 1 john john 0 May 20 21:47 test

我们确实认识到,这并不真正符合有多少人打算使用无根Podman -他们希望他们的UID在容器内外相匹配。因此,我们提供了 --userns=keep-id 标志,它确保您的用户映射到容器内自己的UID和GID。

区分作为无根用户运行Podman和为运行无根用户而构建的容器也很有帮助。如果您尝试运行的容器有一个 USER ,而不是根目录,那么在挂载卷时,您必须使用 --userns=keep-id 。这是因为容器用户将无法成为 root 并访问装载的卷。

  • 在提供要绑定装载的目录的路径时,需要提供绝对路径或以 . (点)开头的相对路径,否则字符串将被解释为已命名卷的名称。

Podman 在 rootless 下是如何工作的

Podman是libpod库的一部分,使用户能够管理pod、容器和容器映像。

我们在 docker open in new window 中了解到,用户名称空间是分隔容器的一个很好的特性。用户命名空间允许指定用户标识符(UID)和组标识符(GID)映射以运行容器。

这意味着您可以在容器内以 UID 0 运行,在容器外以 UID 100000 运行。如果容器进程脱离容器,内核 会将它们视为 UID 100000

不仅如此,UID 拥有的任何文件对象如果没有映射到用户名称空间,都将被视为"nobody"(65534,kernel. overflowuid)拥有,并且容器进程将不被允许访问,除非该对象可以被"other"(完全可读/可写)访问。

如果您有一个由"真实"根拥有权限 660 的文件,并且用户名称空间中的容器进程试图读取它,则它们将被阻止访问它,并将看到该文件不为任何人拥有。

work theory:

$ sudo bash -c "echo Test > /tmp/test"
$ sudo chmod 600 /tmp/test 
$ sudo ls -l /tmp/test 
-rw-------. 1 root root 5 Dec 17 16:40 /tmp/test

接下来,我将文件卷装载到一个使用用户名称空间映射 0:100000:5000 运行的容器中。

[sealer@iZuf~]$ sudo podman run -ti -v /tmp/test:/tmp/test:Z --uidmap 0:100000:5000 fedora sh
sh-5.2# id
uid=0(root) gid=0(root) groups=0(root)
sh-5.2# ls -al /tmp/test
-rw------- 1 nobody nobody 5 Feb 10 05:42 /tmp/test
sh-5.2# cat /tmp/test
cat: /tmp/test: Permission denied

对于 podman 的命令的解释说明:

上面的 --uidmap 设置告诉Podman将容器内的5000个UID映射到容器外的UID 100000(因此范围是100000 - 104999)到容器内的UID 0(因此范围是0 - 4999)。

在容器内,如果我的进程以UID 1运行,则它在主机上为100001

因为真实的的 UID=0 没有映射到容器中,所以root拥有的任何文件都将被视为无人拥有。即使容器内的进程具有CAP_DAC_OVERRIDE,它也不能覆盖此保护。DAC_OVERRIDE使根进程能够读/写系统上的任何文件,即使该进程不归根所有或完全不可读或可写。

用户命名空间权能与主机上的权能不同。它们是命名空间权能。这意味着我的容器根目录(rootfs)仅在容器内具有功能-实际上仅在映射到用户名称空间的UID范围内具有功能。

如果一个容器进程脱离了容器,那么它将不具有任何针对未映射到用户名称空间的UID(包括UID=0)的功能。

即使进程可以以某种方式进入另一个容器,如果容器使用不同范围的UID,它们也不会具有这些功能。

使用“podman top”显示用户名称空间

使用UID映射运行一个休眠容器。

$ sudo podman run --uidmap 0:100000:5000 -d fedora sleep 1000

现在就可以 运行 podman :

$  sudo podman top --latest user huser
USER   HUSER
root   ?

$ ps -ef | grep sleep
sealer    1513 30899  0 14:00 pts/1    00:00:00 grep --color=auto sleep
100000   31026 31014  0 13:58 ?        00:00:00 sleep 1000

注意,podman top 报告用户进程在容器内以root身份运行,但在主机(HUSER)上以UID 100000 运行。ps命令还确认休眠进程正在以 UID 100000 运行。

现在让我们运行第二个容器,但这次我们将选择一个从 200000 开始的单独UID映射。

$ sudo podman run --uidmap 0:200000:5000 -d fedora sleep 1000
b5d72d262f89d1db0bc60ebdd2ef3ddc425932c593e83d2991f5921f563d9766

$ sudo podman top --latest user huser
USER   HUSER
root   ?

$ ps -ef | grep sleep
200000    6466  6454  0 14:04 ?        00:00:00 sleep 1000
sealer    6985 30899  0 14:05 pts/1    00:00:00 grep --color=auto sleep
100000   31026 31014  0 13:58 ?        00:00:00 sleep 1000

⚠️ 注意,podman top 报告第二个容器在容器内以root用户身份运行,但在主机上的UID=200000

还可以查看ps命令-它显示两个休眠进程都在运行:一个为100000,另一个为200000。

这意味着在单独的用户名称空间中运行容器可以在进程之间提供传统的UID分离,这从一开始就是 Linux/Unix 的标准安全工具。

user namaspace

在容器中,您希望在多个容器之间共享基本映像。上面的例子在每个例子中都使用了Fedora基础映像。Fedora映像中的大多数文件都是真实的 UID=0 的文件。

如果我在这个映像上运行一个容器,其用户名称空间为 0:100000:5000,默认情况下,它会将所有这些文件视为无人拥有,因此我们需要移动所有这些UID以匹配用户名称空间。

Podman可以在同一个映像上使用不同的用户名称空间,因为Nalin Dahyabhai领导的团队在容器/存储中内置了自动chowning。Podman使用容器/存储,并且Podman第一次在新用户命名空间中使用容器映像时,容器/存储“chowns”(即改变所有权)映像中的所有文件映射到用户名称空间中映射的UID,并创建新映像。将此图像视为:0:100000:5000 图像。

当Podman在具有相同UID映射的映像上运行另一个容器时,它使用“预chowned”映像。当我在 0:200000:5000上运行第二个容器时,containers/storage 会创建第二个映像,我们将其命名为fedora:0:200000:5000

注意,如果您正在执行podman构建或podman提交,并将新创建的映像推送到容器注册表中,则Podman将使用容器/存储来反向转换,并将包含所有文件的映像推回真实的UID=0。

这可能会导致在新UID映射中创建容器的真实的慢,因为chown可能会很慢,具体取决于映像中的文件数量。同样,在普通的 OverlayFS 上,映像中的每个文件都被复制。正常的Fedora映像可能需要30秒来完成chown并启动容器。

going forward

我想给Podman添加一个新的标志,比如 --userns=auto,告诉它为您运行的每个容器自动选择一个唯一的用户名称空间。这类似于SELinux使用单独的多类别安全(MCS)标签的方式。如果设置环境变量 PODMAN_USERNS=auto,则甚至不需要设置标志。

Podman终于允许用户在单独的用户名称空间中运行容器。像BuildahCRI-O这样的工具也将能够利用用户名称空间。但是对于 CRI-O,Kubernetes需要了解哪个用户命名空间将运行容器引擎,上游正在处理这个问题。

在我看来,最好的特性是以非root用户身份运行Podman和containers。这意味着您从未在主机上给予用户root权限,而在客户机/服务器模型中(如Docker使用的),您必须打开一个套接字,连接到以root身份运行的特权守护进程以启动容器。这样,您就处于守护进程中实现的安全机制与主机操作系统中实现的安全机制之间--这是一个危险的命题。

Podman 如何创建用户名称空间

大多数当前的Linux发行版都包含一个 shadow-utils 版本,该版本使用/etc/subuid和 /etc/subgid 文件来确定用户名称空间中的用户可用的UID和GID。

我们创建用户的用户组的命令很熟练了:

useradd程序为添加到系统的每个用户自动分配65536UID。如果 系统上已有用户,则需要自己分配UID。这些文件的格式为用户名:STARTUID:TOTALUIDS

这意味着dwalsh被分配了 UID 100000165535 沿着我的默认UID,它恰好是在 /etc/passwd 中定义的3265。在分配这些UID范围时需要小心,不要与系统上的任何realUID重叠。如果您有一个用户被列为 UID 100001,现在我(dwalsh)将能够成为这个UID,并可能读取/写入/执行该UID所拥有的文件。

Shadow-utils 还添加了两个setuid程序(或setfilecap)。在Fedora上我有:

# getcap /usr/bin/newuidmap
/usr/bin/newuidmap = cap_setuid+ep
# getcap /usr/bin/newgidmap
/usr/bin/newgidmap = cap_setgid+ep

Podman执行这些文件来设置用户名称空间。您可以通过从无根容器内部检查 /proc/self/uid_map/proc/self/gid_map 来查看映射。

$ podman run alpine cat /proc/self/uid_map /proc/self/gid_map
     	0   	3267      	1
     	1 	100000  	65536
     	0   	3267      	1
     	1 	100000  	65536

如上所述,Podman默认将容器中的根映射到您当前的UID(3267),然后映射 /etc/subuid/etc/subgid 中从1开始的已分配UID/GID的范围。

UID=1的容器是UID 100000,UID=2的容器是UID 100001,一直到65536,即165535。

用户名称空间之外的任何项,如果由未映射到用户名称空间的UID或GID拥有,则看起来属于在内核中配置的用户。overflowuid sysctl,默认值为35534,我的/etc/passwd文件将其命名为nobody。由于您的进程不能以未映射的ID运行,因此所有者和组权限不适用,因此您只能基于它们的“其他”权限访问这些文件。这包括运行容器的系统上真实的root拥有的所有文件,因为root没有映射到用户名称空间。

buildah unshareopen in new window会将您置于Podman运行所在的用户 (sealer) 名称空间中,但不需要进入容器的文件系统,因此您可以列出主目录的内容。

$ ls -ild /home/sealer 
131079 drwx------ 10 sealer root 4096 Feb 10 13:46 /home/sealer

$ buildah unshare ls -ld /home/sealer 
drwx------ 10 root root 4096 Feb 10 13:46 /home/sealer

注意,当列出用户名称空间之外的 home dir 属性时,内核将所有权报告为 sealer,而在用户名称空间之内,它将目录报告为 root所有。

这是因为主目录归3267所有,并且在用户名称空间中,我们将该UID视为 rootfs

设置命名空间后,podman 接下来会发生什么

Podman使用 containers/storage 来拉取容器映像,containers/storage 非常智能,可以将映像中 root 拥有的所有文件映射到用户名称空间的 根(rootfs),并将不同 UID 拥有的任何其他文件映射到它们的用户名称空间 UID。默认情况下,此内容将写入 ~/

本地/共享/容器/存储。容器存储在 vfs 模式或Overlay下的 rootless (non root) 下工作。注:只有安装了fuse-overlayfs可执行文件,才支持覆盖。

内核只允许用户命名空间根挂载某些类型的文件系统;现在它允许挂载procfssysfstmpfsfusefsbind挂载(只要源和目标是由运行Podman的用户拥有的。

如果集装箱使用保险丝覆盖,则Podman安装集装箱的存储器;如果存储驱动器使用vfs,则不需要安装。vfs上的podman需要大量的空间,因为每个容器复制整个底层文件系统。

然后,Podman挂载 /proc/sys沿着一些tmpfs,并在容器中创建设备。

为了使用主机网络以外的网络,Podman 使用 slirp 4 netns 程序为非特权网络名称空间设置用户模式网络。Slirp 4 netns允许 Podman 向主机公开容器内的端口。注意,内核仍然不允许非特权进程绑定到小于1024的端口。需要Podman-1.1或更高版本才能绑定到端口。

Rootless Podman可以使用用户名称空间进行容器分离,但是您只能访问 /etc/subuid 文件中定义的UID。