支援 ARM 與 AMD64,讓 Image 在各種節點都能跑。
過去大家都用 Intel/AMD (x86_64),日子很單純。但現在 Apple Silicon (M 系列)、AWS Graviton、Oracle 的 Ampere A1 這些 ARM 機器越來越普及。如果你在 Mac (ARM) 上打包的 Image 直接丟到 x86 的雲端節點跑,K8S 會無情地賞你一個:
exec format error
程式沒錯、YAML 也沒錯,錯的是 CPU 架構對不上。
這件事其實有點反直覺。Docker 一直被講成「Build once, run anywhere」,久了就會以為它是那種打包好就到處都能跑的萬用膠囊。但它沒那麼神——容器共用的是宿主機的 kernel,它沒有模擬一台電腦給你。
說穿了,Docker 解決的是「環境」的問題:少裝哪個 library、Python 版本對不上、同事機器上有你沒有的環境變數。這些它處理得很好。但它從來沒有處理過「這顆 CPU 看不看得懂這串指令」。
image 裡面裝的是編譯好的執行檔,而 x86_64 跟 arm64 的機器碼根本是兩種語言。你在 M 系列 Mac 上 build 出來的 binary 帶的是 ARM 指令集,丟到 x86 節點上,kernel 讀 ELF header 一看架構對不上,連載入都懶得載入,直接回你 exec format error。它不是跑到一半壞掉,是根本沒開始跑。
拿 Java 來對照就很清楚:.jar 為什麼可以到處丟?因為中間有 JVM 幫你把 bytecode 翻譯成當下那顆 CPU 的指令。Docker 沒有這一層,它就是把原生執行檔連同環境一起打包——所以「這包是給 ARM 跑的」這個屬性,也一起被忠實地打包進去了。
以前幾乎不會遇到這個坑,是因為大家清一色 x86,build 的機器跟跑的機器架構天生一樣,你根本不會意識到有這回事。Apple Silicon 普及之後才開始翻車。而且 Docker Desktop 還會偷偷用 QEMU 幫你模擬,所以在本機拉別人的 x86 image「好像也能跑」——這個貼心反而讓你更晚發現問題,一路順到 CI 或 production 才爆。
Docker 的 buildx 搭配 QEMU 模擬,可以在一台機器上同時產出多個架構的 image。直接來實測,把我們的 API Gateway 同時建給 amd64 跟 arm64:

▲ docker buildx 多架構建置:amd64 與 arm64 兩條產線平行開工 + python:3.10-slim 的八架構 manifest list
注意看 build log:[linux/amd64 builder ...] 跟 [linux/arm64 builder ...] 兩組 stage 平行跑,其中 arm64 那條靠 QEMU 模擬執行。跑完後的產物不是一個 image,而是一個 Manifest List——同一個 tag 底下掛著多個架構的 image,拉取時 Docker 會自動挑符合你 CPU 的那個。
截圖下半部是官方 python:3.10-slim 的 manifest list——人家一個 tag 底下藏了八種架構,從 amd64 到 s390x 都有。以後 docker pull 都不用想架構的事,就是這個機制在默默工作。
CI 上要做多架構,多加兩個零件:setup-qemu-action(裝模擬器)和 platforms 參數:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and Push
uses: docker/build-push-action@v5
with:
context: ${{ matrix.context }}
push: true
platforms: linux/amd64,linux/arm64 # ← 關鍵就這行
tags: ghcr.io/${{ env.OWNER_LC }}/${{ matrix.service }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
這裡要注意的是:QEMU 模擬不是免費的。純 JS 的 api-gateway 跑 arm64 很快(npm 只是解壓套件),但 Java 的 mvn package 在模擬環境下會慢好幾倍。目前本機 Docker Desktop 開發只用得到 amd64,arm64 這條線暫時還沒有實際跑它的節點——Day 9 提過,Always Free 的 Ampere A1(ARM64)本來是雲端部署的備案,但帳號拿不到 OKE、運算容量又連續缺貨,這條路線目前擱置了。platforms 這行先留著:不管日後是升級帳號拿到 OKE(多半是 x86_64),還是真的用上 Ampere A1,兩條產線已經先備好,不用等到真正需要才回頭補。
這裡補充一下,我們的鏡像不是推 Docker Hub 而是 ghcr.io。原因很實際:Docker Hub 有嚴格的 Rate Limit,OCI 的免費 Registry 又有試用期結束被鎖的問題(我們真的被鎖過,站台直接躺平,哭啊)。GHCR 跟 GitHub Actions 無縫整合,權限跟著 repo 走。
一個必踩的坑先幫大家踩:新 Package 首次推上 GHCR 預設是 Private,記得去 Package Settings 把 Visibility 改成 Public,不然叢集拉取時會一直 ImagePullBackOff,你會在叢集裡查半天最後發現是 GitHub 設定問題(別問我怎麼知道的)。
Image 的生產線已經工業化:push 一次,六個服務、(未來)兩種架構、雙 tag 自動出貨。但「部署」這件事還是要人手動嗎?明天迎來本系列的主角之一——GitOps 與 ArgoCD。