使用 buildx 构建多平台 Docker 镜像

在工作和生活中,我们可能经常需要将某个程序跑在不同的 CPU 架构上,比如让某些不可描述的软件运行在树莓派或嵌入式路由器设备上。特别是 Docker 席卷全球之后,我们可以轻松地在 ARM 设备上通过容器部署各种好玩的应用,而不用在意各种系统的差异性。

但是想要跨平台构建 Docker 镜像可不是一件轻松的活,要么到不同 CPU 架构的系统上全部构建一遍,要么就得在当前系统上通过虚拟化技术模拟不同的 CPU 架构,最后可能还要想办法合并镜像,费力不讨好。

不过值得庆幸的是,Docker 19.03 引入了一个新的实验性插件,该插件使得跨平台构建 Docker 镜像比以往更加容易了。在介绍这个新特性之前,我们先来了解一下跨 CPU 架构构建程序的基础知识。

构建多平台 Docker 镜像

利用 Docker 19.03 引入的插件 buildx,可以很轻松地构建多平台 Docker 镜像。buildx 是 docker build ... 命令的下一代替代品,它利用 BuildKit 的全部功能扩展了 docker build 的功能。

下面就来演示一下如何在短短几分钟内使用 buildx 构建出不同平台的 Docker 镜像。步骤如下:

启用 buildx 插件

开启 Dockerd 的实验特性

要想使用 buildx,首先要确保 Docker 版本不低于 19.03,同时还要通过设置环境变量 DOCKER_CLI_EXPERIMENTAL 来启用。可以通过下面的命令来为当前终端启用 buildx 插件:

1
export DOCKER_CLI_EXPERIMENTAL=enabled

或者

编辑 /etc/docker/daemon.json,新增如下条目

1
2
3
{
"experimental": true
}

验证是否开启:

1
2
docker buildx version
github.com/docker/buildx v0.5.1-docker 11057da37336192bfc57d81e02359ba7ba848e4a

如果在某些系统上设置环境变量 DOCKER_CLI_EXPERIMENTAL 不生效(比如 Arch Linux),你可以选择从源代码编译:

1
2
3
export DOCKER_BUILDKIT=1
docker build --platform=local -o . git://github.com/docker/buildx
mkdir -p ~/.docker/cli-plugins && mv buildx ~/.docker/cli-plugins/docker-buildx

docker buildx介绍https://docs.docker.com/buildx/working-with-buildx/

docker buildx --help,docker buildx COMMAND --help查看用法,或者参考https://docs.docker.com/engine/reference/commandline/buildx/

启用 binfmt_misc

如果你使用的是 Docker 桌面版(MacOS 和 Windows),默认已经启用了 binfmt_misc,可以跳过这一步。

如果你使用的是 Linux,需要手动启用 binfmt_misc。大多数 Linux 发行版都很容易启用,不过还有一个更容易的办法,直接运行一个特权容器,容器里面写好了设置脚本:

1
docker run --rm --privileged docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64

建议将 Linux 内核版本升级到 4.x 以上,特别是 CentOS 用户,你可能会遇到错误。

在软件依赖中我们提到需要 Linux 内核版本>= 4.8.0;如果在内核版本为3.10.0的系统(比如 CentOS)上运行 docker/binfmt,会出现报错 Cannot write to /proc/sys/fs/binfmt_misc/register: write /proc/sys/fs/binfmt_misc/register: invalid argument,这是由于内核不支持(F)标志造成的。出现这种情况,建议您升级系统内核或者换使用较高版本内核的 Linux 发行版。

验证是 binfmt_misc 否开启:

1
ls -al /proc/sys/fs/binfmt_misc/

image-20210730111258966

验证是否启用了相应的处理器:

1
cat /proc/sys/fs/binfmt_misc/qemu-aarch64

image-20210730111322768

从默认的构建器切换到多平台构建器

Docker 默认会使用不支持多 CPU 架构的构建器,我们需要手动切换。

先创建一个新的构建器:

1
2
docker buildx create --use --name mybuilder
docker buildx use mybuilder

启动构建器:

1
docker buildx inspect mybuilder --bootstrap

image-20210730111437558

查看当前使用的构建器及构建器支持的 CPU 架构,可以看到支持很多 CPU 架构:

1
docker buildx ls

image-20210730111505853

构建多平台镜像

现在我们就可以构建支持多 CPU 架构的镜像了!假设有一个简单的 golang 程序源码:

1
2
3
4
5
6
7
8
9
10
11
12
cat > hello.go <<EOF
package main

import (
"fmt"
"runtime"
)

func main() {
fmt.Printf("Hello, %s!\n", runtime.GOARCH)
}
EOF

创建一个 Dockerfile 将该应用容器化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cat > Dockerfile <<EOF
FROM golang:alpine AS builder
ENV GO111MODULE auto
RUN mkdir /app
ADD . /app/
WORKDIR /app
RUN go build -o hello .

FROM alpine
RUN mkdir /app
WORKDIR /app
COPY --from=builder /app/hello .
CMD ["./hello"]
EOF

这是一个多阶段构建 Dockerfile,使用 Go 编译器来构建应用,并将构建好的二进制文件拷贝到 alpine 镜像中。

现在就可以使用 buildx 构建一个支持 arm、arm64 和 amd64 多架构的 Docker 镜像了,同时将其推送到 Docker Hub

1
docker buildx build -t yaokun/hello --platform=linux/arm,linux/arm64,linux/amd64 . --push

需要提前通过 docker login 命令登录认证 Docker Hub。

image-20210730112155120

现在就可以通过 docker pull yaokun/hello 拉取刚刚创建的镜像了,Docker 将会根据你的 CPU 架构拉取匹配的镜像。

image-20210730113757557

背后的原理也很简单,之前已经提到过了,buildx 会通过 QEMUbinfmt_misc 分别为 3 个不同的 CPU 架构(arm,arm64 和 amd64)构建 3 个不同的镜像。构建完成后,就会创建一个 manifest list,其中包含了指向这 3 个镜像的指针。

如果想将构建好的镜像保存在本地,可以将 type 指定为 docker,但必须分别为不同的 CPU 架构构建不同的镜像,不能合并成一个镜像,即:

1
2
3
docker buildx build -t yaokun/hello --platform=linux/arm -o type=docker .
docker buildx build -t yaokun/hello --platform=linux/arm64 -o type=docker .
docker buildx build -t yaokun/hello --platform=linux/amd64 -o type=docker .

测试多平台镜像

由于之前已经启用了 binfmt_misc,现在我们就可以运行任何 CPU 架构的 Docker 镜像了,因此可以在本地系统上测试之前生成的 3 个镜像是否有问题。

首先列出每个镜像的 digests

1
docker buildx imagetools inspect yaokun/hello

image-20210730113905794

运行每一个镜像并观察输出结果:

1
2
3
docker run --rm docker.io/yaokun/hello:latest@sha256:957012ecd4bbfc3b1fe6766163c52a54f1ca86c1a941992819f581abad7e3b6a
docker run --rm docker.io/yaokun/hello:latest@sha256:77dfa0171d8fa646f6903654068b5de5a0d0867d43caa18f92d3ed4ebf7c3754
docker run --rm docker.io/yaokun/hello:latest@sha256:8a85cf1b5dbabc18e32c2a14a47542756dd7d3054e57fb338d89aa8ee8178f35

image-20210730114236944

参考链接:

使用 buildx 构建多平台 Docker 镜像