Share Notes

chundev

View the Project on GitHub latteouka/share-notes

BuildKit 叢集內 Container Image Build — 告別本地 push,零頻寬部署

日期:2026-06-02 環境:K3s 叢集 + 本地 Registry + BuildKit Job


TL;DR

用 BuildKit 的 buildctl-daemonless.sh 在 K8s Job 裡 build container image,直接 push 到叢集內的 local registry。開發者只需 git push,不需要從本地 push 數百 MB 的 image。實測 3 分 25 秒完成 Next.js 全量 build(含 pnpm install + next build + push)。Kaniko 已被 Google archive(2025 年 6 月),BuildKit 是目前唯一活躍維護且效能最佳的選項。


問題:本地 build + push 吃光頻寬

傳統 CI/CD 在開發者本機跑 docker build + docker push。image 動輒 500MB-1GB,每次 deploy 都要上傳到 registry,在頻寬有限的環境(VPN、辦公室網路)下非常痛苦。

理想狀態:build 和 push 都在叢集內完成,開發者只需 push code。


為什麼選 BuildKit?

在 K8s 內 build image 有幾個選項:

工具 維護狀態 效能 安全性 備註
Docker-in-Docker 活躍 ❌ 需要 --privileged 安全風險高
Kaniko ⚠️ 已 archive (2025/6) 中等 ✅ 無 daemon Google 不再維護
BuildKit ✅ 活躍(Docker 官方) 最快(平行 stage) 需 privileged 或 rootless 推薦
Buildah ✅ 活躍(Red Hat) 中等 ✅ rootless OCI 相容

BuildKit 勝出因為:

  1. Docker 官方維護,跟 docker build 是同一個引擎
  2. 平行 multi-stage build——獨立的 stage 同時執行
  3. 高效 cache——支援 registry-based cache(--export-cache / --import-cache
  4. daemonless 模式——buildctl-daemonless.sh 跑完即銷毀,適合 K8s Job

BuildKit 運作原理

架構

BuildKit 分兩個元件:

在 K8s Job 裡用 buildctl-daemonless.sh——它在同一個 container 裡啟動一個臨時 buildkitd,build 完自動結束。

執行流程

1. 解析 Dockerfile → 建立 DAG(有向無環圖)
2. 平行執行獨立 stage(multi-stage build 的關鍵加速)
3. 每步用 snapshot 記錄 filesystem 差異 → 產生 layer
4. 組裝 OCI image manifest
5. Push layers + manifest 到 registry(OCI Distribution API over HTTPS)

與 Kaniko 的關鍵差異

  Kaniko BuildKit
Stage 執行 Sequential(一步一步) Parallel(獨立 stage 同時跑)
Cache 存儲 Registry only Registry + local + inline
Snapshot 自己實作(較慢) 用 overlayfs/native(較快)
Multi-arch 不支援 支援(QEMU)

實戰:K3s + BuildKit Job

架構圖

開發者                           K3s 叢集
┌──────────┐                    ┌─────────────────────────────┐
│ git push │─── GitHub ────────→│ BuildKit Job                │
│          │                    │  ├─ init: git clone (PAT)   │
│ make     │─── kubectl ───────→│  └─ main: buildctl build    │
│ app-deploy-remote             │       ↓ push                │
│          │                    │  Local Registry (:30500)    │
│          │                    │       ↓ pull                │
│          │                    │  App Deployment (restart)   │
└──────────┘                    └─────────────────────────────┘
     零 image 傳輸 ✅

Job Manifest 重點

initContainers:
  - name: git-clone
    image: alpine/git:2.45.2
    command: ["/bin/sh", "-c"]
    args:
      - |
        TOKEN=$(cat /etc/github/token)
        git clone --depth 1 --branch ${GIT_BRANCH:-main} \
          "https://${TOKEN}@github.com/org/repo.git" /workspace

containers:
  - name: buildkit
    image: moby/buildkit:v0.21.1
    command: [buildctl-daemonless.sh]
    args:
      - build
      - --frontend=dockerfile.v0
      - --local=context=/workspace
      - --local=dockerfile=/workspace/apps/web
      - --opt=platform=linux/amd64
      - --output=type=image,name=registry:5000/app:latest,push=true,registry.insecure=true
      - --export-cache=type=registry,ref=registry:5000/app-cache:latest,registry.insecure=true
      - --import-cache=type=registry,ref=registry:5000/app-cache:latest,registry.insecure=true
    securityContext:
      privileged: true
    resources:
      requests: { memory: "4Gi", cpu: "2" }
      limits: { memory: "8Gi", cpu: "4" }

關鍵設定說明

參數 用途
registry.insecure=true 自架 HTTP registry 必須加,否則 TLS 失敗
--export-cache + --import-cache Cache 存到同一 local registry,後續 build 快很多
privileged: true BuildKit 需要 mount namespace。Rootless 模式需要 kernel user namespace 支援,K3s 不一定有
resources.memory: 4Gi pnpm install + next build 吃記憶體,低於 4Gi 會 OOMKilled
--depth 1 Shallow clone,只拉最新 commit,省時省空間

Makefile 用法

# 推完 code 後一鍵部署(零頻寬)
make app-deploy-remote

# 指定 branch
make app-deploy-remote BRANCH=feat/my-feature

# 只 build 不 deploy
make app-build-remote
make app-build-remote-logs    # 即時日誌
make app-build-remote-status  # 查看狀態

效能實測

階段 耗時 備註
git clone(shallow) ~5s --depth 1
pnpm install(1750 packages) ~90s 首次無 cache
prisma generate ~8s  
next build(Turbopack) ~25s 92 pages
Push image + cache ~15s 到 local registry
總計 ~3m25s 首次 build,後續有 cache 更快

對比本地 docker build + docker push:build 本身差不多,但省掉了 push ~800MB image 到 registry 的時間(視頻寬 2-10 分鐘)。


⚠️ Kaniko 已死,別再用

Google 於 2025 年 6 月 archive 了 GoogleContainerTools/kaniko。最後版本 v1.24.0,gcr.io/kaniko-project/executor 不再收安全更新。

原始作者(Priya Wadhwa、Dan Lorenc)fork 到 chainguard-forks/kaniko(v1.25+),但長期建議直接用 BuildKit。


學到的事


參考資料