Buildah 是一个专注于构建 OCI 镜像的工具,Buildah CLI 工具使用底层 OCI 技术实现(例如 containers/image[^1] 和 containers/storage[^2])。
OCI 三剑客包括:
- 专注于镜像构建的 Buildah
- 专注于镜像和容器管理的 Podman
- 专注于镜像操作和管理(尤其是涉及远程仓库的操作)的 Skopeo
这三者一起形成了一个 Dockerless 的容器生态,支持构建、管理、推送和操作镜像和容器,且不依赖 Docker 守护进程。
注意:三者之间功能是有一定重复的,特别是 Buildah 和 Podman,不过各自专注点不同,建议合理搭配使用。
1. 什么是 Buildah?
Buildah 是一个专注于构建 OCI 镜像的工具,Buildah CLI 工具使用底层 OCI 技术实现(例如 containers/image[^4] 和 containers/storage[^5])。
官方描述原文:
A tool that facilitates building OCI images.the Buildah command line tool (CLI) and the underlying OCI based technologies (e.g. containers/image[^6] and containers/storage[^7])
Buildah CLI 工具则基于这些项目实现了构建、移动、管理镜像的功能:
containers/imageproject provides mechanisms to copy (push, pull), inspect, and sign container imagescontainers/storageproject provides mechanisms for storing filesystem layers, container images, and containers
那么问题来了:构建镜像已经有 Docker 了为什么还需要 Buildah?
Buildah 是无守护进程以及可以 rootless 运行的,相比于 docker 更加轻量级。
如果使用 Buildah 来代替 Docker 镜像构建能力,由于可以无守护进程以及可以 rootless 运行,因此即使在容器中使用也非常方便,对于 Devops 来说是一个很好的选择。
即:相较于现有的构建工具, Buildah 更轻量级,做到了 Dockerless 和 Rootless。
2. 安装 Buildah
官方文档:buildah#install.md[^8]
Buildah 为各大发行版都提供了对应的 Package,可以方便的通过 yum、apt-get、dnf 等等工具安装,当然也可以通过源码编译安装。
推荐使用发行版自带的包管理工具安装:
Demo 用的 Ubuntu22.04
查看 Buildah 版本
ps:系统版本比较低,所以安装的 buildah 也比较旧
3. 基础功能
使用命令式构建镜像
Buildah 相对于 Dockerfile 提供了强大的命令式构建方式,将 Dockerfile 指令变成一条一条的命令,为我们构建镜像提供了新的选择:
输出如下:
查看到刚才构建的镜像
通过 Dockerfile 构建镜像
当然,Buildah 也支持通过 Dockerfile 构建镜像,这个应该是比较常见的用法。
准备一个 Dockerfile
使用 buildah 构建镜像
输出如下
用法和 Docker build 基本一致,迁移的话也没有太多学习成本。
4. 配置文件
同为 OCI 三剑客,Podman 、Buildah 配置文件也是通用的。
您可以在以下目录中找到默认的 Podman 、Buildah 的配置文件:
- 全局配置文件:
/etc/containers/ - 用户配置文件:
~/.config/containers/
ps:会优先使用用户配置文件,若没有则使用全局配置文件。 即:不同用户都可以单独指定自己的配置文件
在/etc/containers 目录下,包括多种配置文件:
- storage.conf:存储相关配置
- registries.conf:镜像仓库相关配置
- policy.json:容器签名验证相关配置
- auth.json:镜像仓库的认证信息,执行 login 命令后会将 token 存到该文件
- ...
各个文件的具体配置可以参考:Podman&Buildah 配置文件说明[^9]
作为使用者,主要关系 registries.conf 配置,因此重点分析。
完整内容
/etc/containers/registries.conf 完整内容如下:
大致可以分为以下几部分:
- 默认镜像仓库
- 为镜像仓库配置 Insecure、Mirror 等
- shortName 处理模式
不同仓库配置使用 [[registry]] 块进行区分。
注意:下面这样的配置是 V1 版本,已经废弃了,虽然还可以使用,但是不推荐。
参数解释
官方文档:containers-registries.conf.5.md[^3]
unqualified-search-registries
unqualified-search-registries 是一个配置项,用来指定当拉取一个 没有指定完整路径(即不包含域名和路径) 的镜像时,应该尝试哪些仓库(注册表)。这通常适用于 “没有指定镜像仓库” 的情况。
一句话描述:在拉取没有指定完整路径(即不包含域名和路径) 的镜像时,应该尝试哪些仓库(注册表)。
short-name-mode
short-name-mode 选项定义了如何处理不带仓库路径的镜像名(例如,golang:1.20)。有三种模式:
- disabled:不允许使用短名称,必须指定完整的仓库路径。
- permissive(默认):允许使用短名称,并尝试按顺序从配置的注册表列表中查找镜像。
- full:只有在仓库名称为完整名称时才能拉取镜像。
默认值就可以了,不用改。
prefix
Registry 块下的 prefix 用于匹配在拉取镜像时会用那个 Registry 块里的配置,只会使用最长匹配的 Registry 块。
假设有下面这样的配置,包含两个 Registry 块
当我们拉取镜像docker.io.example.com/library/busybox:latest 时,根据镜像完整命令中解析得到一个域名,然后和我们的配置文件中的 prefix 进行匹配,最终会匹配到第二个 Registry 块,这样就会使用该 Registry 块中的配置。
一句话描述:一般填写 Registry 地址即可,但是需要按照 *.example.com 格式,或者就是指定 location。
location
Registry 块中的 location 用于指定最终拉取镜像时访问的地址。
我们在拉取镜像时指定的是 docker.io/library/busybox:1.36,但是最终会去 registry-1.docker.io 这个地址拉取。
对于 docker.io 来说,就需要以下配置文件:
还有就是 prefix 不是*.example.com 格式时,也必须指定 location,内容和 prefix 一致就行。
一句话描述:用于指定真正拉取镜像的地址,例如 registry-1.docker.io,或者当 prefix 不是*.example.com 格式时,也必须指定 location,内容和 prefix 一致就行。
insecure
registry 块下的 Insecure 参数比较常见,就是配置使用 http 访问该仓库,一般自建私有仓库会用到该配置。
blocked
官方解释是这样的: If true, pulling images with matching names is forbidden.
默认是 false,配置为 true 之后就不能冲对应 Prefix 指定的镜像仓库中拉取镜像了。
一句话描述:用于关闭某些禁止使用的仓库。
mirror
对于部分无法拉取或拉取慢的仓库,可以配置 mirror 仓库。
registry.mirror 块放在那个 Registry 块下面就是为哪个仓库配置的 Mirror。
参考配置文件
以下就是一个比较常用的配置文件 Demo,包括了 location、mirror、insecure 等配置,增加其他镜像仓库时可以做参考。
5. 进阶用法
这里主要分享一些进阶的用法,包括:
- 多阶段构建
- 多架构镜像构建
- CI 环境中使用 Buildah
多阶段构建
多阶段构建是一种优化镜像大小的常用手段,通过将程序编译环境和运行环境分开来降低最终镜像大小。 用一个简单的 Go 程序演示一下多阶段构建。
main.go
使用 net/http 启动一个 http 服务。
Dockerfile
多阶段构建核心其实是 Dockerfile,可以看到当前 Dockerfile 有两个 FROM 语句,分别对应到编译阶段和运行阶段。
- 编译阶段:使用 golang:1.20-alpine 作为基础镜像,保证 Go 程序可以正常编译
- 运行阶段:因为 Go 程序编译后二进制可以直接运行,不在依赖 Go 环境了,因此直接使用 alpine 作为基础镜像,减少最终镜像的体积
构建
输出如下:
多架构镜像构建
很多应用程序和服务都需要在不同架构的机器上运行,如 amd64 和 arm64,但我们不可能为每一个架构都准备一台专门的机器。
之前主要用的是 Docker Buildx,不过 Buildah 也是支持多架构构建的。
ps:当然了,都要借助 qemu
安装 qemu-user-static
buildah 使用 qemu 来模拟不同架构。
首先需要确保你的系统上安装了 qemu。
ps:经过测试,如果你的 Dockerfile 中没有 RUN 命令去执行某些操作其实不需要 qemu 也能正常构建多架构镜像。
直接包管理工具安装:
构建并推送多架构镜像
和 Docker buildx 一样,Buildah 也通过 --platform 参数来指定要构建的架构。
不过 Buildah 没有 --push 参数,不能在构建完成后自动生成 manifest 并推送,因此需要手动创建一个 manifest 并将构建的镜像和 manifest 绑定并手段推送到最终镜像仓库。
整体流程大致分为三步:
- 1)创建 Manifest
- 这里创建的 manifest 其实是一个镜像,会出现在 buildah images 列表里
- 名称推荐使用完整镜像名,例如:172.20.150.222/lixd/nginx-hello:v0.0.2,不过用别的也不影响
- 2)构建多架构镜像
- 注意要使用 --manifest 代替 --tag 参数,让镜像和 manifest 绑定
- 3)推送 Manifest 和 Image 到镜像仓库
- Push 时需要指定 Manifest 名称,同时还要指定完整的 Registry 路径
- 如果 manifest 用的就是完整镜像名,这里二者就是一样的
Command 如下:
定义了一个简单的脚本来实现构建多架构镜像,build.sh 完整内容如下:
就以上一步的 Go Demo 编译生成一个多架构镜像:
输出如下:
CI 系统中使用
这里以 Github Action 为例,演示如何使用 Buildah 构建多架构镜像。
源码:lixd/github-action-lab[11]
Dockerfile 和 main.go 和之前一样,就不贴了,感兴趣的同学可以调整 Github 查看~
Workflow.yaml
Workflow 就是最终执行的 Pipeline,分为几个步骤:
- 1)启动运行环境,这里是 ubuntu-20.04
- 2)Clone 代码
- 3)安装 QEMU
- 4)Buildah 构建多架构镜像
- 5)推送到镜像仓库
``` name: Build and Push Multi-Arch Image
on: push:
env: IMAGENAME: test-multi-arch IMAGETAG: latest IMAGEREGISTRY: docker.io IMAGENAMESPACE: lixd96
jobs: build: name: Build and Push Multi-Architecture Image runs-on: ubuntu-20.04
steps: # Checkout the repository - name: Checkout repository uses: actions/checkout@v2
# Set up QEMU for cross-platform builds - name: Set up QEMU for multi-arch support uses: docker/setup-qemu-action@v1
# Build the Docker image using Buildah - name: Build multi-architecture image id: build-image uses: redhat-actions/buildah-build@v2 with: image: ${{ env.IMAGENAME }} tags: ${{ env.IMAGETAG }} archs: amd64,ppc64le,s390x,arm64 # Specify the architectures for multi-arch support dockerfiles: | ./Dockerfile
# Push the built image to the specified container registry - name: Push image to registry id: push-to-registry uses: redhat-actions/push-to-registry@v2 with: image: ${{ steps.build-image.outputs.image }} tags: ${{ steps.build-image.outputs.tags }} registry: ${{ env.IMAGEREGISTRY }}/${{ env.IMAGENAMESPACE }} username: ${{ secrets.REGISTRYUSERNAME }} # Secure registry username password: ${{ secrets.REGISTRYPASSWORD }} # Secure registry password
# Print the image URL after the image has been pushed - name: Print pushed image URL run: echo "Image pushed to ${{ steps.push-to-registry.outputs.registry-paths }}"