
本文快速比較 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 來運作。
配置這環境蠻麻煩的,很多時候環境一點點的差距就會有不一樣的行為,多會幾種建置方法,才可以應對不同的環境,不過隨著時間及技術的迭代,這類問題應該會越來越少。