多平台构建

多平台构建是指一个构建调用可以同时针对多个不同的操作系统或 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 Engine 镜像存储中。有关更多信息,请参阅构建驱动程序


如果您使用的是独立的 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 二进制文件必须是静态编译的,并使用fix_binary标志注册

使用tonistiigi/binfmt镜像,可以使用单个命令安装 QEMU 并注册主机上的可执行文件类型。

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

这将安装 QEMU 二进制文件并使用binfmt_misc注册它们,使 QEMU 能够执行用于模拟的非原生文件格式。

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

多个原生节点

使用多个原生节点可以更好地支持 QEMU 无法处理的更复杂的情况,并且性能也更好。

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

以下命令根据名为node-amd64node-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 Build Cloud,这是一项在 Docker 的基础设施上提供托管多节点构建器的服务。使用 Docker Build Cloud,您可以获得原生的多平台 ARM 和 X86 构建器,而无需维护它们。使用云构建器还可以提供其他好处,例如共享构建缓存。

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

$ 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 Build Cloud

交叉编译

根据您的项目,如果使用的编程语言对交叉编译有良好的支持,您可以利用多阶段构建从构建器的原生架构为目标平台构建二进制文件。特殊的构建参数,例如BUILDPLATFORMTARGETPLATFORM,会在您的 Dockerfile 中自动可用。

在下面的示例中,FROM指令固定到构建器的原生平台(使用--platform=$BUILDPLATFORM选项)以防止模拟启动。然后在RUN指令中内插预定义的$BUILDPLATFORM$TARGETPLATFORM构建参数。在本例中,值只是使用echo打印到标准输出,但这说明了如何将它们传递给编译器进行交叉编译。

# 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 模拟构建简单的多平台镜像。该镜像包含一个打印容器架构的单个文件。

先决条件

  • 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/amd64linux/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 Build Cloud 运行多平台构建,以编译和导出Neovimlinux/amd64linux/arm64平台的二进制文件。

Docker Build Cloud 提供托管的多节点构建器,支持原生多平台构建,无需模拟,因此可以更快地完成编译等 CPU 密集型任务。

先决条件

步骤

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

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

    # 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/amd64linux/arm64构建镜像

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

    此命令使用云构建器构建镜像并将二进制文件导出到bin目录。

  4. 验证是否为这两个平台构建了二进制文件。您应该看到linux/amd64linux/arm64nvim二进制文件。

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

交叉编译 Go 应用程序

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

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

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

先决条件

  • Docker Desktop 或 Docker Engine

步骤

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

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

    # 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 以使用预定义的BUILDPLATFORMTARGETPLATFORM构建参数。当您在docker build中使用--platform标志时,这些参数会在 Dockerfile 中自动可用。

    • 使用--platform=$BUILDPLATFORM选项将golang镜像固定到构建器的平台。
    • 为 Go 编译阶段添加ARG指令,使TARGETOSTARGETARCH构建参数可用于此阶段的命令。
    • GOOSGOARCH环境变量设置为TARGETOSTARGETARCH的值。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/amd64linux/arm64构建镜像

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

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

提示

您可能还需要考虑查看xx - Dockerfile 交叉编译助手xx是一个包含实用程序脚本的 Docker 镜像,使 Docker 构建的交叉编译更容易。