背景

  • 在国产化和信创的潮流下,应用程序容器镜像支持多 CPU 架构的需求已经很普遍。常见的做法是为不同平台单独构建一个版本,或者分别构建不同架构的镜像再利用 docker manifest 的能力来做镜像合并 。当用来开发的平台与部署的目标平台不同时,实现这一目标并不容易,例如在 x86 架构上开发一个应用程序并将其部署到 ARM 平台的机器上,通常需要准备 ARM 平台的基础设施用于编译打包。在这个场景下,一次构建多处部署的必要性就比较高了,在没有一个成熟的 CI 流水线的情况下,利用 docker buildx 构建跨平台的镜像也是一种比较高效的解决方案。

前置知识

镜像 Registry 对多系统架构镜像的支持

目前大部分镜像托管平台(如 dockerhub、各个公有云的容器镜像服务以及开源镜像仓库工具 harbor)都支持多系统架构镜像,以下图 dockerhub 平台的 golang 镜像为例,单个镜像 tag golang:latest 就包含了 10 种不同系统架构的镜像。 go_image

Docker 对多系统架构镜像的管理

  • Docker 使用 manifest 组来管理多 CPU 架构的镜像,manifest 是一个 JSON 文件,指容器镜像的元数据文件,一个 manifest 对应一个镜像 tag 下一个CPU 架构的镜像。
  • 一个 manifest 组的文件并不直接表示镜像信息,而是使用了一个列表指向了该清单中包含的多份子清单文件,每一份子清单文件均表示一种架构的镜像清单。
  • 查看一个镜像的manifest 列表
docker manifest inspect golang:latest

结果: go image manifest

多系统架构镜像的使用

我们通过 docker pull 或者 docker run 进行镜像的拉取或使用时,docker 会自动拉取匹配当前系统架构的镜像。

# 镜像拉取
docker pull golang:latest
# 镜像 manifest 查看
docker inspect golang:latest
# 只看 CPU 架构
docker inspect golang:latest | grep Architecture
  • ARM 平台 go image arm
  • linux X86 平台 go image x86

Docker Buildx

简介

  • buildx 是一个 Docker CLI 插件,用于使用 BuildKit 扩展构建功能。使用 docker buildx CLI 插件可以轻松构建多系统架构 Docker 镜像。

版本要求

  • Docker Engine 19.03 及以上版本,可以安装和使用 buildx 插件,低版本 Docker Engine 可能会出现兼容性的问题。

安装

Windows 或者 MacOS

  • 参考 Docker Buildx 默认包含在适用于 Windows 和 macOS 的 Docker Desktop 中 。

Linux

  • 参考 Docker Engine 安装文档Docker Engine 软件包存储库包含 Docker Buildx 软件包,安装 docker-buildx-plugin 包以安装 Buildx 插件。
# centos
sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin 
# ubuntu
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin 
  • 启用 binfmt_misc

binfmt_misc 是 Linux 内核的一个模块,全称是混杂二进制格式的内核支持(Kernel Support for miscellaneous Binary Formats) ,它允许用户在 Linux 系统上注册一些特定格式的二进制文件,并指定相应的解释器进行解释和执行。 这些注册的文件类型可以是任何格式,例如 Windows 可执行文件、Java 类文件或 Python 脚本等,而解释器可以是任何可执行程序,例如 shell、Python 解释器或者 Java 虚拟机等。通过 binfmt_misc,用户可以在 Linux 系统上方便地运行各种不同的二进制文件,从而提高了系统的灵活性和可用性。

Docker Desktop 版本不需要执行此项,默认就是启用的。

docker run --privileged --rm docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64

验证

docker buildx version
# 输出示例:github.com/docker/buildx v0.11.2-desktop.1 986ab6afe790e25f022969a18bc0111cff170bc2

多架构镜像构建

构建器 builder

docker buildx 通过 builder 实例对象来管理构建配置和节点,命令行将构建任务发送至 builder 实例,再由 builder 指派给符合条件的节点执行。

我们可以基于同一个 docker 服务程序创建多个 builder 实例,提供给不同的项目使用以隔离各个项目的配置。

  • 查看构建器列表
docker buildx ls
# 在结果列表中可以看到目前使用的构建器及其支持的架构,如果目前使用的构建器支持我们需要的架构,则不需要创建新的构建器
# 示例
mybuilder *  docker-container
  mybuilder0 unix:///var/run/docker.sock running linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
default      docker
  default    default                     running linux/amd64, linux/386
  • 创建新的构建器
docker buildx create --use --name mybuilder
  • 检查和启动
docker buildx inspect mybuilder --bootstrap

镜像构建

  • Dockerfile 示例
FROM nginx:latest

CMD ["nginx", "-g", "daemon off;"]
  • 进入 Dockerfile 所在目录,执行如下示例的命令,就可以构建自己指定的架构版本镜像并推送到当前用户的dockerhub 镜像仓库。
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t yanhuan6252/nginx:v1 --push .
  • 构建结果示例

docker buildx res

原理

  • buildx 通过 QEMUbinfmt_misc 分别为 不同的 CPU 架构构建不同的镜像。构建完成后,就会创建一个 manifest list ,其中包含了指向这几个镜像的指针,当在不同的架构环境使用时会根据其环境拉取相应架构的镜像。

缺点

  • 构建速度比较慢,效率比较低。
  • 只能推送到镜像仓库后在查看和使用,没办法构建到本地。
  • 没办法构建复杂镜像,比如说 MySQL 这种,安装时要 copy 不同架构的 rpm 包到镜像中,用这种方法就实现不了,还是得使用两个 Dockerfile 分别构建镜像,然后再合并manifest list 的方法来实现。
  • 更适合个人/小团队开发测试使用,企业级使用的话最好还是使用内部流水线或者 github action 这种自动化程度更高的工具来完成。

参考资料