- 云原生
【免费下载链接】buildah
A tool that facilitates building OCI images.
本文基于 Buildah 官方入门教程(docs/tutorials/01-intro.md)整理并深入扩展,带你完整走一遍 Buildah 构建 OCI 容器镜像的核心工作流:安装与验证、基于现有镜像创建 working container、从scratch空镜像逐层填充内容、提交与配置元数据,以及使用 Dockerfile/Containerfile 构建镜像。读完之后,你将掌握 Buildah CLI 的典型操作序列,并理解每个命令背后对应的源码实现与底层库(containers/image、containers/storage)的协作关系。
一、Buildah 与 OCI 镜像规范
Buildah 的目标是构建符合 OCI 镜像规范 的容器镜像。镜像既可以基于现有镜像扩展,也可以完全从空白(scratch)开始,还可以直接由 Dockerfile 驱动构建。
Buildah 的镜像能力建立在两个基础库之上:
- containers/image:提供镜像的复制(push、pull)、检视(inspect)与签名(sign)机制;
- containers/storage:提供文件系统层(layers)、容器镜像以及容器本身的存储机制。
Buildah 本身是一个 CLI,它利用上述两个库来构建、移动和管理容器镜像与工作容器。由于产物严格遵循 OCI 标准,用 Buildah 构建的镜像可以在 Docker 等其他容器环境中运行。
需要明确的适用边界:
- Buildah 支持多种 Linux 发行版,但不支持 Windows 或 macOS;
- Buildah 专注于构建OCI 镜像,而 Podman 提供覆盖面更广的命令集(维护、修改、运行镜像与容器)。二者同属 containers 生态,定位互补。
二、安装 Buildah
官方入门教程以使用dnf包管理器的 Linux 发行版为前提,安装步骤如下(安装包需要 root 权限):
$ sudo -s # dnf -y install buildah仓库中的 install.md 给出了更多发行版的安装方式,包括:
- Debian/Ubuntu:
sudo apt-get -y install buildah; - Fedora:
sudo dnf -y install buildah; - CentOS:
sudo yum -y install buildah; - openSUSE:
sudo zypper install buildah; - Arch Linux:
sudo pacman -S buildah; - Gentoo:
sudo emerge app-containers/buildah。
除了发行版差异,还有两个关键前提值得注意:
- 内核要求:RHEL/CentOS 上要求 7.4 及以上内核;其他发行版需要支持 OverlayFS 或 fuse-overlayfs 的内核;
- runc 依赖:
buildah run执行命令、或buildah build遇到RUN指令时,Buildah 依赖runc来真正运行进程。通过 yum/dnf/apt 安装 Buildah 时通常会顺带装好 runc。
Rootless(免 root)运行
如果计划以非 root 用户(rootless user)运行 Buildah,系统管理员可能需要预先做额外配置。Buildah 对 rootless 用户的配置要求与 Podman 完全一致,可参考 Podman 官方的 rootless tutorial。仓库中的 buildah-unshare.1.md 则说明了后文会用到的buildah unshare命令:它用于创建并进入 user namespace 与 mount namespace。
三、安装后验证:images、containers 与 working container
安装完成后,先确认本地存储是空的。buildah images列出所有镜像,buildah containers列出所有 working container:
# buildah images # buildah containers此时两条命令都应该没有输出。接下来创建第一个 working container:
# container=$(buildah from fedora)这里有两点值得深入理解。
第一,buildah from的输出可以直接赋给 shell 变量。从源码看,cmd/buildah/from.go 中fromCmd在成功构建Builder之后执行fmt.Printf("%s\n", builder.Container),把容器名打印到标准输出——这正是教程中container=$(buildah from fedora)能够捕获名称的机制。
第二,working container 的命名规则。Buildah 默认会在基础镜像名后追加-working-container后缀来生成容器名,例如fedora-working-container。命名逻辑位于 new.go:
name := "working-container" if options.ContainerSuffix != "" { name = options.ContainerSuffix } if options.Container != "" { name = options.Container } else { if imageSpec != "" { name = imageNamePrefix(imageSpec) + "-" + name } }其中imageNamePrefix(new.go)会去掉镜像名中的 tag、digest(截断到 12 位)、@与/部分,只保留最后一段镜像名,从而得到fedora这样的前缀。如果同名容器已存在,findUnusedContainer会进一步追加编号避免冲突。
查看变量内容并进入容器执行命令:
# echo $container fedora-working-container # buildah run $container bash执行后会看到一个新的 shell 提示符,说明 bash 正在容器内运行。注意buildah run的定位:主要用于调试和构建过程中的命令执行。如果要在生产环境长期运行容器,更适合使用 Podman 或 CRI-O 这类完整的容器运行时。
buildah run中的--分隔符
退出容器后,教程演示了"容器内缺软件再装软件"的场景:
# buildah run $container java容器里没装 Java,会看到类似这样的报错:
runc create failed: unable to start start container process: exec: "java": executable file not found in $PATH于是需要在容器里安装 Java:
# buildah run $container -- dnf -y install java这里的--语法告诉 Buildah:其后不再解析buildah run自身的选项,--之后的所有内容都作为容器内命令的参数。当你要执行的容器内命令自身携带选项(如dnf -y install)时,使用--是必要的,可以防止选项被 Buildah 误吞。
之后再次运行buildah run $container java将输出 Java 的标准Usage信息,说明安装成功。
四、从 scratch 构建:真正"从零"的镜像
Buildah 的一个重要能力是从空白构建镜像:可以精确控制镜像内容,剔除生产环境不需要的组件(比如大多数生产镜像并不需要dnf这样的包管理器)。
4.1 创建空容器
特殊的"镜像名"scratch告诉 Buildah 创建一个空容器——它只带有少量元数据,没有任何实际的 Linux 内容:
# newcontainer=$(buildah from scratch) # buildah containers输出类似:
CONTAINER ID BUILDER IMAGE ID IMAGE NAME CONTAINER NAME 82af3b9a9488 * 3d85fcda5754 docker.io/library/fedora:latest fedora-working-container ac8fa6be0f0a * scratch working-container注意两点:
- 空容器的默认名字就是
working-container(对应 new.go 中的默认值,没有镜像名可作前缀); - 运行
buildah images时看不到名为scratch的镜像——scratch只是一个特殊值,表示该 working container 并非基于任何镜像,"从 nothing 开始"。
此时在容器里执行buildah run $newcontainer bash会失败——容器里连 bash、dnf 都没有,它本质上只是内核之上的一层空文件系统。
4.2 用 buildah mount 暴露容器根文件系统
要把内容"塞进"这个空容器,需要buildah mount命令:
# scratchmnt=$(buildah mount $newcontainer) # echo $scratchmnt /var/lib/containers/storage/overlay/b78d0e11957d15b5d1fe776293bd40a36c28825fb6cf76f407b4d0a95b2a200d/merged输出的路径是一个overlay 挂载点,它就是容器使用的根文件系统。以 root 运行时,overlay 挂载点位于/var/lib/containers/storage之下;rootless 模式下则位于家目录的.local/share/containers/storage之下——这正体现了底层 containers/storage 库的存储布局。
从源码看,buildah mount最终调用 mount.go 中的Builder.Mount:
func (b *Builder) Mount(label string) (string, error) { mountpoint, err := b.store.Mount(b.ContainerID, label) if err != nil { return "", fmt.Errorf("mounting build container %q: %w", b.ContainerID, err) } b.MountPoint = mountpoint err = b.Save() if err != nil { return "", fmt.Errorf("saving updated state for build container %q: %w", b.ContainerID, err) } return mountpoint, nil }可以看到它委托给存储驱动(b.store.Mount)完成实际的 overlay 挂载,并把挂载点持久化到 Builder 状态(b.Save()),供后续buildah unmount时清理。
rootless 注意事项:rootless 模式下直接执行buildah mount会失败,因为挂载容器必须在你自己拥有的 mount namespace 中进行。正确的做法是先用buildah unshare创建并进入 user namespace 与 mount namespace,再执行 mount,并且由于 shell 环境变了,需要把变量导出:
$ export newcontainer $ buildah unshare # scratchmnt=$(buildah mount $newcontainer)4.3 用 dnf installroot 向容器填充软件
拿到挂载点之后,就可以在宿主机上直接"装包进容器"。教程以安装bash与coreutils为例(换成nginx等任何需要的包也一样):
# dnf install --installroot $scratchmnt --releasever 42 bash coreutils --use-host-config --setopt "*.countme=false" --setopt installweak_deps=false -y关键参数是--installroot $scratchmnt:让 dnf 把包装进挂载目录而不是宿主机。教程特别提示:示例中的--releasever 42对应 Fedora 42,这个版本值必须对宿主机上的 dnf 有效——例如在 RHEL 平台上应写--releasever 8.1之类的有效版本。若希望容器最终基于某个特定发行版,可把buildah from scratch换成buildah from fedora,并使用该平台的版本值。
验证一下容器内确实有了/usr/bin:
# buildah run $newcontainer sh sh-5.1# cd /usr/bin sh-5.1# ls sh-5.1# exit4.4 copy、config、commit:完成第一张镜像
在宿主机上创建一个可执行脚本runecho.sh:
#!/usr/bin/env bash for i in `seq 0 9`; do echo "This is a new container from ipbabble [" $i "]" done# chmod +x runecho.sh然后三步完成镜像:
# buildah copy $newcontainer ./runecho.sh /usr/bin/ # buildah config --cmd /usr/bin/runecho.sh $newcontainer # buildah commit $newcontainer newimage这一步值得理解buildah run与podman run的本质区别,教程给出的类比非常准确:
buildah run等价于 Dockerfile 中的RUN:它永远需要你显式告诉它要运行什么命令;podman run等价于docker run:它可以读取镜像配置(即上一步buildah config --cmd写入的默认命令)来决定运行什么。
先用 Buildah 直接指定命令验证脚本可执行:
# buildah run $newcontainer /usr/bin/runecho.sh This is a new container from ipbabble [ 0 ] This is a new container from ipbabble [ 1 ] ... This is a new container from ipbabble [ 9 ]再用 Podman 基于新镜像起一个全新容器,不指定命令,它会自动执行镜像配置里的--cmd:
# dnf -y install podman # 先安装 # podman run --rm newimage This is a new container from ipbabble [ 0 ] ... This is a new container from ipbabble [ 9 ]输出一致,说明这个从 scratch 构建的镜像完全可用。
4.5 元数据、二次 commit 与清理
继续为工作容器补充元数据:
# buildah config --created-by "ipbabble" $newcontainer # buildah config --author "wgh at redhat.com @ipbabble" --label name=fedora42-bashecho $newcontainer # buildah inspect $newcontainer注意一个容易踩的坑:元数据修改发生在上一次 commit 之后,所以必须再次 commit 才能得到包含新元数据的镜像:
# buildah unmount $newcontainer # buildah commit $newcontainer fedora-bashecho # buildah images此时会看到新镜像localhost/fedora-bashecho:latest。检查镜像元数据用--type=image:
# buildah inspect --type=image fedora-bashecho之后每次需要基于该镜像起容器,直接buildah from fedora-bashecho即可。工作容器已完成使命,可以删除(按变量或按名字等价):
# buildah rm $newcontainer # buildah rm working-container五、可移植性:把镜像推给 Docker daemon
教程用一个实验证明 Buildah 产出的 OCI 镜像是标准、可移植的:安装并启动 Docker,然后把镜像从 containers/storage 的存储区复制到 Docker daemon 的存储区(/var/lib/docker):
# dnf -y install docker # systemctl start docker # buildah push fedora-bashecho docker-daemon:fedora-bashecho:latest # docker run --rm fedora-bashecho This is a new container from ipbabble [ 0 ] ... This is a new container from ipbabble [ 9 ]几个要点:
docker-daemon:是显式的 transport 前缀。docker://(默认,指向 Registry HTTP API V2)、docker-daemon:(指向本地 Docker daemon 的内部存储)、dir:、oci:、oci-archive:、docker-archive:等 transport 的完整定义见 buildah-from.1.md 与 buildah-push.1.md;- 底层机制:containers/image 库调用 containers/storage 库从 Buildah 的存储位置读出镜像内容,发送给本地 Docker daemon 写入其存储。教程提醒这一步通常用不到——用 Buildah 的人多半不用 Docker,这里仅作可移植性演示;
- 架构差异:Docker 必须依赖一个常驻 daemon 进程才能执行任何客户端命令;Buildah 与 Podman 则没有 daemon 依赖,命令直接操作存储与命名空间。
演示完成后可以dnf -y remove docker收尾。
六、使用 Containerfile/Dockerfile 构建镜像
如果你已有现成的 Dockerfile 资产,Buildah 可以无缝复用:build命令接受 Dockerfile 作为输入,产出 OCI 镜像。教程给出的示例 Dockerfile 如下:
# Base on the most recently released Fedora FROM fedora:latest MAINTAINER ipbabble email buildahboy@redhat.com # not a real email # Install updates and httpd RUN echo "Updating all fedora packages"; dnf -y update; dnf -y clean all RUN echo "Installing httpd"; dnf -y install httpd && dnf -y clean all # Expose the default httpd port 80 EXPOSE 80 # Run the httpd CMD ["/usr/sbin/httpd", "-DFOREGROUND"]执行构建:
# buildah build -f Dockerfile -t fedora-httpd .由于buildah build默认使用当前目录下的Dockerfile并以当前目录作为构建上下文,上式可以简写为:
# buildah build -t fedora-httpd构建过程中你会看到 Dockerfile 的每一步依次执行(FROM会先创建一个以<镜像名>-working-container命名的工作容器,RUN指令经由 runc 在容器内执行——这与前面buildah run的机制同源),完成后buildah images中出现新镜像。
用 Podman 起容器验证服务,并做端口映射:
# podman run --rm -p 8123:80 fedora-httpd另开一个 shell:
# curl localhost:8123看到标准的 Apache 欢迎页即验证成功。教程最后的开放练习:修改 Dockerfile,不安装 httpd,改用ADD指令引入前面的runecho.sh并把它设为CMD,再走一遍构建流程。
仓库的 tests/ 目录中有大量围绕 Dockerfile 行为的集成测试(如 tests/bud.bats 及各tests/bud/子目录的测试用例),可以结合源码进一步理解buildah build对COPY、ADD、多阶段构建、ARG/ENV等指令的具体处理逻辑。
七、小结:一条完整的 Buildah 心智模型
把上面的操作串起来,Buildah 的核心工作模型是四步:
buildah from <image|scratch>:创建 working container(源码入口 cmd/buildah/from.go,命名规则见 new.go);- 填充内容:
buildah run(在容器内执行命令)、buildah copy(拷入文件)、buildah mount+ 宿主工具(直接操作根文件系统,实现见 mount.go); buildah config:写入元数据(cmd、label、author 等);buildah commit:把工作容器的变更固化为 OCI 镜像,之后可通过buildah push发送到 registry、Docker daemon 等任意 transport。
所有产物严格遵循 OCI 规范,因此天然可移植。若想继续深入,建议按序阅读 docs/tutorials/ 下的后续教程(镜像仓库交互、ONBUILD 机制、把 Buildah 作为库集成进自有构建工具、rootless OpenShift 构建),以及各命令的 man page(docs/buildah-run.1.md、docs/buildah-commit.1.md、docs/buildah-mount.1.md、docs/buildah-config.1.md、docs/buildah-push.1.md 等),它们覆盖了本教程未展开的全部命令行参数。
- 云原生
【免费下载链接】buildah
A tool that facilitates building OCI images.
相关推荐
container构建系统详解:从Dockerfile到OCI镜像的完整流程
container构建系统详解:从Dockerfile到OCI镜像的完整流程 container构建系统是专门为Mac平台优化的Linux容器构建工具,采用Sw
CLI虚拟化容器运行时云原生Buildah: OCI镜像构建工具
Buildah: OCI镜像构建工具 1. 项目介绍 Buildah是一个开源命令行工具,用于构建Open Container Initiative OCI 容
云原生终极指南:Docker构建工具链如何从Dockerfile到OCI镜像的完整流程
终极指南:Docker构建工具链如何从Dockerfile到OCI镜像的完整流程 Docker构建工具链是现代容器化开发的核心,它通过Dockerfile指令逐
云原生容器运行时虚拟化容器编排
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考