多架构镜像

Docker 容器和底层架构的关系:

  • Docker 不完全跨架构 - Docker 引擎本身可以在不同架构上运行,但容器中的应用程序二进制文件是特定于架构的
  • 容器共享主机的内核,但容器中的应用二进制文件需要与 CPU 架构兼容

  • 容器镜像通常包含为特定 CPU 指令集编译的二进制文件

  • ARM CPU (如 Graviton) 使用不同的指令集,无法直接执行为 x86/Intel 编译的代码

例如,在 ARM EC2 上运行构建于 x86 的 Node.js 容器镜像时,Node.js 二进制文件将无法执行, 错误信息通常为 Exec format error,表示二进制文件与当前 CPU 架构不兼容

解决方案:

  • 使用多架构镜像构建流程 (通过 buildx)

  • 利用 ECR 的 Manifest Lists 功能,确保合适的镜像变体被拉取

  • 如果是解释型语言 (如 Python/Java),问题较少严重,但其依赖的原生扩展仍需适配

所以,ECR 的多架构镜像功能解决了这一核心问题,允许从同一个镜像标签自动拉取适合当前架构的正确变体。

兼容性测试

我们先在intel的机器上,运行一个docker中的arm程序:

#!/bin/bash

# 拉取特定的ARM64架构镜像
docker pull --platform=linux/arm64 node:16-alpine
docker inspect node:16-alpine | grep Architecture

# 尝试运行ARM容器 - 这将失败并显示错误信息
set +e  # 不因错误退出
docker run --platform=linux/arm64 node:16-alpine node -e 'console.log("Hello World")'
exit_code=$?
echo $exit_code

image-20250424084824642

执行完成后,通常错误消息为: ‘exec format error’ 或 ‘standard_init_linux.go: exec user process caused: exec format error

原因上面也提到,容器不是完全虚拟化的环境,容器内的二进制文件仍需与主机CPU架构匹配

multi-architecture image

传统解决方式(没有 Manifest Lists):

分别为 x86 架构构建镜像:myapp:v1-amd64, 为 ARM 架构构建镜像:myapp:v1-arm64

在 Kubernetes 部署文件中使用条件判断:

   containers:
   - name: myapp
     image: ${NODE_ARCH == "arm64" ? "123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp:v1-arm64" : "123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp:v1-amd64"}

使用 Manifest Lists 的方式:

为不同架构构建各自的镜像

#!/bin/bash
# Create ECR repo and upload multi-architecture Docker images
set -e

# Setup variables
REPO_NAME="myapp"
AWS_REGION='us-west-2'
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
ECR_REPO_URI="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${REPO_NAME}"

# Create ECR repository if it doesn't exist
aws ecr describe-repositories --repository-names "${REPO_NAME}" >/dev/null 2>&1 || aws ecr create-repository --repository-name "${REPO_NAME}" 

aws ecr get-login-password --region "${AWS_REGION}" | docker login --username AWS --password-stdin "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"

# Pull, tag and push AMD64 image
docker pull --platform=linux/amd64 node:16-alpine && docker tag node:16-alpine "${ECR_REPO_URI}:v1-amd64" && docker push "${ECR_REPO_URI}:v1-amd64"

# Pull, tag and push ARM64 image
docker pull --platform=linux/arm64 node:16-alpine && docker tag node:16-alpine "${ECR_REPO_URI}:v1-arm64" && docker push "${ECR_REPO_URI}:v1-arm64"

image-20250424090130929

创建并推送 Manifest List:

   # 创建 Manifest List
   docker manifest create 123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp:v1 \
     123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp:v1-amd64 \
     123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp:v1-arm64
   
   # 为每个架构添加注解
   docker manifest annotate 123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp:v1 \
     123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp:v1-amd64 --os linux --arch amd64
   
   docker manifest annotate 123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp:v1 \
     123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp:v1-arm64 --os linux --arch arm64
   
   # 推送 Manifest List
   docker manifest push 123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp:v1

实际执行命令:

docker manifest create "${ECR_REPO_URI}:latest" "${ECR_REPO_URI}:v1-amd64" "${ECR_REPO_URI}:v1-arm64"
docker manifest annotate "${ECR_REPO_URI}:latest" "${ECR_REPO_URI}:v1-amd64" --os linux --arch amd64
docker manifest annotate "${ECR_REPO_URI}:latest" "${ECR_REPO_URI}:v1-arm64" --os linux --arch arm64
docker manifest push "${ECR_REPO_URI}:latest"

执行完成后,在ECR中看到新上传的manifest,它的类型是Image index

image-20250424090435272

实际效果:

  • 当部署到 x86 节点时,自动拉取 amd64 版本;当部署到 ARM 节点时,自动拉取 arm64 版本

  • 无需修改部署文件就能同时使用 AWS Graviton2 (ARM) 和 x86 实例

  • CI/CD 流程统一,只需推送和管理单一的 myapp:v1 标签

测试:

# 拉取多架构镜像 (会自动选择适合当前系统架构的镜像)
echo "拉取latest标签的镜像(多架构)..."
docker pull "${ECR_REPO_URI}:latest"

# 运行容器测试
echo "运行容器测试..."
docker run --rm -it "${ECR_REPO_URI}:latest" node -v  # 可以执行成功

# 可选:分别测试特定架构的镜像
echo "测试AMD64镜像..."
docker run --rm --platform=linux/amd64 "${ECR_REPO_URI}:v1-amd64" node -v  # 可以执行成功

echo "测试ARM64镜像..."
docker run --rm --platform=linux/arm64 "${ECR_REPO_URI}:v1-arm64" node -v  # 报错invalid reference format

这大大简化了在混合架构环境中运行容器的复杂性,特别是当想要利用 AWS Graviton 实例时。它的工作原理如下图:

img