冒险岛079

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 5|回复: 0

Docker多架构镜像构建:兼容ARM与x86

[复制链接]

621

主题

13

回帖

1987

积分

管理员

积分
1987
发表于 昨天 08:46 | 显示全部楼层 |阅读模式
2024 年,我在一台 M2 MacBook 上 build 了一个镜像,推到服务器,然后它就崩了。

报错信息简洁有力:
  1. exec format error
复制代码


翻译成人话就是——你在 ARM 上烤的饼,x86 的烤箱不认。这个问题,每一个用 Apple Silicon 的开发者迟早都会遇到。而解决它的最佳方案,就是 Docker 多架构镜像构建

先搞清楚:什么是多架构镜像?

传统 Docker 镜像是"一个镜像对应一个平台"。你在 x86 机器上 build 的镜像,只能在 x86 上跑;ARM 机器上 build 的,只能在 ARM 上跑。

多架构镜像的本质是一个 manifest list——它本身不包含任何文件层,而是一个"目录",指向不同平台的实际镜像。当你执行
  1. docker pull
复制代码
时,Docker 会自动根据当前机器的架构,从 manifest list 中选择匹配的那个版本拉取。

用一个生活化的类比:manifest list 就像一个多语言菜单,中国人拿到中文版,日本人拿到日文版,但封面写的是同一个餐厅名字。

涉及的核心架构主要有两种:

| 架构标识 | 典型设备 |
|---|---|
|
  1. linux/amd64
复制代码
| 绝大多数云服务器、传统 PC |
|
  1. linux/arm64
复制代码
| Apple Silicon Mac、树莓派4、AWS Graviton |

当然还有
  1. linux/arm/v7
复制代码
(老款树莓派)等,但 amd64 和 arm64 覆盖了 95% 的场景。

工具准备:Docker Buildx 与 QEMU

Docker Buildx 是 Docker 官方提供的增强构建工具,从 Docker 19.03 开始内置。它基于 BuildKit,支持多平台构建、缓存导出等高级特性。

第一步:确认 Buildx 可用。
  1. docker buildx version
复制代码

如果输出版本号,说明已经就绪。

第二步:创建并启用一个支持多平台的 builder 实例。
  1. docker buildx create --name multiarch-builder --driver docker-container --bootstrap --use
复制代码

这里有个关键参数:
  1. --driver docker-container
复制代码
。默认的
  1. docker
复制代码
驱动不支持多平台构建,必须切换到
  1. docker-container
复制代码
驱动,它会启动一个独立的 BuildKit 容器来执行构建任务。

第三步:检查支持的平台列表。
  1. docker buildx inspect --bootstrap
复制代码

你会看到类似这样的输出:
  1. Platforms: linux/amd64, linux/arm64, linux/arm/v7, linux/386...
复制代码

这些平台支持来自 QEMU 用户态模拟器。简单说,QEMU 允许你在 x86 机器上模拟 ARM 指令集(反之亦然),这样你就能在一台机器上为多个平台编译镜像。

在 Docker Desktop(Mac/Windows)中,QEMU 已经预装好了。如果你用的是 Linux 服务器,需要手动安装:
  1. docker run --privileged --rm tonistiigi/binfmt --install all
复制代码

这条命令会注册所有支持的二进制格式处理器到内核中。执行一次即可,重启后需要重新执行(除非你做了持久化配置)。

实战:构建你的第一个多架构镜像

假设我们有一个简单的 Go 应用,Dockerfile 如下:
  1. FROM golang:1.22-alpine AS builder
  2. WORKDIR /app
  3. COPY . .
  4. RUN CGO_ENABLED=0 go build -o server .
  5. FROM alpine:3.19
  6. COPY --from=builder /app/server /usr/local/bin/server
  7. EXPOSE 8080
  8. CMD ["server"]
复制代码

这个 Dockerfile 不需要任何修改就能支持多架构构建——因为
  1. golang:1.22-alpine
复制代码
  1. alpine:3.19
复制代码
本身就是多架构镜像,Docker 会自动拉取对应平台的基础镜像。

一条命令搞定构建并推送:
  1. docker buildx build \
  2.   --platform linux/amd64,linux/arm64 \
  3.   -t your-registry/your-app:latest \
  4.   --push \
  5.   .
复制代码

几个要点需要注意:

    1. --platform
    复制代码
    参数指定目标平台,逗号分隔。
    1. --push
    复制代码
    是必须的。多平台镜像无法直接
    1. --load
    复制代码
    到本地 Docker(因为本地只能存一个平台的镜像),所以必须推送到远程仓库。
  • 如果你只是想本地测试单个平台,可以单独指定:
    1. --platform linux/arm64 --load
    复制代码


    构建完成后,验证 manifest list:
    1. docker buildx imagetools inspect your-registry/your-app:latest
    复制代码

    你会看到它包含两个平台的 digest,确认构建成功。

    进阶:Dockerfile 中的架构感知

    有些场景下,你需要在 Dockerfile 内部根据架构做不同处理。比如下载预编译的二进制文件时,不同架构的下载链接不同。

    Buildx 会自动注入几个构建参数:
    1. FROM alpine:3.19
    2. ARG TARGETPLATFORM
    3. ARG TARGETARCH
    4. RUN echo "Building for ${TARGETPLATFORM}, arch: ${TARGETARCH}"
    5. RUN if [ "${TARGETARCH}" = "amd64" ]; then \
    6.       wget -O /usr/local/bin/tool https://example.com/tool-x86_64; \
    7.     elif [ "${TARGETARCH}" = "arm64" ]; then \
    8.       wget -O /usr/local/bin/tool https://example.com/tool-aarch64; \
    9.     fi && \
    10.     chmod +x /usr/local/bin/tool
    复制代码
    1. TARGETARCH
    复制代码
    的值会是
    1. amd64
    复制代码
    1. arm64
    复制代码
    等,
    1. TARGETPLATFORM
    复制代码
    则是完整的
    1. linux/amd64
    复制代码
    格式。这两个变量不需要在
    1. docker buildx build
    复制代码
    命令中手动传入,BuildKit 会自动设置。

    CI/CD 集成:GitHub Actions 实战

    在 CI 中实现自动化多架构构建,GitHub Actions 是最常见的选择。以下是一个完整的工作流配置:
    1. name: Build Multi-Arch Image
    2. on:
    3.   push:
    4.     tags: ['v*']
    5. jobs:
    6.   build:
    7.     runs-on: ubuntu-latest
    8.     steps:
    9.       - uses: actions/checkout@v4
    10.       - name: Set up QEMU
    11.         uses: docker/setup-qemu-action@v3
    12.       - name: Set up Docker Buildx
    13.         uses: docker/setup-buildx-action@v3
    14.       - name: Login to Docker Hub
    15.         uses: docker/login-action@v3
    16.         with:
    17.           username: ${{ secrets.DOCKERHUB_USERNAME }}
    18.           password: ${{ secrets.DOCKERHUB_TOKEN }}
    19.       - name: Build and push
    20.         uses: docker/build-push-action@v5
    21.         with:
    22.           context: .
    23.           platforms: linux/amd64,linux/arm64
    24.           push: true
    25.           tags: |
    26.             your-dockerhub-user/your-app:${{ github.ref_name }}
    27.             your-dockerhub-user/your-app:latest
    28.           cache-from: type=gha
    29.           cache-to: type=gha,mode=max
    复制代码

    这个配置有几个值得注意的细节:

    QEMU 必须单独安装。GitHub Actions 的 runner 是 x86 机器,需要 QEMU 来模拟 ARM 架构。
    1. docker/setup-qemu-action
    复制代码
    这个 action 帮你完成了之前手动执行
    1. binfmt
    复制代码
    那一步。

    缓存策略很重要
    1. cache-from
    复制代码
    1. cache-to
    复制代码
    使用了 GitHub Actions 的缓存后端(
    1. type=gha
    复制代码
    ),能显著加速后续构建。没有缓存的情况下,QEMU 模拟构建 ARM 镜像比原生构建慢 3-5 倍,缓存能把这个痛苦降到最低。

    性能优化:QEMU 太慢怎么办?

    QEMU 模拟构建的速度确实感人(反义)。如果你的镜像构建涉及大量编译操作,比如 C/C++ 项目或大型 Node.js 依赖安装,QEMU 模拟可能慢到令人怀疑人生。

    方案一:交叉编译替代模拟执行。

    Go、Rust 等语言天然支持交叉编译。以 Go 为例,设置
    1. GOARCH
    复制代码
    环境变量就能编译出目标架构的二进制文件,完全不需要 QEMU:
    1. FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS builder
    2. ARG TARGETARCH
    3. WORKDIR /app
    4. COPY . .
    5. RUN GOARCH=${TARGETARCH} CGO_ENABLED=0 go build -o server .
    6. FROM alpine:3.19
    7. COPY --from=builder /app/server /usr/local/bin/server
    8. CMD ["server"]
    复制代码

    关键在第一行的
    1. --platform=$BUILDPLATFORM
    复制代码
    ——它强制 builder 阶段使用构建机器的原生架构运行,只在最终产物上做交叉编译。这样
    1. go build
    复制代码
    以原生速度运行,只是输出了不同架构的二进制文件。

    方案二:使用原生 ARM runner。

    GitHub Actions 已经提供了 ARM runner(
    1. ubuntu-latest-arm64
    复制代码
    ),你可以用矩阵策略在不同架构的 runner 上分别原生构建,然后合并 manifest。不过这会增加工作流复杂度,适合构建时间超过 20 分钟的大型项目。

    常见踩坑清单

    1. 基础镜像不支持多架构。 不是所有镜像都提供了 ARM 版本。构建前先用
    1. docker buildx imagetools inspect
    复制代码
    检查基础镜像支持的平台。

    2.
    1. apt-get install
    复制代码
    安装的包架构不对。
    这种情况通常发生在你用
    1. --platform=$BUILDPLATFORM
    复制代码
    做交叉编译时,多阶段构建的运行阶段会自动使用目标架构,不用担心。

    3. 本地无法
    1. docker run
    复制代码
    测试另一个架构的镜像。
    其实可以。只要 QEMU 已注册,直接
    1. docker run --platform linux/arm64 your-image
    复制代码
    就能在 x86 机器上运行 ARM 镜像,速度虽然慢但功能完全正常。

    4. 构建缓存失效。 多平台构建的缓存是按平台分别存储的,切换 platform 列表顺序不会影响缓存命中。但如果你更换了 builder 实例,缓存会全部丢失。

    ━━━━━━━━━━━━━━━━━━━━

    1. exec format error
    复制代码
    到一条命令搞定全平台兼容,Docker 多架构构建的学习曲线其实并不陡。核心就三步:装好 QEMU,创建 Buildx builder,构建时指定
    1. --platform
    复制代码


    剩下的,交给 BuildKit 就好。
  • 您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    果子博客
    扫码关注微信公众号

    Archiver|手机版|小黑屋|风叶林

    GMT+8, 2026-3-25 04:21 , Processed in 0.118755 second(s), 19 queries .

    Powered by 风叶林

    © 2001-2026 Discuz! Team.

    快速回复 返回顶部 返回列表