使用 buildx 构建多平台 Docker 镜像

警告
本文最后更新于 2022-07-01,文中内容可能已过时。

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

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

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

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

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

开启 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/

如果你使用的是 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/

https://img.bwcxtech.com/img/20210730111341.png

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

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

https://img.bwcxtech.com/img/20210730111344.png

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

先创建一个新的构建器:

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

启动构建器:

1
docker buildx inspect mybuilder --bootstrap

https://img.bwcxtech.com/img/20210730111444.png

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

1
docker buildx ls

https://img.bwcxtech.com/img/20210730111513.png

现在我们就可以构建支持多 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。

https://img.bwcxtech.com/img/20210730112156.png

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

https://img.bwcxtech.com/img/20210730113758.png

背后的原理也很简单,之前已经提到过了,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

https://img.bwcxtech.com/img/20210730113915.png

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

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

https://img.bwcxtech.com/img/20210730114249.png

参考链接:

使用 buildx 构建多平台 Docker 镜像