斯科特·罗西洛
Inception 团队软件工程师
AWS 最近宣布了其新一代 Amazon EC2 M6g 实例的预览版,该实例搭载基于 64 位 ARM 的 AWS Graviton2 处理器。 与最新一代 AWS x86-64 实例相比,其预计的性能和价格优势令人印象深刻,不容忽视。
虽然我们可以简单地使用 ARM 上的标准 Docker 来为这些新的 AWS Graviton 处理器构建映像,但支持这两种架构而不是放弃 x86-64 架构有很多好处:
- 开发人员需要能够在本地运行其 CI/CD 生成的 Docker 镜像。 在可预见的未来,开发人员的机器将继续使用 x86–64 CPU。
- 在 x86–64 和 Graviton2 集群之间共享通用容器。
- 在 ARM 上运行暂存环境并在 x86–64 上运行生产环境,直到 Graviton2 结束预览。
- 一旦 Graviton2s 普遍可用,如果服务迁移到 ARM 导致任何问题,请快速切换回 x86–64。
构建多架构 Docker 镜像仍是一个实验性功能。 然而,托管多架构镜像已经得到了 Docker Registry 的良好支持,包括自托管和 hub.docker.com 上的镜像。 您的里程可能会因第三方 Docker 注册表实现而异
在本文中,我们将演示如何在 ARM Linux 主机上为 x86–64(AMD64)和 ARM64 构建和发布多架构 Docker 镜像,以便您可以在任一架构上从镜像运行 Docker 容器。
注意:如果您可以在 macOS 或 Windows 桌面上构建映像,则 Docker Desktop 开箱即用,支持构建多架构 Docker 映像。 但是,如果您运行 Linux,或者想要正确构建 Docker 镜像作为 CI/CD 管道的一部分,请继续阅读。
安装 Docker 19.03 或更高版本
首先,我们需要一个能够运行 Docker 19.03 或更高版本的 ARM64 Linux 主机。 您也可以使用 x86–64 主机。
但是,由于我们希望从 ARM 的成本节省中获益,因此我们将使用 Ubuntu 19.10 作为我们的构建服务器。 Ubuntu 是一个流行的 Linux 发行版,受到多种云服务的支持,但是其他较新的发行版也应该可以正常运行。 但是,您需要确保您运行的是 Linux 内核 5.x 或更高版本。 在 AWS 上,您可以使用 Ubuntu 19.10 AMI。
在 Ubuntu 上,安装docker.io
作为 Ubuntu 的存储库。 我们还安装了binfmt-support
和qemnu-user-static
。 QEMU 使单个主机能够为多种架构构建映像,而binfmt-support
为 Linux 内核添加了多种二进制格式支持。 请注意binfmt-support
版本 2.1.43 或更高版本是必需的。
将您的用户添加到 Docker 组以便从您的用户帐户运行命令。 请记住在运行后重新启动或注销并重新登录:
1. #!/bin/bash #安装Docker 和多架构依赖项
2.
3. sudo apt-get install binfmt-support qemu-user-static
4. sudo apt-get 安装 docker.io
5. sudo usermod -aG docker $USERp
6. sudo 重启
安装 Docker Buildx
接下来,我们需要安装Docker的buildx
命令。 Buildx 处于技术预览阶段,并提供多架构构建等实验性构建功能。 如果您允许以您的用户身份运行 docker,您可以以普通用户身份(而不是 root 用户)安装它。
为 Docker 安装buildx
命令行插件。 以下代码将安装 ARM 64 位的最新版本。
/bin/bash 复制代码
2.#安装 buildx for arm64 并启用 Docker CLI 插件
3.
4. sudo apt-get 安装 jq
5. mkdir -p ~/.docker/cli-plugins
6. BUILDX_URL=$(curl https://api.github.com/repos/docker/buildx
/发布/最新|jq -r.assets[].browser_download_url | grep arm64
7. wget $BUILDX_URL -O ~/.docker/cli-plugins/docker-build
8. chmod + x ~/.docker/cli-plugins/docker-buildx
构建多架构图像
构建多架构镜像(Docker 的文档将其称为多平台镜像)需要由
docker-container
驱动程序支持的构建器,并支持两种构建跨平台镜像的策略:
- 在内核中使用 QEMU 仿真支持
- 由单个构建器协调在多个本机节点上构建
这里我们使用 QEMU 方法,因为它是两种选项中更便宜的一种,因为它只需要一个适用于所有目标架构的构建主机。 此外,Docker 在这里没有使用 QEMU 来创建功能齐全的虚拟机。 我们使用 QEMU 用户模式,因此只需要模拟系统调用。
随着您的 CI/CD 需求不断发展,您可能希望投资本机节点构建场以加快构建过程。
让我们创建一个引导构建器,你可以给它起任何你喜欢的名字:
1. $ docker buildx create --name mbuilder
2. mbuilder
3.
4. $ docker buildx 使用 mbuilder
5.
6. $ docker buildx inspect --bootstrap
7.名称:mbuilder
8. 驱动:docker-container
9.
10. 节点:
11. 名称:mbuilder0
12.端点:unix:///var/run/docker.sock
13. 状态:正在运行
14. 平台:linux/arm64、linux/amd64、linux/riscv64、linux/ppc64le、
Linux/s390x、Linux/386、Linux/arm/v7、Linux/arm/v6
完美,我们现在有一个能够针对 linux/arm64 linux/amd64 和其他架构的构建器!
现在让我们从一个简单的 Dockerfile 构建一个可以在 Linux amd64 和 arm64 上运行的映像。
请注意,您提取的图像也必须支持您计划针对的架构。 可以使用以下方法检查:
$ docker buildx imagetools 检查 alpine
Dockerfile:
来自阿尔卑斯山
运行 apk 添加 util-linux
CMD [“lscpu”]
$ docker buildx build --platform linux/amd64,linux/arm64 -t foo4u/demo-mutliarch:2 --push 。
[+] 建造 4.7s (9/9) 已完工
=> [内部] 从 Dockerfile 加载构建定义
=> => 正在传输 dockerfile:31B
=> [内部] 加载 .dockerignore
=> => 传输上下文:2B
=> [linux/amd64 internal] 为 docker.io/library/alpine:latest 加载元数据
=> [linux/arm64 internal] 为 docker.io/library/alpine:latest 加载元数据
=> [linux/amd64 1/2] 来自 docker.io/library/alpine@sha256:2171658620155679240babee0a7714f6509fae66898db422ad803b951257db78
=> => 解析docker.io/library/alpine@sha256:2171658620155679240babee0a7714f6509fae66898db422ad803b951257db78
=> 缓存 [linux/amd64 2/2] 运行 apk 添加 util-linux
=> [linux/arm64 1/2] 来自 docker.io/library/alpine@sha256:2171658620155679240babee0a7714f6509fae66898db422ad803b951257db78
=> => 解析docker.io/library/alpine@sha256:2171658620155679240babee0a7714f6509fae66898db422ad803b951257db78
=> 缓存 [linux/arm64 2/2] 运行 apk 添加 util-linux
=> 导出至图像
=> => 导出图层
=> => 导出清单 sha256:cb54200a7c04dded134ca9e3e6a0e434c2fdf851fb3a7226941d0983ad5bfb88
=> => 导出配置 sha256:307b885367f8ef4dc443dc35d6ed3298b9a3a48a846cf559a676c028a359731b
=> => 导出清单 sha256:6f4fe17def66ef5bc79279448e1cb77a1642d460ed58d5dc60d0e472c023e2eb
=> => 导出配置 sha256:26e6b092c7c1efffe51ce1d5f68e3359ab44152d33df39e5b85cd4ff6cfed3d4
=> => 导出清单列表 sha256:3b4e4135b92017e5214421543b813e83a77fcea759af8067c685b70a5d978497
=> => 推动层
=> => 推送 docker.io/foo4u/demo-mutliarch:2 的清单
这里有很多事情要做,让我们来分析一下:
1. Docker 将构建上下文传输到我们的构建器容器
2.构建器使用 --platform 参数为我们请求的每种架构构建一个映像
3. 镜像被推送到 Docker Hub
4. Buildx 生成清单 JSON 文件并将其作为镜像标签推送到 Docker Hub。
让我们使用imagetools
来检查生成的 Docker 镜像:
1. $ docker buildx imagetools 检查 foo4u/demo-mutliarch:2
2.名称:docker.io/foo4u/demo-mutliarch:2
3.媒体类型:application/vnd.docker.distribution.manifest.list.v2+json
4.摘要:sha256:3b4e4135b92017e5214421543b813e83a77fcea759af8067c685b70a5d978497
5.
6.清单:
7.名称:docker.io/foo4u/demo-mutliarch:2@sha256:cb54200a7c04dded134ca9e3e6a0e434c2fdf851fb3a7226941d0983ad5bfb88
8. 媒体类型:application/vnd.docker.distribution.manifest.v2+json
9.平台:linux/amd64
10.
11. 名称:docker.io/foo4u/demo-12。 多体系结构:2@sha256:6f4fe17def66ef5bc79279448e1cb77a1642d460ed58d5dc60d0e472c023e2eb
12. 媒体类型:application/vnd.docker.distribution.manifest.v2+json
13. 平台:linux/arm64
在这里我们可以看到foo4u/demo-multiarch:2
是一个 JSON 清单,指向我们在构建期间针对的每个平台的清单。 尽管该图像在注册表中作为单个图像出现,但它实际上是包含指向平台特定图像的链接的清单。 Buildx 为每个架构构建并发布了一个图像,然后生成了一个将它们链接在一起的清单。
Docker 在拉取镜像时使用此信息来下载适合机器运行时架构的镜像。
让我们在 x86–64 / amd64 上运行该映像:
$ docker run --rm foo4u/demo-mutliarch:2
无法在本地找到图像“foo4u/demo-mutliarch:2”
2:从 foo4u/demo-mutliarch 拉取
e6b0cf9c0882:已存在
状态:已为 foo4u/demo-mutliarch:2 下载较新的图像
架构:x86_64
现在让我们在 arm64 上运行该图像:
$ docker run --rm foo4u/demo-mutliarch:2
无法在本地找到图像“foo4u/demo-mutliarch:2”
2:从 foo4u/demo-mutliarch 拉取
状态:已为 foo4u/demo-mutliarch:2 下载较新的图像
架构:aarch64
就是这样! 现在我们有一个功能齐全的 Docker 映像,可以在我们现有的 x86-64 服务器或全新的 ARM 64 服务器上运行!
总之,在 Linux 上开始使用多架构 Docker 镜像并不是那么难。 我们甚至可以使用 ARM 服务器来构建图像,从而有可能节省我们的 CI/CD 服务器以及暂存和生产基础设施的成本。
额外好处:如果您使用的语言具有良好的多架构支持(例如 Java 或 Go),您可以进一步优化您的 Docker 构建。 例如,你可以构建一个具有单一平台编译的 Spring Boot 应用程序:
1. FROM --platform=$BUILDPLATFORM amazoncorretto:11 作为构建者
2.
3. 复制。 /srv/
4. WORKDIR /srv
5. 运行 ./mvnw -DskipTests = true 包 spring-boot:重新打包
6.
7. 来自 amazoncorretto:11
8.
9. 复制 --from=builder /srv/target/my-service-0.0.1-SNAPSHOT.jar /srv/
10.
11. 曝光 8080
12.
13. 入口点 ["java", "-jar", "/srv/my-service-0.0.1-SNAPSHOT.jar"]