在之前的文章中,包括 无需root权限的 Podman 工作原理是什么?,我谈到了 Podman,它是一个允许用户管理 Pod、容器和容器镜像的工具。
Buildah 是一个用于构建开放容器倡议 (OCI) 容器镜像的工具和库,它与 Podman 互补。(这两个项目都由 containers 组织维护,我也是该组织的成员。)在本文中,我将讨论无需 root 权限的 Buildah,包括它与 Podman 之间的区别。
我们使用 Buildah 的目标是构建一个低级工具,可以直接使用或集成到其他工具中来构建容器镜像。
为什么要使用 Buildah?
以下是我描述容器镜像的方式:它基本上是一个 rootfs 目录,其中包含运行容器所需的代码。此目录称为 rootfs,因为它通常看起来像 Linux 机器上的 / (根),这意味着您可能会在 rootfs 中找到像 /etc、/usr、/bin 等目录。
容器镜像的第二部分是一个 JSON 文件,用于描述 rootfs 的内容。它包含诸如运行容器的命令、入口点、运行容器所需的环境变量、容器的工作目录等字段。基本上,此 JSON 文件允许容器镜像的开发者描述容器镜像的预期使用方式。此 JSON 文件中的字段已在 OCI 镜像格式规范 中标准化。
然后,rootfs 和 JSON 文件会被 tar 压缩在一起,以创建一个存储在容器注册表中的镜像包。要创建分层镜像,您需要将更多软件安装到 rootfs 中并修改 JSON 文件。然后,您将新 rootfs 和旧 rootfs 的差异打包成 tar 文件,并将其存储在另一个镜像 tarball 中。第二个 JSON 文件通过校验和引用第一个 JSON 文件。
多年前,Docker 引入了 Dockerfile,这是一种简化的脚本语言,用于构建容器镜像。Dockerfile 非常棒并且真正流行起来,但它有许多用户抱怨的缺点。例如
-
Dockerfile 鼓励将用于构建容器的工具包含在容器镜像中。容器镜像不需要包含 yum/dnf/apt,但大多数都包含其中一个及其所有依赖项。
-
每一行都会导致创建一个层。因此,机密可能会错误地添加到容器镜像中。如果您在 Dockerfile 的一行中创建了一个机密,然后在下一行中将其删除,则该机密仍然存在于镜像中。
我对“容器革命”的最大抱怨之一是,自从它开始已经六年了,构建容器镜像的唯一方法仍然是使用 Dockerfiles。除了 Buildah 之外,还出现了许多工具而不是 docker build,但大多数工具仍然只处理 Dockerfile。因此,用户继续破解 Dockerfile 的问题。
请注意,umoci 是 docker build 的替代方案,允许您构建容器镜像而无需 Dockerfile。
我们使用 Buildah 的目标是构建一个简单的工具,该工具只需在磁盘上创建一个 rootfs 目录,并允许其他工具填充该目录,然后创建 JSON 文件。最后,Buildah 将创建 OCI 镜像并将其推送到容器注册表,任何容器引擎(如 Docker、Podman、CRI-O 或另一个 Buildah)都可以使用它。
Buildah 还支持 Dockerfile,因为我们知道构建容器的大部分人已经创建了 Dockerfiles。
直接使用 Buildah
很多人直接使用 Buildah。Buildah 的一个很棒的功能是,您可以直接在 Bash 中编写容器构建脚本。
下面的示例创建一个名为 myapp.sh 的 Bash 脚本,该脚本使用 Buildah 拉取 Fedora 镜像,然后使用机器上的 dnf 和 make 将软件安装到容器镜像 rootfs $mnt 中。然后,它使用 buildah config 将一些字段添加到 JSON 文件中,并将容器提交到容器镜像 myapp。最后,它将容器镜像推送到容器注册表 quay.io。(它可以将其推送到任何容器注册表。)现在,任何容器引擎或 Kubernetes 都可以使用此 OCI 镜像。
cat myapp.sh
#!/bin/sh
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
要创建真正小的镜像,您可以使用 scratch 替换上面脚本中的 fedora,Buildah 将构建一个容器镜像,该镜像仅包含容器镜像内部 httpd 软件包的要求。无需 Python 或 DNF。
Podman 与 Buildah 的关系
通过 Buildah,我们有了一个用于构建容器镜像的低级工具。Buildah 还提供了一个库,供其他工具构建容器镜像。Podman 旨在取代 Docker 命令行界面 (CLI)。Docker CLI 命令之一是 docker build。我们需要有 podman build 来支持使用 Dockerfiles 构建容器镜像。Podman 集成了 Buildah 库以允许它执行 podman build。任何时候您执行 podman build,您都在执行 Buildah 代码来构建您的容器镜像。如果您只打算使用 Dockerfiles 来构建容器镜像,我们建议您只使用 Podman;根本不需要 Buildah。
使用 Buildah 库的其他工具
Podman 并不是唯一利用 Buildah 库的工具。OpenShift 4 Source-to-Image (S2I) 也将使用 Buildah 来构建容器镜像。OpenShift S2I 允许使用 OpenShift 的开发者使用 Git 命令来修改源代码;当他们将源代码的更改推送到 Git 存储库时,OpenShift 会启动一个作业来编译源代码更改并创建一个容器镜像。它还在底层使用 Buildah 来构建此镜像。
Ansible-Bender 是一个通过 Ansible playbook 构建容器镜像的新项目。对于熟悉 Ansible 的人来说,Ansible-Bender 可以轻松描述容器镜像的内容,然后使用 Buildah 打包容器镜像并将其发送到容器注册表。
我们很乐意看到其他工具和语言用于描述和构建容器镜像,并欢迎其他人使用 Buildah 来进行转换。
无需 root 权限的问题
Buildah 在无需 root 权限的模式下工作正常。它以与 Podman 相同的方式使用用户命名空间。如果您执行
$ buildah bud --tag myapp -f Dockerfile .
$ buildah push myapp http://quay.io/username/myapp
在您的主目录中,一切正常。
但是,如果您执行上面描述的脚本,它将失败!
问题是,当在无需 root 权限的模式下运行 buildah mount 命令时,buildah 命令必须将自身置于用户命名空间中并创建一个新的挂载命名空间。不允许无 root 权限的用户在未在用户命名空间中运行时挂载文件系统。
当 Buildah 可执行文件退出时,用户命名空间和挂载命名空间会消失,因此挂载点不再存在。这意味着在 buildah mount 之后尝试写入 $mnt 的命令将失败,因为 $mnt 不再挂载。
我们如何使脚本在无需 root 权限的模式下工作?
Buildah unshare
Buildah 有一个特殊的命令 buildah unshare,允许您进入用户命名空间。如果您在不使用任何命令的情况下执行它,它将在用户命名空间中启动一个 shell,您的 shell 看起来像是在以 root 身份运行,并且主目录的所有内容看起来都归 root 所有。如果您查看 /usr 中的所有者或文件,它会将它们列为 nfsnobody(或 nobody)所有。这是因为您的用户 ID (UID) 现在是用户命名空间中的 root,并且真正的 root (UID=0) 未映射到用户命名空间中。内核将用户命名空间中未映射的 UID 拥有的所有文件表示为 NFSNOBODY 用户。当您退出 shell 时,您将退出用户命名空间,您将回到正常的 UID,并且主目录将再次归您的 UID 所有。
如果您想执行上面定义的 myapp.sh 命令,您可以执行 buildah unshare myapp.sh,脚本现在将正确运行。
结论
现在可以在非特权环境中构建和运行容器,并且相当实用。开发者几乎没有理由以 root 用户身份开发容器。
如果你想使用传统的容器引擎,并使用 Dockerfile 进行构建,那么你应该直接使用 Podman。但如果你想尝试用新的方式构建容器镜像,而不使用 Dockerfile,那么你真的应该看看 Buildah。
6 条评论