使用 buildx 构建多平台 Docker 镜像

在工作和生活中,我们可能经常需要将某个程序跑在不同的 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 插件:

bash

export DOCKER_CLI_EXPERIMENTAL=enabled

或者

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

json

{
  "experimental": true
}

bash

export DOCKER_BUILDKIT=1
wget https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-amd64
mkdir -p ~/.docker/cli-plugins
mv buildx-v0.10.0.linux-amd64 ~/.docker/cli-plugins/docker-buildx
chmod +x ~/.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/

安装模拟器的主要作用是让 buildx 支持跨 CPU 架构编译。

bash

docker run --rm --privileged docker/binfmt --install all

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

验证

text

docker buildx ls

会看到running 20.10.21 linux/amd64, linux/386, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/arm/v7, linux/arm/v6

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

先创建一个新的构建器:

bash

docker buildx create --use --name mybuilder
docker buildx use mybuilder

启动构建器:

bash

docker buildx inspect mybuilder --bootstrap

image-20210730111437558

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

bash

docker buildx ls

image-20210730111505853

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

bash

cat > hello.go <<EOF
package main

import (
        "fmt"
        "runtime"
)

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

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

bash

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

bash

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 架构构建不同的镜像,不能合并成一个镜像,即:

bash

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

bash

docker buildx imagetools inspect yaokun/hello

image-20210730113905794

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

bash

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 镜像