Buzeli
buzeliSoluções Digitais
DevSecOps

ARM64构建:40分钟→8-12分钟——在GitHub Actions中消除QEMU

发布于 2026年4月14日

Build ARM64: 40 min → 8-12 min eliminando QEMU no GitHub Actions para OKE Kubernetes

问题:x86运行器上的静默QEMU

一个GitHub Actions CI/CD流水线正在为OKE(Oracle Kubernetes Engine)集群构建Docker镜像,集群使用ARM64节点(OCI A1.Flex Ampere实例)。配置的运行器是x86机器上的自托管运行器。Dockerfile是标准的,buildx配置正确——但构建需要约40分钟。

原因不在于Dockerfile或应用大小(生成350个页面的Next.js)。而在于工作流中的一行:

复制
- uses: docker/setup-qemu-action@v3
- run: docker buildx build --platform linux/arm64 .

当x86运行器遇到--platform linux/arm64时,Docker使用QEMU在x86硬件上模拟ARM64架构。模拟带来的性能惩罚是编译时间的5-10倍——每条ARM64指令都由模拟器实时翻译。

setup-qemu-action不会警告构建会变慢。它只是启用模拟,buildx继续执行。结果是功能上正确的构建,但速度比在原生硬件上慢了几个数量级。

背景:从AWS(x86)迁移到OCI ARM64

该项目已从AWS ECS(x86实例)迁移到使用A1.Flex节点(ARM64 Ampere)的OKE以降低成本。在ECS上运行的生产镜像是amd64的——第一个ARM64构建是在Apple Silicon Mac(原生ARM)上手动用Docker buildx完成的。为了自动化CI/CD,现有的自托管x86运行器被复用,通过QEMU以避免搭建新基础设施。

数周内,生产部署仅在构建步骤就需要约40分钟。流水线的其余部分(推送到OCIR、kubectl rollout)只需几秒钟。

解决方案:GitHub Actions中的ubuntu-24.04-arm

GitHub自2024年起提供托管的ARM64运行器。ubuntu-24.04-arm运行器在真实ARM硬件上运行——没有模拟,没有QEMU,没有指令翻译惩罚。

工作流被分为两个独立的作业:

复制
# 修改前——单个x86作业,对生产和测试环境都使用QEMU
jobs:
  Build-and-Deploy:
    runs-on: self-hosted          # x86
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-qemu-action@v3   # ARM64模拟
      - uses: docker/setup-buildx-action@v3
      - run: |
          docker buildx build \
            --platform linux/arm64 \        # 通过QEMU强制ARM64
            --push \
            -t $IMAGE_TAG .
复制
# 修改后——按分支/环境分离的作业
jobs:
  Build-and-Deploy-Stage:
    runs-on: self-hosted           # x86——测试环境使用amd64,没问题
    if: github.ref == 'refs/heads/staging'
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - run: docker buildx build --push -t $IMAGE_TAG .

  Build-and-Deploy-Prod:
    runs-on: ubuntu-24.04-arm      # 原生ARM——无QEMU
    if: github.ref == 'refs/heads/master'
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - run: |
          docker buildx build \
            --push \             # 无--platform:运行器已是ARM64
            -t $IMAGE_TAG .

关键点:在生产作业中,--platform linux/arm64被移除了。当运行器已经是ARM64时,指定平台是多余的——在某些版本中保留它甚至会让buildx产生困惑。构建变为原生执行,无需任何其他更改。

从生产作业中移除的内容

docker/setup-qemu-action@v3 — 不需要:运行器已是ARM64

--platform linux/arm64 — 不需要:运行器的原生架构

Set deploy Key — 未使用:通过kubectl部署,非SSH

Load Centralized Configuration — 变量迁移到GitHub仓库的secrets/vars

附带修复:kubectl rollout status的RBAC

构建完成后,流水线运行kubectl rollout status等待部署完成后再将作业标记为成功。该命令因权限错误而静默失败——CI的ServiceAccount在deployments上只有get、patch和update权限。

复制
# 运行kubectl rollout status时报错
Error from server (Forbidden): deployments.apps "nextjs" is forbidden:
User "system:serviceaccount:myapp:cicd-deployer" cannot list resource
"deployments" in API group "apps" in the namespace "myapp"

rollout status需要list和watch权限来观察滚动更新的进度。Role已更新:

复制
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: cicd-deployer-role
  namespace: myapp
rules:
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch", "patch", "update"]
- apiGroups: ["apps"]
  resources: ["replicasets"]
  verbs: ["get", "list"]

第二个瓶颈:2.1 GB的镜像

解决构建问题后,下一个瓶颈变得明显:滚动更新期间的停机时间为4-5分钟——不是因为Kubernetes慢,而是因为镜像拉取时间。OKE上的每个新节点需要从OCIR(Oracle容器镜像仓库)下载2.1 GB,每个Pod耗时1分34秒。在2个副本和顺序滚动更新的情况下,部分不可用窗口是稳定的。

减小镜像大小是下一步:多阶段构建以排除开发用node_modules、Next.js output: 'standalone'(大幅减少进入最终镜像的内容)和层优化。约500 MB的镜像将把拉取时间从1分34秒减少到约20秒——几乎消除所有滚动更新的停机时间。

结果

切换到ubuntu-24.04-arm后的第一次部署确认了效果:构建成功,镜像发布到OCIR,Pod在OKE中以新标签启动。

复制
# 部署后验证
kubectl get pods -n myapp
# nextjs-7f4cbbdffd-g595h   1/1   Running   0   6m
# nextjs-7f4cbbdffd-gt5jt   1/1   Running   0   5m

kubectl get deployment nextjs -n myapp \
  -o jsonpath='{.spec.template.spec.containers[0].image}'
# registry.example.com/myapp/www:abc1234f
QEMU是开发机器上一次性构建的有效工具。对于每次提交都要经过流水线的生产CI/CD来说,5-10倍的惩罚是不可接受的。GitHub Actions中存在原生ARM运行器,其成本与同等x86运行器相同——没有理由在长时间运行的流水线中保留QEMU。

迁移流水线的检查清单

1. 确认构建是否对运行器架构不同的平台使用--platform

2. 检查setup-qemu-action是否存在(模拟的明显标志)

3. 将runs-on切换为ubuntu-24.04-arm(或ARM自托管运行器)

4. 从生产作业中移除setup-qemu-action和--platform

5. 检查ServiceAccount的RBAC:rollout status需要list + watch权限

6. 测量镜像拉取时间——超过30秒就是优化候选