跳到主要内容

Buildx 多机多架构构建

手上有 amd64 和 arm64 多台机器时,最理想的多架构构建方式是:每台机器原生构建自己架构的镜像,最后合成一个多架构镜像。比在单机上用 QEMU 模拟构建快得多,也避免模拟环境下的各种诡异问题。

实现方式就是 buildx 的 remote driver:在每台机器上跑一个 buildkitd,本机用 docker buildx create --append 把它们组进同一个 builder,构建时 buildx 自动把每个平台调度到对应架构的节点上。

三种多架构构建方式对比​

方式速度适用场景
单机 + QEMU 模拟慢(模拟执行指令)偶尔构建一次、机器只有一台
多机 remote driver(本文)快(全部原生构建)常做多架构发布、手上有不同架构的机器
分架构推送 + imagetools 合并快机器间不能组网、CI 里不同架构的 runner
--append 到底是哪个命令

和追加相关的有两个:docker buildx create --append 是给 builder 追加构建节点(本文的主角);docker buildx imagetools create --append 是往已有 manifest 里追加平台(备选方案里会用到)。不要混淆。

整体架构​

Buildx 多机多架构构建流程

本机只负责下发构建任务和拼装结果,实际编译都发生在远端节点上,所以远端机器要能访问镜像仓库(推镜像)和基础镜像源(拉镜像)。

前置条件​

  • 每台远端机器:已安装 Docker(用来跑 buildkitd 容器)
  • 本机:已安装 Docker 和 buildx(docker buildx version 确认)
  • 本机到各远端机器的 1234 端口网络可达
  • 所有机器都能访问目标镜像仓库

第一步:远端机器生成 TLS 证书​

buildkitd 走 TCP 暴露服务,必须用 TLS 加密和鉴权。在每台远端机器上执行(官方提供的 bake 目标,SAN 里写本机 IP):

SAN="192.168.10.10 localhost 127.0.0.1" docker buildx bake "https://github.com/moby/buildkit.git#master:examples/create-certs"

执行后当前目录生成 .certs/:

.certs/
├── daemon/ # 服务端证书,留在本机给 buildkitd 用
│ ├── ca.pem
│ ├── cert.pem
│ └── key.pem
└── client/ # 客户端证书,稍后拷给本机(发起构建的机器)
├── ca.pem
├── cert.pem
└── key.pem
TLS 不是可选项

不配 TLS 直接把 buildkitd 暴露在 TCP 上,等于把这台机器的 root 级构建权限开放给任何能连上端口的人。仅限完全可信的隔离网络才可以省略。

第二步:远端机器启动 buildkitd​

每台远端机器上执行:

docker run -d \
--name buildkitd \
--restart unless-stopped \
--privileged \
-p 1234:1234 \
-v $PWD/.certs:/etc/buildkit/certs \
moby/buildkit:latest \
--addr tcp://0.0.0.0:1234 \
--tlscacert /etc/buildkit/certs/daemon/ca.pem \
--tlscert /etc/buildkit/certs/daemon/cert.pem \
--tlskey /etc/buildkit/certs/daemon/key.pem

确认运行正常:

docker logs buildkitd --tail 20

第三步:拷贝客户端证书到本机​

每台远端机器各自生成了一套 CA,客户端证书要分开存放。在本机执行:

mkdir -p certs/amd64 certs/arm64
scp -r root@192.168.10.10:$PWD/.certs/client/. certs/amd64/
scp -r root@192.168.10.11:$PWD/.certs/client/. certs/arm64/

第四步:创建 builder 并追加节点​

在本机执行。先创建 builder 并加入第一个节点(amd64 机器):

docker buildx create --name multi-arch --driver remote \
--node amd64 \
--driver-opt cacert=$PWD/certs/amd64/ca.pem,cert=$PWD/certs/amd64/cert.pem,key=$PWD/certs/amd64/key.pem \
tcp://192.168.10.10:1234

然后用 --append 追加 arm64 节点:

docker buildx create --name multi-arch --driver remote --append \
--node arm64 \
--driver-opt cacert=$PWD/certs/arm64/ca.pem,cert=$PWD/certs/arm64/cert.pem,key=$PWD/certs/arm64/key.pem \
tcp://192.168.10.11:1234

启用并检查:

docker buildx use multi-arch
docker buildx inspect --bootstrap

输出里应该看到两个节点,各自带上自己架构的平台:

Name:          multi-arch
Driver: remote

Nodes:
Name: amd64
Endpoint: tcp://192.168.10.10:1234
Status: running
Platforms: linux/amd64*, linux/amd64/v2, linux/amd64/v3, linux/386

Name: arm64
Endpoint: tcp://192.168.10.11:1234
Status: running
Platforms: linux/arm64*, linux/arm/v8, linux/arm/v7, linux/arm/v6
提示

节点的平台列表是连接时自动探测的,一般不用手动指定。只有多个节点都支持同一平台、想人为分配时,才在 create 时用 --platform 固定。

第五步:构建多架构镜像​

在本机项目目录下,一条命令构建两个架构并推送:

docker buildx build --platform linux/amd64,linux/arm64 \
-t registry.example.com/myapp:1.0.0 \
--push .

buildx 会按平台把构建任务调度到对应节点:amd64 部分发给 amd64 节点、arm64 部分发给 arm64 节点,两边都是原生速度并行编译,最后自动拼成 manifest list 推到仓库。

验证镜像​

docker buildx imagetools inspect registry.example.com/myapp:1.0.0

能看到一个 tag 下挂了两个平台的 manifest 就说明成功了:

Name:      registry.example.com/myapp:1.0.0
MediaType: application/vnd.oci.image.index.v1+json
Digest: sha256:xxxx

Manifests:
Name: registry.example.com/myapp:1.0.0@sha256:aaaa
MediaType: application/vnd.oci.image.manifest.v1+json
Platform: linux/amd64

Name: registry.example.com/myapp:1.0.0@sha256:bbbb
MediaType: application/vnd.oci.image.manifest.v1+json
Platform: linux/arm64

常见问题​

context deadline exceeded,节点连不上

按顺序查:

  • 本机到远端的 1234 端口是否可达:nc -zv 192.168.10.10 1234
  • 证书的 SAN 里是否包含你连接用的 IP 或域名(第一步生成时写对没有)
  • 如果连接地址和证书里的名字不一致,用 --driver-opt servername=证书里的名字 指定 TLS 校验用的服务器名
  • docker logs buildkitd --tail 50 看远端守护进程有没有报错

远端拉取基础镜像很慢

构建是在远端节点上执行的,拉基础镜像走的也是远端的网络。在远端机器的 buildkitd 里配镜像加速(写 buildkitd.toml 的 registry.mirrors,启动时用 --config 挂载),不要只配本机的。

要移除某个节点或整个 builder

# 从 builder 移除一个节点
docker buildx create --name multi-arch --node arm64 --leave

# 删除整个 builder
docker buildx rm multi-arch

备选方案:分架构推送再合并​

如果机器之间不能组网(比如 CI 里不同架构的 runner),可以各自本地构建、推不同标签,最后合并:

# amd64 机器上
docker buildx build --platform linux/amd64 -t registry.example.com/myapp:1.0.0-amd64 --push .

# arm64 机器上
docker buildx build --platform linux/arm64 -t registry.example.com/myapp:1.0.0-arm64 --push .

# 任意一台能访问仓库的机器上合并成一个 tag
docker buildx imagetools create -t registry.example.com/myapp:1.0.0 \
registry.example.com/myapp:1.0.0-amd64 \
registry.example.com/myapp:1.0.0-arm64

如果镜像已存在、只想往里加一个平台,用 docker buildx imagetools create --append -t 已有标签 新平台镜像 即可,不用把所有源重新列一遍。

参考资料​