多平台构建

多平台构建是指一次构建调用,该调用针对多种不同的操作系统或CPU架构组合。在构建镜像时,这使您能够创建一个可以在多个平台上运行的单个镜像,例如 linux/amd64、linux/arm64 和 windows/amd64。

为什么要进行多平台构建?

Docker 通过将应用程序及其依赖项打包到容器中,解决了“在我的机器上可以运行”的问题。这使得在不同的环境中(如开发、测试和生产)运行相同的应用程序变得容易。

但仅靠容器化只能解决部分问题。容器共享主机内核,这意味着在容器内运行的代码必须与主机架构兼容。这就是为什么你不能在arm64主机上运行linux/amd64容器(不使用模拟),或在Linux主机上运行Windows容器。

多平台构建通过将同一应用程序的多个变体打包到一个镜像中来解决此问题。这使您能够在不同类型的硬件上运行相同的镜像,例如在运行 x86-64 的开发机器或基于 ARM 的 Amazon EC2 实例上,无需使用模拟。

单平台镜像与多平台镜像的区别

多平台镜像的结构与单平台镜像不同。 单平台镜像包含一个指向单个配置和单个层集的清单。 多平台镜像包含一个清单列表,指向多个清单,每个清单指向不同的配置和层集。

Multi-platform image structure

当你将多平台镜像推送到注册表时,注册表会存储清单列表以及所有单独的清单。当你拉取镜像时,注册表会返回清单列表,Docker 会根据主机的架构自动选择正确的变体。例如,如果你在基于 ARM 的 Raspberry Pi 上运行多平台镜像,Docker 会选择 linux/arm64 变体。如果你在同一镜像上运行 x86-64 笔记本电脑,Docker 会选择 linux/amd64 变体(如果你使用的是 Linux 容器)。

前提条件

要构建多平台镜像,您首先需要确保 Docker 环境已设置为支持多平台。有两种方法可以实现:

  • 您可以从“经典”镜像存储切换到containerd镜像存储。
  • 您可以创建并使用自定义构建器。

Docker Engine 的“经典”镜像仓库不支持多平台镜像。切换到 containerd 镜像仓库可确保您的 Docker Engine 能够推送、拉取和构建多平台镜像。

创建一个使用支持多平台的驱动程序的自定义构建器(例如 docker-container 驱动程序),可以在不切换到不同镜像存储的情况下构建多平台镜像。但是,您仍然无法将构建的多平台镜像加载到 Docker Engine 镜像存储中。不过,您可以直接使用 docker build --push 将它们推送到容器注册表。


启用 containerd 镜像存储的步骤取决于您使用的是 Docker Desktop 还是独立的 Docker Engine:

要创建自定义构建器,请使用 docker buildx create 命令创建一个使用 docker-container 驱动程序的构建器。

$ docker buildx create \
  --name container-builder \
  --driver docker-container \
  --bootstrap --use

注意

使用 docker-container 驱动程序构建的镜像不会自动加载到您的 Docker 引擎镜像存储中。有关更多信息,请参阅 构建驱动程序。


如果你正在使用独立的 Docker Engine,并且需要使用模拟来构建多平台镜像,你也需要安装 QEMU,请参阅 手动安装 QEMU。

构建多平台镜像

当触发构建时,使用 --platform 标志来定义构建输出的目标平台,例如 linux/amd64 和 linux/arm64:

$ docker buildx build --platform linux/amd64,linux/arm64 .

策略

您可以根据使用情况,使用三种不同的策略构建多平台镜像:

  1. 使用模拟,通过 QEMU
  2. 使用具有 多个本机节点 的构建器
  3. 使用 跨平台构建 与 多阶段构建

QEMU

在支持 QEMU 模拟的构建器上构建多平台镜像是最简单的入门方式。使用模拟无需更改您的 Dockerfile,BuildKit 会自动检测可用于模拟的架构。

注意

使用 QEMU 进行仿真通常比原生构建慢得多,尤其是在编译和压缩或解压缩等计算密集型任务中。

使用 多个本机节点 或 交叉编译,如果可能的话。

Docker Desktop 默认支持在仿真环境下运行和构建多平台镜像。无需进行任何配置,构建器会使用 Docker Desktop 虚拟机中捆绑的 QEMU。

手动安装 QEMU

如果你在 Docker Desktop 之外使用构建器,例如在 Linux 上使用 Docker Engine,或使用自定义远程构建器,你需要在主机操作系统上安装 QEMU 并注册可执行文件类型。安装 QEMU 的前提条件是:

  • Linux 内核版本 4.8 或更高版本
  • binfmt-support 版本 2.1.7 或更高版本
  • QEMU Binaries必须静态编译并使用 fix_binary 标志进行注册

使用 tonistiigi/binfmt 镜像以一条命令在主机上安装 QEMU 并注册可执行文件类型:

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

这将安装 QEMU Binaries,并将其注册到 binfmt_misc,从而启用 QEMU 执行非本机文件格式以进行模拟。

一旦在主机操作系统上安装了 QEMU 并注册了可执行文件类型, 它们会在容器内透明地工作。您可以通过检查 /proc/sys/fs/binfmt_misc/qemu-* 中的标志是否包含 F 来验证您的注册。

多个原生节点

使用多个原生节点可以为 QEMU 无法处理的更复杂案例提供更好的支持,并且性能也更佳。

您可以使用 --append 标志向构建器添加其他节点。

以下命令从名为node-amd64和node-arm64的 Docker 上下文中创建一个多节点构建器。此示例假定您已添加了这些上下文。

$ docker buildx create --use --name mybuild node-amd64
mybuild
$ docker buildx create --append --name mybuild node-arm64
$ docker buildx build --platform linux/amd64,linux/arm64 .

虽然这种方法比模拟具有优势,但管理多节点构建器会带来一些设置和管理构建器集群的开销。 或者,您可以使用 Docker 构建云,这是一个在 Docker 基础设施上提供托管多节点构建器的服务。使用 Docker 构建云,您可以在无需维护的情况下获得原生多平台 ARM 和 X86 构建器。使用云构建器还提供了额外的好处,例如共享构建缓存。

在注册 Docker 构建云后,将构建器添加到您的本地环境中并开始构建。

$ docker buildx create --driver cloud <ORG>/<BUILDER_NAME>
cloud-<ORG>-<BUILDER_NAME>
$ docker build \
  --builder cloud-<ORG>-<BUILDER_NAME> \
  --platform linux/amd64,linux/arm64,linux/arm/v7 \
  --tag <IMAGE_NAME> \
  --push .

欲了解更多信息,请参阅 Docker 构建云。

Cross-compilation

根据您的项目,如果所使用的编程语言对交叉编译支持良好,您可以利用多阶段构建从构建者本机架构为目标平台构建Binaries。特殊的构建参数,例如 BUILDPLATFORM 和 TARGETPLATFORM,会在 Dockerfile 中自动可用。

在以下示例中,FROM 指令被固定到构建器的本机平台(使用 --platform=$BUILDPLATFORM 选项)以防止仿真启动。然后在 RUN 指令中插入预定义的 $BUILDPLATFORM 和 $TARGETPLATFORM 构建参数。在这种情况下,这些值只是使用 echo 打印到 stdout,但这说明了如何将它们传递给编译器进行交叉编译。

# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:alpine AS build
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "I am running on $BUILDPLATFORM, building for $TARGETPLATFORM" > /log
FROM alpine
COPY --from=build /log /log

示例

以下是一些多平台构建的示例:

使用模拟器的简单多平台构建

此示例演示如何使用 QEMU 模拟构建简单的多平台镜像。该镜像包含一个文件,用于打印容器的架构。

Prerequisites:

  • Docker Desktop,或带有已安装的 QEMU 的 Docker Engine
  • containerd 镜像存储已启用

步骤:

  1. 创建一个空目录并导航到它:

    $ mkdir multi-platform
    $ cd multi-platform
    
  2. 创建一个简单的 Dockerfile,打印容器的架构:

    # syntax=docker/dockerfile:1
    FROM alpine
    RUN uname -m > /arch
  3. 构建 linux/amd64 和 linux/arm64 的镜像:

    $ docker build --platform linux/amd64,linux/arm64 -t multi-platform .
    
  4. 运行镜像并打印架构:

    $ docker run --rm multi-platform cat /arch
    
    • 如果你在x86-64机器上运行,你应该看到 x86_64。
    • 如果你在 ARM 机器上运行,你应该看到 aarch64。

使用 Docker Build Cloud 的多平台 Neovim 构建

此示例演示如何使用 Docker 构建云运行多平台构建,以编译并导出 Neovim Binaries 适用于 linux/amd64 和 linux/arm64 平台。

Docker 构建云提供托管的多节点构建器,支持原生的多平台构建,无需仿真,从而大大加快编译等 CPU 密集型任务的执行速度。

Prerequisites:

步骤:

  1. 创建一个空目录并导航到它:

    $ mkdir docker-build-neovim
    $ cd docker-build-neovim
    
  2. 创建一个Dockerfile来构建Neovim。

    # syntax=docker/dockerfile:1
    FROM debian:bookworm AS build
    WORKDIR /work
    RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
        --mount=type=cache,target=/var/lib/apt,sharing=locked \
        apt-get update && apt-get install -y \
        build-essential \
        cmake \
        curl \
        gettext \
        ninja-build \
        unzip
    ADD https://github.com/neovim/neovim.git#stable .
    RUN make CMAKE_BUILD_TYPE=RelWithDebInfo
    
    FROM scratch
    COPY --from=build /work/build/bin/nvim /
  3. 使用 Docker Build Cloud 为 linux/amd64 和 linux/arm64 构建镜像:

    $ docker build \
       --builder <cloud-builder> \
       --platform linux/amd64,linux/arm64 \
       --output ./bin .
    

    此命令使用云构建器构建镜像,并将Binaries导出到 bin 目录。

  4. 验证是否为两个平台都构建了Binaries。你应该看到为 linux/amd64 和 linux/arm64 的 nvim Binaries。

    $ tree ./bin
    ./bin
    ├── linux_amd64
    │   └── nvim
    └── linux_arm64
        └── nvim
    
    3 directories, 2 files
    

跨平台编译 Go 应用程序

此示例演示如何使用多阶段构建为多个平台交叉编译 Go 应用程序。该应用程序是一个简单的 HTTP 服务器,它在端口 8080 上监听并返回容器的架构。此示例使用 Go,但相同的原理适用于支持交叉编译的其他编程语言。

使用 Docker 构建进行交叉编译的工作原理是利用一系列预定义(在 BuildKit 中)的构建参数,这些参数提供了有关构建器平台和构建目标平台的信息。您可以使用这些预定义参数将平台信息传递给编译器。

在 Go 中,您可以使用 GOOS 和 GOARCH 环境变量来指定要构建的目标平台。

Prerequisites:

  • Docker Desktop 或 Docker Engine

步骤:

  1. 创建一个空目录并导航到它:

    $ mkdir go-server
    $ cd go-server
    
  2. 创建一个基础的 Dockerfile,用于构建 Go 应用程序:

    # syntax=docker/dockerfile:1
    FROM golang:alpine AS build
    WORKDIR /app
    ADD https://github.com/dvdksn/buildme.git#eb6279e0ad8a10003718656c6867539bd9426ad8 .
    RUN go build -o server .
    
    FROM alpine
    COPY --from=build /app/server /server
    ENTRYPOINT ["/server"]

    此 Dockerfile 尚无法通过交叉编译构建多平台。如果您尝试使用 docker build 构建此 Dockerfile,构建器将尝试使用仿真为指定平台构建镜像。

  3. 要添加交叉编译支持,请更新 Dockerfile 以使用预定义的 BUILDPLATFORM 和 TARGETPLATFORM 构建参数。当您在使用 docker build 时带上 --platform 标志,这些参数会自动在 Dockerfile 中可用。

    • 将 golang 镜像固定到构建器的平台,使用 --platform=$BUILDPLATFORM 选项。
    • 为 Go 编译阶段添加 ARG 指令,使 TARGETOS 和 TARGETARCH 构建参数可用于本阶段的命令。
    • 设置环境变量 GOOS 和 GOARCH 的值为 TARGETOS 和 TARGETARCH。Go 编译器使用这些变量进行交叉编译。

    # syntax=docker/dockerfile:1
    FROM --platform=$BUILDPLATFORM golang:alpine AS build
    ARG TARGETOS
    ARG TARGETARCH
    WORKDIR /app
    ADD https://github.com/dvdksn/buildme.git#eb6279e0ad8a10003718656c6867539bd9426ad8 .
    RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o server .
    
    FROM alpine
    COPY --from=build /app/server /server
    ENTRYPOINT ["/server"]
    # syntax=docker/dockerfile:1
    FROM golang:alpine AS build
    WORKDIR /app
    ADD https://github.com/dvdksn/buildme.git#eb6279e0ad8a10003718656c6867539bd9426ad8 .
    RUN go build -o server .
    
    FROM alpine
    COPY --from=build /app/server /server
    ENTRYPOINT ["/server"]
    # syntax=docker/dockerfile:1
    -FROM golang:alpine AS build
    +FROM --platform=$BUILDPLATFORM golang:alpine AS build
    +ARG TARGETOS
    +ARG TARGETARCH
    WORKDIR /app
    ADD https://github.com/dvdksn/buildme.git#eb6279e0ad8a10003718656c6867539bd9426ad8 .
    -RUN go build -o server .
    RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o server .
    
    FROM alpine
    COPY --from=build /app/server /server
    ENTRYPOINT ["/server"]
    

  4. 构建 linux/amd64 和 linux/arm64 的镜像:

    $ docker build --platform linux/amd64,linux/arm64 -t go-server .
    

此示例展示了如何使用 Docker 构建为多个平台交叉编译 Go 应用程序。具体交叉编译步骤可能因您使用的编程语言而异。请查阅您所使用编程语言的文档,了解如何为不同平台进行交叉编译的更多信息。

提示

你可能还想考虑查看一下 xx - Dockerfile 跨平台编译助手。 xx 是一个包含实用脚本的 Docker 镜像,使使用 Docker 构建进行跨平台编译变得更加容易。