chundev
日期:2026-06-02 環境:K3s 叢集 + 本地 Registry + BuildKit Job
用 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 是目前唯一活躍維護且效能最佳的選項。
傳統 CI/CD 在開發者本機跑 docker build + docker push。image 動輒 500MB-1GB,每次 deploy 都要上傳到 registry,在頻寬有限的環境(VPN、辦公室網路)下非常痛苦。
理想狀態:build 和 push 都在叢集內完成,開發者只需 push code。
在 K8s 內 build image 有幾個選項:
| 工具 | 維護狀態 | 效能 | 安全性 | 備註 |
|---|---|---|---|---|
| Docker-in-Docker | 活躍 | 快 | ❌ 需要 --privileged |
安全風險高 |
| Kaniko | ⚠️ 已 archive (2025/6) | 中等 | ✅ 無 daemon | Google 不再維護 |
| BuildKit | ✅ 活躍(Docker 官方) | 最快(平行 stage) | 需 privileged 或 rootless | 推薦 |
| Buildah | ✅ 活躍(Red Hat) | 中等 | ✅ rootless | OCI 相容 |
BuildKit 勝出因為:
docker build 是同一個引擎--export-cache / --import-cache)buildctl-daemonless.sh 跑完即銷毀,適合 K8s JobBuildKit 分兩個元件:
在 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 | BuildKit | |
|---|---|---|
| Stage 執行 | Sequential(一步一步) | Parallel(獨立 stage 同時跑) |
| Cache 存儲 | Registry only | Registry + local + inline |
| Snapshot | 自己實作(較慢) | 用 overlayfs/native(較快) |
| Multi-arch | 不支援 | 支援(QEMU) |
開發者 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 傳輸 ✅
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,省時省空間 |
# 推完 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 分鐘)。
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。
buildctl-daemonless.sh)是 K8s Job 的完美搭配——啟動臨時 daemon、build 完自動結束,不留殘留程序user_namespaces 支援,K3s 不一定有。Privileged 模式在 build-only Job 裡是可接受的安全 trade-off(Job 跑完就消失)--export-cache + --import-cache 讓 cache 跟著 registry 走,不綁定特定節點。Local registry = 零網路延遲的 cache hitregistry.insecure=true 不是可選的——自架 HTTP registry 不加這個,BuildKit 會嘗試 TLS 然後 connection refused