使用 buildx 构建多平台 Docker 镜像

注意
本文最后更新于 2024-05-08,文中内容可能已过时。

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