前言

之前我们用的都是 Intel 芯片的 Mac 做开发,后来统一的都换成了 Apple M1 系列的 MacBook Pro,这样导致一个问题。就是我们基于 Intel 平台构建的镜像在 Apple M1 系列的电脑上会有兼容性提示。

还有就是如果对 Dockerfile 进行了修改,例如升级了 PHP Runtime,那么在 ARM 芯片上构建的镜像无法在 linux/amd64 平台上运行。

经过研究发现 Docker 团队很早之前就在打磨的 moby/buildkit

驱动

Docker buildx 的驱动如下:

  • docker: Docker Engine 中内置的 Builder,不支持构建多平台镜像;
  • docker-container: 使用 BuildKit Container 用于构建多平台镜像;
  • kubernetes: 将 BuildKit Container 在 Kubernetes Pods 上调度运行,用于构建多平台镜像;
  • remote: 自行在远端部署 buildkitd 服务,然后使用 buildx 创建远端 builder 实例。

具体区别如下:

Feature docker docker-container kubernetes remote
Automatically load image
Cache export Inline only
Tarball output
Multi-arch images
BuildKit configuration Managed externally

所需条件

  • Docker Desktop: latest
  • Docker buildx: latest

buildx 是 docker CLI 的一个插件,类似与 compose 插件,该插件用于调用 BuildKit 的构建功能。

开始构建多平台镜像

构建多平台镜像基本分为两种方案:

  • 在 ARM Chip 上使用 QEMU 模拟 linux/amd64
  • 在远端服务器上使用 BuildKit Container 实现原生的平台进行编译

上面两种方案,第一种最简单,但是在 ARM 上编译 linux/amd64 的镜像会慢很多!第二种方式将不同平台的镜像构建任务分发到不同的远端服务器上原生构建,速度上有绝对的优势,只是配置相对复杂一些。

在本地使用 QUEM 模拟

这种方案的好处就是如果你么有 linux/amd64 架构的远端服务器,那么可以在本地使用 QEMU 来进行模拟,但是缺点就是速度相当的慢(构建过程慢 5~10 倍)。

docker buildx create --name local-builder --driver docker-container --bootstrap
[+] Building 18.2s (1/1) FINISHED
 => [internal] booting buildkit                                     18.2s
 => => pulling image moby/buildkit:buildx-stable-1                  17.6s
 => => creating container buildx_buildkit_local-builder0            0.6s
local-builder

docker buildx ls
NAME/NODE        DRIVER/ENDPOINT             STATUS  BUILDKIT PLATFORMS
local-builder    docker-container
  local-builder0 unix:///var/run/docker.sock running v0.10.5  linux/arm64, linux/amd64, linux/amd64/v2, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
default *        docker
  default        default                     running 20.10.17 linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
desktop-linux    docker
  desktop-linux  desktop-linux               running 20.10.17 linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6

可以看到已经创建了一个名为 local-builder 的 builder instance,这个 instance 是在本地运行了一个 Docker Container:

docker ps
CONTAINER ID   IMAGE                           COMMAND                  CREATED         STATUS         PORTS                        NAMES
999b76900e61   moby/buildkit:buildx-stable-1   "buildkitd"              5 minutes ago   Up 5 minutes                                buildx_buildkit_local-builder0

使用创建好的 Builder instance 来构建镜像:

docker buildx build --builder local-builder --platform linux/amd64,linux/arm64 --tag betterde/php:8.1-fpm --push .
[+] Building 20.4s (4/16)
 => [linux/arm64 1/6] FROM docker.io/library/php:8.1-fpm@sha256:66f22e43c5b2546cdc953d8fdd6ce994925207f3c3c958f59da00519afc8c548                                                     16.3s
 => => sha256:d4cbe7e5b3a1e3bf8dd36ca0f6c0975474c90c6a7002db1eab83ee683cdbf530 245B / 245B                                                                                            2.3s
 => => sha256:51922d488e38ac090f75c9f825d4030e42811615d2727509ff0e0bda221e15f9 2.45kB / 2.45kB                                                                                        2.3s
 => => sha256:db98555fb860484aa5b7e93135449c9efa6513b035bed0ec52ff312dbd09aa46 26.05MB / 26.05MB                                                                                      5.3s
 => => sha256:0643c2113c832fd423cbebe2ee0c779f249e90bf494e32e09fe7edc7e762d666 492B / 492B                                                                                            0.5s
 => => sha256:677ee647cc49e1ae8e670f191f4eac1ed4c2fb0f22d6a19ecef70f6469f199b6 11.90MB / 11.90MB                                                                                      4.8s
 => => sha256:632ec72a2e3295f16e0814b1568d1498d4940618e1cdf95dd056118af372eee8 224B / 224B                                                                                            0.5s
 => => sha256:abafaf8826eab2c82e152b7058ed06211207f6c7027c37225e1f1c19ebba793d 31.46MB / 86.72MB                                                                                     13.5s
 => => sha256:df8e44b0463f16c791d040e02e9c3ef8ec2a84245d365f088a80a22a455c71e8 27.26MB / 30.06MB                                                                                     13.4s
 => => sha256:2a21100b08aef7a04fe5e61ba611f278fa0bfe87fd02bbf28823b0df1dcea3b1 225B / 225B                                                                                            0.4s
 => [linux/amd64 1/6] FROM docker.io/library/php:8.1-fpm@sha256:66f22e43c5b2546cdc953d8fdd6ce994925207f3c3c958f59da00519afc8c548                                                     16.3s
 => => resolve docker.io/library/php:8.1-fpm@sha256:66f22e43c5b2546cdc953d8fdd6ce994925207f3c3c958f59da00519afc8c548                                                                  0.0s
 => => sha256:06ce6b583d54b66d9e12dd7504fd42abf3d3d8af02d5d63ab9ac34436e6e6081 8.62kB / 8.62kB                                                                                        0.4s
 => => sha256:454617d5c6c725ec1d97e379553019fb6fa4af02cf400ad221101790f69b7888 247B / 247B                                                                                            0.4s
 => => sha256:9e1d78cd066abba93a02e92179987869c2019532b221af8174fbde18e154cacb 2.45kB / 2.45kB                                                                                        0.4s
 => => sha256:c3fb5aa0621191d99a0819209f8b4d7a57d94c28aefaf1dd628d9111d053d650 26.22MB / 26.22MB                                                                                      7.0s
 => => sha256:a18ae08838ec8895e844b9547716fa8a9ff4ab5bee1747edb7d8669e183d3242 494B / 494B                                                                                            0.5s
 => => sha256:77f71a584e447d63c4666a2b48a6f88599ce646645ce553d76b1a4c66566fcba 12.12MB / 12.12MB                                                                                      3.5s
 => => sha256:4220e0c033772bd27ff577c6cc30afe1cd81296bf0d58615a56a8923c07c655a 271B / 271B                                                                                            0.4s
 => => sha256:e7793be89e9cdecbe7f44d7cdb803c08522f057e4fbdd2b0cc72019e378bb660 11.53MB / 91.63MB                                                                                      4.7s
 => => sha256:1e83b070fd9716453dd96b679a05d9d11c1f66d95582736a8d809d73a3f70c0d 227B / 227B                                                                                            0.4s
 => => sha256:bd159e379b3b1bc0134341e4ffdeab5f966ec422ae04818bb69ecef08a823b05 3.15MB / 31.42MB

可以看到两个平台的镜像正在同时进行构建,但是 linux/amd64 架构的镜像要远比 linux/arm64 的慢得多。

一切都构建完成后,可以使用如下命令查看构建的镜像信息:

docker buildx imagetools inspect betterde/php:8.1-fpm
Name:      docker.io/betterde/php:8.1-fpm
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Digest:    sha256:341d1a09197003b9e4f16abe46aa1b700d54d2b60c4514f760bcaa33ce849dc9

Manifests:
  Name:      docker.io/betterde/php:8.1-fpm@sha256:3433c5247713f44a57bceddb7d463c01783eaf0694f22884887f7b245ae64fb0
  MediaType: application/vnd.docker.distribution.manifest.v2+json
  Platform:  linux/amd64

  Name:      docker.io/betterde/php:8.1-fpm@sha256:1e34a95b96eb31bb32089f6435176f701b54d79b3f7108162674cee6f0bdf314
  MediaType: application/vnd.docker.distribution.manifest.v2+json
  Platform:  linux/arm64

可以看到镜像已经支持 linux/amd64 和 linux/arm64 两个平台了。

Docker Hub

在远程服务器上构建

在远端服务器上构建又大致分为三种方式:

  • 在 Kubernetes 集群上运行 Buildkit Container 使用 QEMU 模拟;
  • 使用 Buildx CLI 并使用 docker-container 驱动 通过 SSH 或者 TCP Sock 连接到远端 Docker 自动创建 Buildkit Container 容器实例;
  • 预先部署好 Buildkit Container (容器或者宿主机),然后暴露 TCP 1234 端口,再使用 Buildx CLI 的 remote 驱动进行连接。

这里略过 Kubernetes 相关的多平台构建,主要分析一下后两种的区别。

使用 docker-container 驱动

docker buildx create --name builder \
  --node linux-amd64 \
  --driver docker-container \
  --platform  linux/amd64,linux/386 \
  --bootstrap \
  ssh://USER@HOST
[+] Building 17.7s (1/1) FINISHED
 => [internal] booting buildkit                                17.3s
 => => pulling image moby/buildkit:buildx-stable-1             15.7s
 => => creating container buildx_buildkit_staging

这是后登录到远端服务器上查看容器运行状态:

docker ps | grep buildx
75001bb07b22   moby/buildkit:buildx-stable-1    "buildkitd"    1 minutes ago   Up 1 minutes    buildx_buildkit_staging

使用 remote 驱动

在远端服务器上运行如下命令,创建 Buildkit Container:

docker run -d --rm \
  --name=remote-buildkitd \
  --privileged \
  -p 1234:1234 \
  -v /root/smallstep:/etc/buildkit/certs \
  moby/buildkit:latest \
  --addr tcp://0.0.0.0:1234 \
  --tlscacert /etc/buildkit/certs/daemon/root_ca.crt \
  --tlscert /etc/buildkit/certs/daemon/docker-daemon.crt \
  --tlskey /etc/buildkit/certs/daemon/docker-daemon.key

docker ps | grep remote-buildkitd
8ea4c52b97a8   moby/buildkit:latest   "buildkitd --addr tc…"   1 minutes ago   Up 1 minutes   0.0.0.0:1234->1234/tcp, :::1234->1234/tcp        remote-buildkitd

使用 Buildx CLI 创建远端实例:

docker buildx create \
  --use \
  --name builder \
  --node linux-amd64 \
  --platform linux/amd64,linux/386 \
  --driver remote \
  --driver-opt key=/Users/George/Desktop/smallstep/docker-client.key \
  --driver-opt cert=/Users/George/Desktop/smallstep/docker-client.crt \
  --driver-opt cacert=/Users/George/Desktop/smallstep/root_ca.crt \
  tcp://HOST:1234

docker buildx ls
NAME/NODE     DRIVER/ENDPOINT           STATUS  BUILDKIT PLATFORMS
builder *       remote
  linux-amd64   tcp://47.103.204.136:1234 running          linux/amd64*, linux/386*
default         docker
  default       default                   running 20.10.21 linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
desktop-linux   docker
  desktop-linux desktop-linux             running 20.10.21 linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6

docker buildx create [OPTIONS] [CONTEXT|ENDPOINT] 虽然是支持 Docker Context,但 CONTEXT 只适用于 dockerdocker-container 驱动!

构建多平台镜像

在构建之前,还需要在 ARM 的 macOS 上添加 linux/arm64 的 Builder:

docker run -d \
  --name=remote-buildkitd \
  --privileged \
  -p 1234:1234 \
  -v /YOURPATH/certs:/etc/buildkit/certs \
  moby/buildkit:latest \
  --addr tcp://0.0.0.0:1234 \
  --tlscacert /etc/buildkit/certs/root_ca.crt \
  --tlscert /etc/buildkit/certs/docker-daemon.crt \
  --tlskey /etc/buildkit/certs/docker-daemon.key

docker buildx create \
  --append \
  --name builder \
  --node linux-arm64 \
  --platform linux/arm64 \
  --driver remote \
  --driver-opt key=/YOURPATH/docker-client.key \
  --driver-opt cert=/YOURPATH/docker-client.crt \
  --driver-opt cacert=/YOURPATH/root_ca.crt \
  tcp://127.0.0.1:1234

docker buildx ls
builder         remote
  linux-amd64   tcp://47.103.204.136:1234 running          linux/amd64, linux/386
  linux-arm64   tcp://127.0.0.1:1234      running v0.11.1  linux/arm64*
default *       docker
  default       default                   running 20.10.21 linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
desktop-linux   docker
  desktop-linux desktop-linux             running 20.10.21 linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
docker buildx build --builder builder --platform linux/amd64,linux/arm64 --tag betterde/php:8.2-fpm --push .

其他解决方案

Depot 是一个云构建平台,提供原生的 ARM 构建环境,同时支持自托管构建节点。

I hope this is helpful, Happy hacking…