概述
本文快速比較 DinD、DooD、Kaniko、BuildKit、Buildah 的差異,並提供在 GitLab Runner on K8s 的最小可用配置與常見踩雷排解。
TL;DR: 在 K8s 上建置映像,如果你追求相容性與安全性,優先選 Kaniko;追求效能與快取,選 BuildKit;DinD/DooD 僅在受信任環境或暫時性需求下使用。
工具比較表
| 工具 | 是否需 daemon | 是否需 privileged | 效能 | 相容性 | 適合場景 |
|---|---|---|---|---|---|
| DinD | ✅ | ✅ | 中 | 高 | 小團隊快速 CI |
| DooD | ✅ (宿主) | ✅ | 高 | 高 | 內部自用 CI |
| Kaniko | ❌ | ❌ | 中 | 高 | 雲原生 CI/CD |
| BuildKit | ✅ (buildkitd) | ✅/rootless | 高 | 高 | 需要快取/效能 |
| Buildah | ❌ | ❌ (可 rootless) | 中 | 高 | OpenShift / Red Hat 系統 |
DinD vs DooD
大多人常常會很困惑,常常會看到兩個詞,下圖可以快速讓你了解差異:

DinD (Docker-in-Docker)
- CI/CD pipeline 中,不需要掛載 docker.sock
- 可以透過 TCP 連線
- 適合隔離環境的 CI/CD
優點:
- 設定簡單、相依關係清楚,與 Docker 指令完全相容
- 容器內部隔離,方便一次性測試
缺點:
- 必須 privileged,風險較高
- 效能中等,nested cgroups 帶來額外開銷
- 在雲端受管 K8s/VM 上常遇到網路與權限限制
DooD (Docker-outside-of-Docker)
- 容器內不運行自己的 Docker
- 直接掛載宿主機的 Docker socket (
/var/run/docker.sock) 給容器使用 - 效能較好,但安全性較低
優點:
- 效能最佳,重用宿主 Docker cache
- 啟動速度快
缺點:
- 等同把宿主 Docker 權限暴露給容器,不適合多租戶
- 與節點相依,K8s 可攜性與彈性較差
為什麼網路上的範例都大多採用 Docker 20.10?
Docker 20.10 是 2020 年底發布的長期穩定版本,一直都有安全更新和修補,很多 Linux 系統都內建這個版本,整個生態圈支援最好,大部分的自動化工具(GitLab Runner、Drone、Jenkins 等)最早都是用這個版本測試的,所以最穩定可靠。
從 Docker 23.x 開始,很多功能都改來改去,一些設定和 API 都變了,導致舊的範例程式碼直接壞掉,在 Kubernetes 或 GitLab Runner 上可能會遇到建置失敗的情況。
上述幾種的建構映像檔的部署指南
1. K8s DinD 部署(GitLab Runner 需設定 privileged=true)
試了蠻多遍的,在 k8s 中 DinD,我並沒有在同一個 Pod 中設定成功,但原理應該是透過 2375 遠端管理 Port 連線Docker去建置,基於我們公司的安全考量,我就沒繼續研究了。
2. K8s DooD 部署
需要掛載 /var/run/docker.sock,共享宿主的 Docker daemon
預先準備
在宿主主機安裝 Docker
GitLab CI 配置
stages:
- build
variables:
IMAGE: $CI_REGISTRY_IMAGE/$CI_BUILD_REF_NAME:$CI_PIPELINE_ID
K8S_NAMESPACE: "kong-api-gateway"
KONG_SECRET_NAME: "kong-api-gateway-secret"
DOCKER_TLS_CERTDIR: ""
DOCKER_DRIVER: overlay2
build:
stage: build
image: docker:20.10
services:
- name: docker:20.10
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- echo "=== 建置 Kong API Gateway Docker 映像檔 (使用 DooD) ==="
- echo "目標映像檔:${IMAGE}"
- echo "建構上下文:${CI_PROJECT_DIR}"
- docker build -f Dockerfile.kong -t "${IMAGE}" "${CI_PROJECT_DIR}"
- docker push "${IMAGE}"
only:
- main
tags:
- K8s-Runner
GitLab Runner(Helm Values)
gitlabUrl: https://gitlab.com
runnerRegistrationToken: "xxx"
unregisterRunners: true
fullnameOverride: "k8s-cd-gitlab-runner"
serviceAccount:
create: true
name: gitlab-runner
runners:
privileged: true
tags: "deploy"
config: |
[[runners]]
[runners.kubernetes]
image = "docker:20.10"
service_account = "gitlab-runner"
service_account_overwrite_allowed = ".*"
[runners.kubernetes.pod_security_context]
run_as_non_root = false
run_as_user = 0
[runners.kubernetes.container_security_context]
privileged = true
[runners.kubernetes.resources]
limits = { "cpu" = "1000m", "memory" = "2Gi" }
requests = { "cpu" = "500m", "memory" = "1Gi" }
[runners.kubernetes.environment]
DOCKER_OPTS = "--insecure-registry 192.168.50.57:30000"
[[runners.kubernetes.volumes.host_path]]
name = "docker-socket"
mount_path = "/var/run/docker.sock"
host_path = "/var/run/docker.sock"
mount_propagation = "HostToContainer"
securityContext:
allowPrivilegeEscalation: true
readOnlyRootFilesystem: false
runAsNonRoot: false
privileged: true
capabilities:
add: ["SYS_ADMIN"]
podSecurityContext:
runAsUser: 0
fsGroup: 0
RBAC 配置
# gitlab-runner-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: gitlab-runner
namespace: k8s-cd-gitlab-runner
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: k8s-cd-gitlab-runner
name: gitlab-runner-role
rules:
- apiGroups: [""]
resources: ["pods", "pods/attach", "pods/exec", "pods/log", "pods/portforward", "pods/proxy", "pods/status"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["secrets", "configmaps", "persistentvolumeclaims", "services", "endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["events"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["batch"]
resources: ["jobs", "cronjobs"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["extensions"]
resources: ["deployments", "statefulsets", "daemonsets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: gitlab-runner-rolebinding
namespace: k8s-cd-gitlab-runner
subjects:
- kind: ServiceAccount
name: gitlab-runner
namespace: k8s-cd-gitlab-runner
roleRef:
kind: Role
name: gitlab-runner-role
apiGroup: rbac.authorization.k8s.io
部署命令
# 部署
helm repo add gitlab https://charts.gitlab.io
helm repo update
kubectl create namespace k8s-cd-gitlab-runner
kubectl apply -f ./rbac.yaml
helm install gitlab-runner -f values.yaml gitlab/gitlab-runner --namespace k8s-cd-gitlab-runner --create-namespace
# 更新
helm upgrade gitlab-runner -f values.yaml gitlab/gitlab-runner --namespace k8s-cd-gitlab-runner
注意: 這個方法在地端自架的Ubuntu K8s 可行, 但在RKE2 可能因為一些安全性設定無法挷定 /var/run/docker.sock
3. Kaniko 部署
安全性最佳,建構過程不需 Docker daemon 或 privileged,在受管 K8s 上相容性極佳,支援常見 Dockerfile 指令,易於遷移除錯,但建構時會比 DinD 或 DooD 慢 20% ~ 30%。
Helm Values 配置
gitlabUrl: https://gitlab.com/
runnerRegistrationToken: "" # 在 GitLab -> Settings -> CI/CD -> Runners 裡看到的 token
unregisterRunners: true
fullnameOverride: "k8s-cd-gitlab-runner"
serviceAccount:
create: false
name: gitlab-runner
runners:
privileged: false
tags: "K8s-Runner"
config: |
[[runners]]
[runners.kubernetes]
image = "gcr.io/kaniko-project/executor:debug"
service_account = "gitlab-runner"
service_account_overwrite_allowed = ".*"
privileged = false
部署命令
helm install gitlab-runner -f values.yaml gitlab/gitlab-runner --namespace k8s-cd-gitlab-runner --create-namespace
GitLab CI 配置
stages:
- build
variables:
IMAGE: $CI_REGISTRY_IMAGE/$CI_BUILD_REF_NAME:$CI_PIPELINE_ID
K8S_NAMESPACE: "kong-api-gateway"
KONG_SECRET_NAME: "kong-api-gateway-secret"
build:
stage: build
image: gcr.io/kaniko-project/executor:debug
variables:
DOCKER_CONFIG: /kaniko/.docker
before_script:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
script:
- echo "=== 建置 Kong API Gateway Docker 映像檔 (使用 Kaniko) ==="
- echo "目標映像檔:${IMAGE}"
- echo "建構上下文:${CI_PROJECT_DIR}"
- /kaniko/executor --context "${CI_PROJECT_DIR}" --dockerfile "Dockerfile.kong" --destination "${IMAGE}" --cache=true --cleanup
only:
- main
tags:
- K8s-Runner
create-secret:
stage: create-secret
image:
name: bitnami/kubectl:latest
script:
- echo "=== 準備目標命名空間與鏡像拉取密鑰 ==="
- kubectl get namespace ${K8S_NAMESPACE} || kubectl create namespace ${K8S_NAMESPACE}
- kubectl delete secret ${KONG_SECRET_NAME} -n ${K8S_NAMESPACE} --ignore-not-found=true || true
- kubectl create secret docker-registry ${KONG_SECRET_NAME} \
--docker-server=$CI_REGISTRY \
--docker-username=$CI_REGISTRY_USER \
--docker-password=$CI_REGISTRY_PASSWORD \
--docker-email=none \
-n ${K8S_NAMESPACE}
only:
- main
tags:
- K8s-Runner
4. BuildKit 部署
Docker 公司推出的新指令集及映像檔,但也都基於 DinD 或是 Dood 來運作。
BuildKit 有兩種常見模式:
模式 1: Docker (DinD / Dood) + BuildKit
只要設定環境變數:
variables:
DOCKER_BUILDKIT: "1"
BUILDKIT_PROGRESS: plain
模式 2: GitLab Runner + BuildKit Pod
- 在 K8s 裡部署一個 buildkitd DaemonSet 或 Deployment
- 每個 runner job 透過 buildctl CLI,呼叫 cluster 內的 buildkitd 服務去 build
- 不需要 mount
/var/run/docker.sock,安全性比 DinD 高

部署建議
選擇建議
- 小團隊快速 CI: 使用 DinD
- 內部自用 CI: 使用 DooD
- 雲原生 CI/CD: 使用 Kaniko
- 需要快取/效能: 使用 BuildKit
- OpenShift/Red Hat 系統: 使用 Buildah
安全性考量
- DinD 和 DooD 需要 privileged 模式,安全性較低
- Kaniko 和 BuildKit 不需要 privileged 模式,安全性較高
- 建議在生產環境使用 Kaniko 或 BuildKit
進階:moby/BuildKit 設定更複雜,暫時我就沒有研究了,需要額外部署 buildkitd Pod 都是基於 DinD / DooD 來運作。
結論
配置這環境蠻麻煩的,很多時候環境一點點的差距就會有不一樣的行為,多會幾種建置方法,才可以應對不同的環境,不過隨著時間及技術的迭代,這類問題應該會越來越少。
其實在比較好的還是CI和CD 拆開來,這樣才會比較安全,設定也比較簡單,也不會遇到一堆在K8s上CI的相容性錯誤。





















留言