Mark Ku's Blog
首頁 關於我
幾個常見在 K8s 上的映像檔建置方法:Docker(DinD DooD)/Kaniko/BuildKit
DevOps
幾個常見在 K8s 上的映像檔建置方法:Docker(DinD DooD)/Kaniko/BuildKit
Mark Ku
Mark Ku
September 01, 2025
1 min

概述

本文快速比較 DinDDooDKanikoBuildKitBuildah 的差異,並提供在 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

大多人常常會很困惑,常常會看到兩個詞,下圖可以快速讓你了解差異:

372ac1ee 0f92 480b a43c 188bfabdd491

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 高

buildKit pod

部署建議

選擇建議

  1. 小團隊快速 CI: 使用 DinD
  2. 內部自用 CI: 使用 DooD
  3. 雲原生 CI/CD: 使用 Kaniko
  4. 需要快取/效能: 使用 BuildKit
  5. OpenShift/Red Hat 系統: 使用 Buildah

安全性考量

  • DinD 和 DooD 需要 privileged 模式,安全性較低
  • Kaniko 和 BuildKit 不需要 privileged 模式,安全性較高
  • 建議在生產環境使用 Kaniko 或 BuildKit

進階:moby/BuildKit 設定更複雜,暫時我就沒有研究了,需要額外部署 buildkitd Pod 都是基於 DinD / DooD 來運作。

結論

配置這環境蠻麻煩的,很多時候環境一點點的差距就會有不一樣的行為,多會幾種建置方法,才可以應對不同的環境,不過隨著時間及技術的迭代,這類問題應該會越來越少。

參考資料

  • GitLab Runner 無法呼叫 host 的 docker

Tags

Mark Ku

Mark Ku

Software Developer

10年以上豐富網站開發經驗,開發過各種網站,電子商務、平台網站、直播系統、POS系統、SEO 優化、金流串接、AI 串接,Infra 出身,帶過幾次團隊,也加入過大團隊一起開發。

Expertise

前端(React)
後端(C#)
網路管理
DevOps
溝通
領導

Social Media

facebook github website

Related Posts

Docker 及 K8s 如何拉取映像檔
Docker 及 K8s 如何拉取映像檔
September 02, 2025
1 min

Quick Links

關於我

Social Media