Graviton 迁移实操:从依赖扫描到灰度切流
ARM 架构能省 20% 左右,但迁移不是改个实例类型就完事。这篇写清楚每一步该做什么、容易踩什么坑。
先说结论
Java、Go、Python、Node.js 这几类服务迁 Graviton,大部分情况下改造成本很低,收益是同性能下单价降 15%-25%。值得做,但要按流程做,不要直接改实例类型上生产。
真正会卡住的场景是:依赖了 x86 原生扩展、商业闭源组件、或者用了特定 CPU 指令集加速的库。这些必须提前扫出来。
五个步骤
-
01
依赖扫描
列出所有运行时依赖,逐个确认有没有 ARM64 版本。Python 看 wheel、Java 看是否有 JNI、Node 看 native module。
交付物:依赖清单、不兼容项列表
1-3 天
-
02
多架构镜像构建
docker buildx 构建 amd64 + arm64 双架构镜像,推到 ECR 生成 manifest list。同一个 tag 在不同架构节点上自动拉对应镜像。
交付物:双架构镜像、CI 流水线改造
2-4 天
-
03
功能验证
在 ARM 节点上跑完整的集成测试。重点看时间处理、字符编码、浮点计算这些容易出现细微差异的地方。
交付物:测试报告、差异清单
3-5 天
-
04
性能回归
同并发下对比 p99 延迟与吞吐,不要只看均值。Graviton 的单核性能与 x86 有差异,有些负载会变快,有些会变慢。
交付物:性能对比数据、规格调整建议
3-5 天
-
05
灰度切流
ALB 权重从 5% 开始,观察一天,再到 25%、50%、100%。保留 x86 节点组作为快速回滚路径。
交付物:切流记录、各阶段指标
1-2 周
依赖扫描脚本
#!/usr/bin/env bash
# 检查项目依赖的 ARM64 兼容性
set -euo pipefail
echo "=== 检查容器镜像的架构支持 ==="
# 列出 compose 或 k8s manifest 里引用的所有镜像
IMAGES=$(grep -rhoP '(?<=image:\s)[^\s"]+' . --include='*.yaml' --include='*.yml' | sort -u)
for img in $IMAGES; do
archs=$(docker manifest inspect "$img" 2>/dev/null \
| jq -r '.manifests[]?.platform | "\(.os)/\(.architecture)"' \
| sort -u | tr '\n' ' ')
if [[ "$archs" == *"linux/arm64"* ]]; then
printf ' [OK] %-52s %s\n' "$img" "$archs"
else
printf ' [WARN] %-52s %s\n' "$img" "${archs:-单架构或无法查询}"
fi
done
echo
echo "=== 检查 Python 依赖的 ARM wheel ==="
if [[ -f requirements.txt ]]; then
while read -r line; do
[[ -z "$line" || "$line" == \#* ]] && continue
pkg="${line%%[<>=!~]*}"
pkg="$(echo "$pkg" | xargs)"
[[ -z "$pkg" ]] && continue
files=$(curl -s "https://pypi.org/pypi/${pkg}/json" \
| jq -r '.urls[]?.filename' 2>/dev/null || true)
if echo "$files" | grep -qE 'aarch64|arm64|none-any'; then
printf ' [OK] %s\n' "$pkg"
else
printf ' [CHECK] %s 需人工确认是否有源码包可编译\n' "$pkg"
fi
done < requirements.txt
fi
echo
echo "=== 检查二进制文件架构 ==="
find . -type f \( -name '*.so' -o -name '*.dylib' -o -name '*.a' \) 2>/dev/null | while read -r f; do
arch=$(file -b "$f" | grep -oE 'x86-64|aarch64|ARM' || echo 'unknown')
printf ' %-14s %s\n' "$arch" "$f"
done
echo
echo "扫描完成。标记为 CHECK 或 WARN 的项需要人工确认。"
多架构镜像构建
name: Build Multi-Arch Image
on:
push:
branches: [main]
tags: ['v*']
permissions:
contents: read
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3 # 交叉编译支持
- uses: docker/setup-buildx-action@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.ECR_PUSH_ROLE }}
aws-region: ap-northeast-1
- uses: aws-actions/amazon-ecr-login@v2
id: ecr
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64 # 关键:双架构
push: true
tags: |
${{ steps.ecr.outputs.registry }}/order-service:${{ github.sha }}
${{ steps.ecr.outputs.registry }}/order-service:latest
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: false # ECR 对 provenance 支持有限
- name: Verify manifest
run: |
docker manifest inspect \
${{ steps.ecr.outputs.registry }}/order-service:${{ github.sha }} \
| jq '.manifests[].platform'
QEMU 交叉编译比原生构建慢不少。如果构建时间成为瓶颈,改用 ARM 原生 runner 分别构建再合并 manifest。
我们实测过的迁移结果
| 服务类型 | 语言/运行时 | 改造工作量 | 性能变化 | 成本变化 |
|---|---|---|---|---|
| API 网关服务 | Go 1.22 | 仅改构建配置 | p99 −6%(更快) | −22% |
| 订单服务 | Java 17 / Spring Boot | 改基础镜像 | p99 +3%(略慢) | −19% |
| 数据处理 | Python 3.12 | 两个包换版本 | 持平 | −21% |
| Node BFF | Node 20 | 重装 native module | p99 −4% | −23% |
| 图像处理 | Python + OpenCV | 需重编译,耗时较多 | p99 +11%(变慢) | −20%,但性能损失需评估 |
| 商业中间件 | 闭源 x86 二进制 | 无法迁移 | - | - |
图像处理这类依赖 SIMD 指令优化的场景要谨慎,可能出现降价但性能损失更多的情况,综合算下来不划算。
混合架构集群的坑
K8s 集群里同时有 x86 和 ARM 节点时,务必给只支持单架构的 Pod 加 nodeSelector 或亲和性规则,否则调度到错误架构的节点上会启动失败。我们的习惯是给节点打 kubernetes.io/arch 标签并在 Deployment 里显式声明。
checklist
- 依赖清单全量扫过,不兼容项有明确处理方案
- CI 已能产出双架构镜像并验证 manifest
- 集成测试在 ARM 节点上全部通过
- 性能对比数据齐全,包含 p50/p95/p99
- 回滚路径清晰(保留 x86 节点组或旧启动模板)
- 监控能按架构维度区分指标,便于对比
- 灰度计划写明各阶段的观察指标与回滚触发条件