惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
S
Schneier on Security
P
Privacy & Cybersecurity Law Blog
S
Security @ Cisco Blogs
W
WeLiveSecurity
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Microsoft Azure Blog
Microsoft Azure Blog
NISL@THU
NISL@THU
T
Troy Hunt's Blog
L
LangChain Blog
L
LINUX DO - 最新话题
T
The Exploit Database - CXSecurity.com
Engineering at Meta
Engineering at Meta
N
News and Events Feed by Topic
A
About on SuperTechFans
N
Netflix TechBlog - Medium
P
Proofpoint News Feed
MyScale Blog
MyScale Blog
Martin Fowler
Martin Fowler
Y
Y Combinator Blog
H
Heimdal Security Blog
aimingoo的专栏
aimingoo的专栏
T
Threat Research - Cisco Blogs
SecWiki News
SecWiki News
Microsoft Security Blog
Microsoft Security Blog
T
Tenable Blog
P
Proofpoint News Feed
H
Hacker News: Front Page
G
GRAHAM CLULEY
I
Intezer
V
V2EX
S
Secure Thoughts
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
人人都是产品经理
人人都是产品经理
博客园 - 聂微东
Latest news
Latest news
Recent Announcements
Recent Announcements
Hugging Face - Blog
Hugging Face - Blog
腾讯CDC
博客园_首页
Webroot Blog
Webroot Blog
博客园 - 三生石上(FineUI控件)
AI
AI
N
News | PayPal Newsroom
Google DeepMind News
Google DeepMind News
Security Archives - TechRepublic
Security Archives - TechRepublic
B
Blog RSS Feed
美团技术团队

Oskyla 烹茶室

修复 Joplin on KDE 菜单栏显示问题 Copy Fail:Linux 内核 2017 年至今的高危漏洞(附临时缓解方案) | CVE-2026-31431 Hermes Agent — 在 K3s / K8s 中运行指南 在 K3s 节点上安装并使用 nerdctl Mouser:轻量开源的罗技鼠标驱动替代方案 Claude Opus 4.7:优缺点与评测信息汇总 openFuyao NPU-Operator故障排查 openFuyao 2603 共测测试报告 openFuyao InferNex AI推理集成部署 310P(300I Pro) 环境问题记录及解决 ceph mon Operation not permitted 问题解决 Ascend 310P + openFuyao + NPU-Operator 故障排查 KDE Plasma6 禁用全局菜单,恢复正常应用菜单 终极指南:在 Linux 裸机服务器上快速部署 Moltbot (原 Clawbot) 并集成飞书 Windows 配置 Claude Code 解决 settings.json 不生效 Windows 配置 Claude Code 全流程 2025-12-31 | 年终总结 AI 生图精品提示词|第二期:城市星球 AI 生图精品提示词|第一期 Kubernetes kubectl --raw 使用指南 彻底解决阿里云和 tailscale 冲突 2025-10-21 | 沉淀思维 macOS 单独为鼠标或触控板开启自然滚动 2025-10-16 | 负载高低 2025-10-15 | 睡眠周期 2025-10-14 | 转换情绪与独立观点 go 拉取 gitcode.com 私有 mod Git 将某个文件恢复到其他分支的状态 SSH 通过跳板机连接 lxc 使用 chronyc 构建 ntp 服务 2025-10-13 | 独立思考于未来能源 2025-10-12|AI Review 及高质量工作流 2025-10-11 | 人生阶段 2025-10-10 | AI Logo | 远程操控 AI 2025-10-09 | 去班味|银杏|域名 2025-10-08 2025-10-07 2025-10-06 2025-10-05 优秀的 SubReddit 清单 Synology 群晖重置 Syncthing 密码 让 LLM 看到真实世界的 Playwright MCP 磁盘占用分析利器 ncdu 自建 gitlab 徽标问题导致项目 500 问题解决 harbor Chartmuseum helm 图表缓存刷新 grep exclude 排除 Debian 11 + PVE LACP Mac 冲突问题调查与解决方案 Claude Code 实用技巧 CentOS 7 重置 root 密码 telnet 如何退出 kubernetes 的挂载传播(mount propagation)机制 vim 将命令输出到当前位置 harbor 替换 ssl 证书 AI提效之使用 cherry-studio + k8sgpt 实现 AI 巡检 k8s Claude Code Subagents 快速开始 《我看见的世界:李飞飞自传》 Lyra - AI Prompt Optimization Specialist Linux 自签名 CA 证书安装方法 超级玛丽、魂斗罗等经典小游戏聚合站 | PlayUnb 个人业余开发项目清单 快速选出收益最高的理财产品 | finance-calculator 苹果液态玻璃风格图像生成和模拟器 | LiquedGlass.icu Linux 进程绑定NUMA节点或CPU核心 判断GPT是否降智的几个问题 Harmony next hap 安装 名侦探柯南贝尔摩德出场集数 k3s k8s 快速部署轻量节点监控方案 beszel k3s-k8s 实现 DevOps 方案横向对比 k8s 配置访问私有镜像仓库 GoAccess 分析多网站日志方法 Octant - 以开发人员为中心的开源 Kubernetes Web 界面 Tailscale 自建 DERP 并配置 SSL 完整教程 OpenManus 使用记录 Plausible 缺失 location 信息的研究 解决 Nginx Ingress returns 413 Entity Too Large 绘图模型效果对比之城市气象 【转】k8s 认知路线 OpenFOAM 两大分支的详细比较 第一个 CUDA 程序之矩阵运算计算效能对比 Archlinux KDE Apache JMeter 配置高分屏缩放 解决 gitlab-runner 移除残留文件 permission denied nginx-ingress 配置路由 302 k8s 触发 pod 重新拉取镜像平滑升级的方法 Clickhouse 迁移后 permission denied 问题解决 Linux CPU 运行模式及功耗分析 Linux vim vi 翻页跳转命令快捷键 git 拉取所有 branch 和 tag 到本地并推送到远程 Rails 性能分析工具 rack-mini-profiler 和 bullet 全球国家、城市、地区开源数据库 Rails Active Record 常用命令 Rails Rake 简介与编写 如何调试 Vim 脚本 名侦探柯南松田阵平出场集数(最新1069话) 名侦探柯南妃英理出场集数(最新1069话) 《名侦探柯南》官方人气投票结果 名侦探柯南TV安室透出场集数(最新1110话) 名侦探柯南TV赤井秀一出场集数(更新至1194话) [转]名侦探柯南TV主线集数整理(截止1165集) 架构风格总结 [转译] 如何成为一个好的开发者? 【影评】名侦探柯南·零的执行人
Buildah 简明教程:让镜像构建更轻量,告别 Docker 依赖
Tianlun Song · 2024-12-24 · via Oskyla 烹茶室

本文 首发于 🌱 煎茶转载 请注明 来源

来源:Buildah 简明教程:让镜像构建更轻量,告别 Docker 依赖

Buildah 是一个专注于构建 OCI 镜像的工具,Buildah CLI 工具使用底层 OCI 技术实现(例如 containers/image1 和 containers/storage2)。

OCI 三剑客包括:

  • 专注于镜像构建的 Buildah
  • 专注于镜像和容器管理的 Podman
  • 专注于镜像操作和管理(尤其是涉及远程仓库的操作)的 Skopeo

这三者一起形成了一个 Dockerless 的容器生态,支持构建、管理、推送和操作镜像和容器,且不依赖 Docker 守护进程。

注意:三者之间功能是有一定重复的,特别是 Buildah 和 Podman,不过各自专注点不同,建议合理搭配使用。

Buildah 是一个专注于构建 OCI 镜像的工具,Buildah CLI 工具使用底层 OCI 技术实现(例如 containers/image3 和 containers/storage4)。

官方描述原文:

A tool that facilitates building OCI images.the Buildah command line tool (CLI) and the underlying OCI based technologies (e.g. containers/image5 and containers/storage6)

Buildah CLI 工具则基于这些项目实现了构建、移动、管理镜像的功能:

  • containers/image project provides mechanisms to copy (push, pull), inspect, and sign container images
  • containers/storage project 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.md7

Buildah 为各大发行版都提供了对应的 Package,可以方便的通过 yumapt-getdnf 等等工具安装,当然也可以通过源码编译安装。

推荐使用发行版自带的包管理工具安装:

# CentOS  
sudo yum -y install buildah  
  
# Ubuntu 20.10 and newer  
sudo apt-get -y update  
sudo apt-get -y install buildah  
  
# Fedora  
sudo dnf -y install buildah

Demo 用的 Ubuntu22.04

sudo apt-get -y update  
sudo apt-get -y install buildah

查看 Buildah 版本

ps:系统版本比较低,所以安装的 buildah 也比较旧

root@builder-ubuntu:~# buildah version  
Version:         1.23.1  
Go Version:      go1.17  
Image Spec:      1.0.1  
Runtime Spec:    1.0.2-dev  
CNI Spec:        0.4.0  
libcni Version:  
image Version:   5.16.0  
Git Commit:  
Built:           Thu Jan  1 08:00:00 1970  
OS/Arch:         linux/amd64  
BuildPlatform:   linux/amd64

3. 基础功能

使用命令式构建镜像

Buildah 相对于 Dockerfile 提供了强大的命令式构建方式,将 Dockerfile 指令变成一条一条的命令,为我们构建镜像提供了新的选择:

# 拉取镜像,类似 Dockerfile 中的 FROM  
container=$(buildah from nginx)  
# 类似 Dockerfile 中的 RUN  
buildah run $container -- bash -c 'echo "hello world" > /usr/share/nginx/html/index.html'  
# 提交保存镜像  
buildah commit $container nginx-hello

输出如下:

[root@builder ~]# container=$(buildah from nginx)  
[root@builder ~]# buildah run $container -- bash -c 'echo "hello world" > /usr/share/nginx/html/index.html'  
[root@builder ~]# buildah commit $container nginx-hello  
Getting image source signatures  
Copying blob c0f1022b22a9 skipped: already exists  
Copying blob fc00b055de35 skipped: already exists  
Copying blob 2c3a053d7b67 skipped: already exists  
Copying blob b060cc3bd13c skipped: already exists  
Copying blob 8aa4787aa17a skipped: already exists  
Copying blob c28e0f7d0cc5 skipped: already exists  
Copying blob d32d820bcf1c skipped: already exists  
Copying blob c6a7a8084917 done   |  
Copying config 19de2f1f4a done   |  
Writing manifest to image destination  
19de2f1f4afc6e0ff9da11e9dfb988619f4bcd1d388ea4c18413ab574487a0d4

查看到刚才构建的镜像

[root@builder ~]# buildah images  
REPOSITORY                          TAG       IMAGE ID       CREATED          SIZE  
localhost/nginx-hello               latest    19de2f1f4afc   22 seconds ago   196 MB

通过 Dockerfile 构建镜像

当然,Buildah 也支持通过 Dockerfile 构建镜像,这个应该是比较常见的用法。

准备一个 Dockerfile

FROM nginx  
RUN echo "Hello World" > /usr/share/nginx/html/index.html  
EXPOSE 80

使用 buildah 构建镜像

buildah build -t nginx-hello2 .

输出如下

[root@builder ~]# buildah build -t nginx-hello2 .  
STEP 1/3: FROM nginx  
STEP 2/3: RUN echo "Hello World" > /usr/share/nginx/html/index.html  
STEP 3/3: EXPOSE 80  
COMMIT nginx-hello2  
Getting image source signatures  
Copying blob c0f1022b22a9 skipped: already exists  
Copying blob fc00b055de35 skipped: already exists  
Copying blob 2c3a053d7b67 skipped: already exists  
Copying blob b060cc3bd13c skipped: already exists  
Copying blob 8aa4787aa17a skipped: already exists  
Copying blob c28e0f7d0cc5 skipped: already exists  
Copying blob d32d820bcf1c skipped: already exists  
Copying blob eec64f0b2723 done   |  
Copying config 1b63bdb270 done   |  
Writing manifest to image destination  
--> 1b63bdb270c1  
Successfully tagged localhost/nginx-hello2:latest  
1b63bdb270c1066520a5ae37dcea3d5c3b9c5e9af581e76bf1287f9f79f77f03

用法和 Docker build 基本一致,迁移的话也没有太多学习成本。

4. 配置文件

同为 OCI 三剑客,Podman 、Buildah 配置文件也是通用的。

您可以在以下目录中找到默认的 PodmanBuildah 的配置文件:

  • 全局配置文件:/etc/containers/
  • 用户配置文件:~/.config/containers/

ps:会优先使用用户配置文件,若没有则使用全局配置文件。 即:不同用户都可以单独指定自己的配置文件

/etc/containers 目录下,包括多种配置文件:

  • storage.conf:存储相关配置
  • registries.conf:镜像仓库相关配置
  • policy.json:容器签名验证相关配置
  • auth.json:镜像仓库的认证信息,执行 login 命令后会将 token 存到该文件

各个文件的具体配置可以参考:Podman&Buildah 配置文件说明8

作为使用者,主要关系 registries.conf 配置,因此重点分析。

vi /etc/containers/registries.conf

完整内容

/etc/containers/registries.conf 完整内容如下:

unqualified-search-registries = ["registry.access.redhat.com", "registry.redhat.io", "docker.io"]  
  
# 配置为 Docker.io 仓库的镜像源  
[[registry]]  
prefix = "docker.io"  
location = "registry-1.docker.io"  
  
# 为 Docker.io 配置镜像源  
[[registry.mirror]]  
location = "mirror.gcr.io"  
  
[[registry.mirror]]  
location = "mirror2.gcr.io"  
  
  
# 配置为私有仓库 10.10.10.49:5000 的镜像源  
[[registry]]  
prefix = "10.10.10.49:5000"  
location = "10.10.10.49:5000"  
insecure = true  
  
# 配置私有仓库镜像源  
[[registry.mirror]]  
location = "mirror.gcr.io"  
  
  
short-name-mode = "permissive

大致可以分为以下几部分:

  • 默认镜像仓库
  • 为镜像仓库配置 Insecure、Mirror 等
  • shortName 处理模式

不同仓库配置使用 [[registry]] 块进行区分。

注意:下面这样的配置是 V1 版本,已经废弃了,虽然还可以使用,但是不推荐。

[registries.search]
registries = ['registry1.com', 'registry2.com']

[registries.insecure]
registries = ['registry3.com']

[registries.block]
registries = ['registry.untrusted.com', 'registry.unsafe.com']

参数解释

官方文档:containers-registries.conf.5.md9

unqualified-search-registries

unqualified-search-registries 是一个配置项,用来指定当拉取一个 没有指定完整路径(即不包含域名和路径) 的镜像时,应该尝试哪些仓库(注册表)。这通常适用于 “没有指定镜像仓库” 的情况。

unqualified-search-registries = ["registry.access.redhat.com", "registry.redhat.io", "docker.io"]

一句话描述:在拉取没有指定完整路径(即不包含域名和路径) 的镜像时,应该尝试哪些仓库(注册表)。

short-name-mode

short-name-mode 选项定义了如何处理不带仓库路径的镜像名(例如,golang:1.20)。有三种模式:

  • disabled:不允许使用短名称,必须指定完整的仓库路径。

  • permissive(默认):允许使用短名称,并尝试按顺序从配置的注册表列表中查找镜像。

  • full:只有在仓库名称为完整名称时才能拉取镜像。

默认值就可以了,不用改。

short-name-mode = "permissive

prefix

Registry 块下的 prefix 用于匹配在拉取镜像时会用那个 Registry 块里的配置,只会使用最长匹配的 Registry 块。

假设有下面这样的配置,包含两个 Registry 块

[[registry]]
prefix = "docker.io"

[[registry]]
prefix = "docker.io.example.com"

当我们拉取镜像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 来说,就需要以下配置文件:

[[registry]]
prefix = "docker.io"
location = "registry-1.docker.io"

还有就是 prefix 不是*.example.com 格式时,也必须指定 location,内容和 prefix 一致就行。

一句话描述:用于指定真正拉取镜像的地址,例如 registry-1.docker.io,或者当 prefix 不是*.example.com 格式时,也必须指定 location,内容和 prefix 一致就行。

insecure

registry 块下的 Insecure 参数比较常见,就是配置使用 http 访问该仓库,一般自建私有仓库会用到该配置。

# 配置为私有仓库 10.10.10.49:5000 的镜像源
[[registry]]
prefix = "10.10.10.49:5000"
location = "10.10.10.49:5000"
insecure = true

blocked

官方解释是这样的: If true, pulling images with matching names is forbidden.

默认是 false,配置为 true 之后就不能冲对应 Prefix 指定的镜像仓库中拉取镜像了。

# 配置为私有仓库 10.10.10.49:5000 的镜像源
[[registry]]
prefix = "10.10.10.49:5000"
blocked = false

一句话描述:用于关闭某些禁止使用的仓库。

mirror

对于部分无法拉取或拉取慢的仓库,可以配置 mirror 仓库。

# 配置 Docker 的镜像源
[[registry]]
prefix = "docker.io"
location = "registry-1.docker.io"

[[registry.mirror]]
location = "docker.m.daocloud.io"

registry.mirror 块放在那个 Registry 块下面就是为哪个仓库配置的 Mirror。

参考配置文件

以下就是一个比较常用的配置文件 Demo,包括了 location、mirror、insecure 等配置,增加其他镜像仓库时可以做参考。

unqualified-search-registries = ["docker.io"]
short-name-mode = "permissive"

# 配置 Docker 的镜像源
[[registry]]
prefix = "docker.io"
location = "registry-1.docker.io"

[[registry.mirror]]
location = "docker.m.daocloud.io"

# 配置为私有仓库 "172.20.150.222" 的镜像源
[[registry]]
prefix = "172.20.150.222"
location = "172.20.150.222"
insecure = true

5. 进阶用法

这里主要分享一些进阶的用法,包括:

  • 多阶段构建
  • 多架构镜像构建
  • CI 环境中使用 Buildah

多阶段构建

多阶段构建是一种优化镜像大小的常用手段,通过将程序编译环境和运行环境分开来降低最终镜像大小。 用一个简单的 Go 程序演示一下多阶段构建。

main.go

使用 net/http 启动一个 http 服务。

// main.go
package main

import (
        "fmt"
        "log"
        "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, World!")
}

func main() {
        http.HandleFunc("/", handler)
        log.Fatal(http.ListenAndServe(":8080", nil))
}

Dockerfile

多阶段构建核心其实是 Dockerfile,可以看到当前 Dockerfile 有两个 FROM 语句,分别对应到编译阶段和运行阶段。

  • 编译阶段:使用 golang:1.20-alpine 作为基础镜像,保证 Go 程序可以正常编译

  • 运行阶段:因为 Go 程序编译后二进制可以直接运行,不在依赖 Go 环境了,因此直接使用 alpine 作为基础镜像,减少最终镜像的体积

# Stage 1: Build stage (builder)
FROM golang:1.20-alpine as builder

# Set the Current Working Directory inside the container
WORKDIR /app

# Copy the source code into the container
COPY . .

# Build the Go binary
RUN CGO_ENABLED=0 go build main.go

# Stage 2: Runtime stage
FROM alpine:latest

# Install the necessary libraries to run the binary (if any)
RUN apk --no-cache add ca-certificates

# Set the Current Working Directory inside the container
WORKDIR /root/

# Copy the compiled binary from the builder stage
COPY --from=builder /app/main .

# Expose port 8080
EXPOSE 8080

# Run the Go application
CMD ["./main"]

构建

buildah build -t server:v0.0.1 .

输出如下:

[root@builder ~]# buildah build -t server:v0.0.1 .
[1/2] STEP 1/4: FROM golang:1.20-alpine AS builder
[1/2] STEP 2/4: WORKDIR /app
[1/2] STEP 3/4: COPY . .
[1/2] STEP 4/4: RUN CGO_ENABLED=0 go build main.go
[2/2] STEP 1/6: FROM alpine:latest
Resolved "alpine" as an alias (/etc/containers/registries.conf.d/000-shortnames.conf)
Trying to pull docker.io/library/alpine:latest...
Getting image source signatures
Copying blob 38a8310d387e done   |
Copying config 4048db5d36 done   |
Writing manifest to image destination
[2/2] STEP 2/6: RUN apk --no-cache add ca-certificates
fetch https://dl-cdn.alpinelinux.org/alpine/v3.21/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.21/community/x86_64/APKINDEX.tar.gz
(1/1) Installing ca-certificates (20241010-r0)
Executing busybox-1.37.0-r8.trigger
Executing ca-certificates-20241010-r0.trigger
OK: 7 MiB in 16 packages
[2/2] STEP 3/6: WORKDIR /root/
[2/2] STEP 4/6: COPY --from=builder /app/main .
[2/2] STEP 5/6: EXPOSE 8080
[2/2] STEP 6/6: CMD ["./main"]
[2/2] COMMIT server:v0.0.1
Getting image source signatures
Copying blob 3e01818d79cd skipped: already exists
Copying blob 529cb79624ea done   |
Copying config 8d0a6344f5 done   |
Writing manifest to image destination
--> 8d0a6344f55c
Successfully tagged localhost/server:v0.0.1
8d0a6344f55c0611c94b23f2571adb0ba1ce98ee1d5009c79fd656fd42247c1b

多架构镜像构建

很多应用程序和服务都需要在不同架构的机器上运行,如 amd64arm64,但我们不可能为每一个架构都准备一台专门的机器。

之前主要用的是 Docker Buildx,不过 Buildah 也是支持多架构构建的。

ps:当然了,都要借助 qemu

安装 qemu-user-static

buildah 使用 qemu 来模拟不同架构。

首先需要确保你的系统上安装了 qemu

ps:经过测试,如果你的 Dockerfile 中没有 RUN 命令去执行某些操作其实不需要 qemu 也能正常构建多架构镜像。

直接包管理工具安装:

# Ubuntu
sudo apt-get install qemu-user-static
# Fedora
sudo dnf install qemu-user-static

构建并推送多架构镜像

和 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 如下:

PUSH_WAY=172.20.150.222/lixd/nginx-hello:v0.0.2

# 创建 manifest
buildah manifest create ${PUSH_WAY}

# 构建
buildah build --manifest ${PUSH_WAY} --platform linux/amd64,linux/arm64 .

# 推送
buildah manifest push ${PUSH_WAY} --all "docker://${PUSH_WAY}"

定义了一个简单的脚本来实现构建多架构镜像,build.sh 完整内容如下:

# Set the required variables
export REGISTRY="172.20.150.222"
export REPOSITORY="lixd"
export IMAGE_NAME="server"
export IMAGE_TAG="v0.0.1"
export BUILD_PATH="."

# Platforms to build for
export PLATFORMS="linux/amd64,linux/arm64"

PUSH_WAY="${REGISTRY}/${REPOSITORY}/${IMAGE_NAME}:${IMAGE_TAG}"
MANIFEST_NAME=$PUSH_WAY
echo $PUSH_WAY

# Create a multi-architecture manifest
### Infact,this command can be ignore,when build will creates manifest list if it does not exist
buildah manifest create ${MANIFEST_NAME}

# Build the container for all platform
### Note: When more than one platform,use manifest to instead of tag flag.
buildah build \
--manifest ${MANIFEST_NAME} \
--platform ${PLATFORMS} \
${BUILD_PATH}

# Push the full manifest, with both CPU Architectures
### If Push To Docker Hub or Gitlab Registry,need add flag:--format v2s2,Default Is oci
buildah manifest push --all \
  ${MANIFEST_NAME} \
  "docker://${PUSH_WAY}"

就以上一步的 Go Demo 编译生成一个多架构镜像:

bash build.sh

输出如下:

root@builder-ubuntu:~/multistage# bash build.sh
172.20.150.222/lixd/server:v0.0.1
e6ba6ec459a1fd7303c19242ab0d85c7c23af8cb156ce348928e2a4135327f15
# amd64
[linux/amd64] STEP 1/4: FROM golang:1.20-alpine AS builder
[linux/amd64] STEP 2/4: WORKDIR /app
[linux/amd64] STEP 3/4: COPY . .
[linux/amd64] STEP 4/4: RUN CGO_ENABLED=0 go build main.go
[linux/amd64] STEP 1/6: FROM alpine:latest
[linux/amd64] STEP 2/6: RUN apk --no-cache add ca-certificates
[linux/amd64] STEP 3/6: WORKDIR /root/
[linux/amd64] STEP 4/6: COPY --from=builder /app/main .
[linux/amd64] STEP 5/6: EXPOSE 8080
[linux/amd64] STEP 6/6: CMD ["./main"]
# arm64
[linux/arm64] [1/2] STEP 1/4: FROM golang:1.20-alpine AS builder
[linux/arm64] [1/2] STEP 2/4: WORKDIR /app
[linux/arm64] [1/2] STEP 3/4: COPY . .
[linux/arm64] [1/2] STEP 4/4: RUN CGO_ENABLED=0 go build main.go
[linux/amd64] [2/2] STEP 1/6: FROM alpine:latest
[linux/arm64] [2/2] STEP 3/6: WORKDIR /root/
[linux/arm64] [2/2] STEP 4/6: COPY --from=builder /app/main .
[linux/arm64] [2/2] STEP 5/6: EXPOSE 8080
[linux/arm64] [2/2] STEP 6/6: CMD ["./main"]
[linux/arm64] [2/2] COMMIT
# push
Getting image source signatures
Copying blob 977340364f39 skipped: already exists
Copying blob d8b4b7adc1e8 done
Copying config d97c60d03e done
Writing manifest to image destination
Storing signatures
--> d97c60d03e8
d97c60d03e822bb29c02c6b5c2c51b0f47871e52bc8c210c1e6324863797ce64
Getting image list signatures
Copying 4 of 4 images in list
Writing manifest list to image destination
...

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:
  IMAGE_NAME: test-multi-arch
  IMAGE_TAG: latest
  IMAGE_REGISTRY: docker.io
  IMAGE_NAMESPACE: 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.IMAGE_NAME }}
          tags: ${{ env.IMAGE_TAG }}
          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.IMAGE_REGISTRY }}/${{ env.IMAGE_NAMESPACE }}
          username: ${{ secrets.REGISTRY_USERNAME }}  # Secure registry username
          password: ${{ secrets.REGISTRY_PASSWORD }}  # 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 }}"

验证

提交代码后,Workflow 会自动运行,到 Dockerhub 查看镜像是否成功推送

可以看到,指定的 4 个架构都成功构建并推送过来了。

6.小结

Buildah 提供了一种灵活且高效的镜像构建方式,无需 Docker 依赖,且支持 rootless 安全模式,适用于各种 DevOps 和 CI/CD 环境。它支持命令式和 Dockerfile 构建方式,还能进行多阶段构建和多架构镜像构建。

References

参考资料