
本文快速比較 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 系統 |
大多人常常會很困惑,常常會看到兩個詞,下圖可以快速讓你了解差異:
優點:
缺點:
/var/run/docker.sock) 給容器使用優點:
缺點:
Docker 20.10 是 2020 年底發布的長期穩定版本,一直都有安全更新和修補,很多 Linux 系統都內建這個版本,整個生態圈支援最好,大部分的自動化工具(GitLab Runner、Drone、Jenkins 等)最早都是用這個版本測試的,所以最穩定可靠。
從 Docker 23.x 開始,很多功能都改來改去,一些設定和 API 都變了,導致舊的範例程式碼直接壞掉,在 Kubernetes 或 GitLab Runner 上可能會遇到建置失敗的情況。
試了蠻多遍的,在 k8s 中 DinD,我並沒有在同一個 Pod 中設定成功,但原理應該是透過 2375 遠端管理 Port 連線Docker去建置,基於我們公司的安全考量,我就沒繼續研究了。
需要掛載 /var/run/docker.sock,共享宿主的 Docker daemon
在宿主主機安裝 Docker
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
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
# 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
安全性最佳,建構過程不需 Docker daemon 或 privileged,在受管 K8s 上相容性極佳,支援常見 Dockerfile 指令,易於遷移除錯,但建構時會比 DinD 或 DooD 慢 20% ~ 30%。
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
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
Docker 公司推出的新指令集及映像檔,但也都基於 DinD 或是 Dood 來運作。
BuildKit 有兩種常見模式:
只要設定環境變數:
variables: DOCKER_BUILDKIT: "1" BUILDKIT_PROGRESS: plain
/var/run/docker.sock,安全性比 DinD 高
進階:moby/BuildKit 設定更複雜,暫時我就沒有研究了,需要額外部署 buildkitd Pod 都是基於 DinD / DooD 來運作。
配置這環境蠻麻煩的,很多時候環境一點點的差距就會有不一樣的行為,多會幾種建置方法,才可以應對不同的環境,不過隨著時間及技術的迭代,這類問題應該會越來越少。
其實在比較好的還是CI和CD 拆開來,這樣才會比較安全,設定也比較簡單,也不會遇到一堆在K8s上CI的相容性錯誤。