使用 BuildKit 构建 Docker 镜像

适用版本:Free、Premium、Ultimate;适用服务:GitLab.com、GitLab Self-Managed、GitLab Dedicated。

BuildKit是 Docker 使用的构建引擎,支持多平台构建与构建缓存。

构建方式

方式 安全要求 命令 适用情况
BuildKit rootless 不需要特权容器 buildctl-daemonless.sh 追求安全性或替代 Kaniko
Docker Buildx 需要 docker:dind docker buildx 熟悉的 Docker 工作流
原生 BuildKit 需要 docker:dind buildctl 更细致地控制 BuildKit

先决条件

  • 使用 Docker 执行器的 GitLab Runner。
  • 使用 Docker Buildx 时,Docker 版本为 19.03 或更新。
  • 项目包含 Dockerfile。

BuildKit rootless

独立运行的 BuildKit 可以在不依赖 Docker 守护进程的情况下以 rootless 模式构建镜像,构建任务不需要特权容器,可直接替代 Kaniko。

Runner 仍必须允许 BuildKit 创建用户命名空间和挂载点所需的系统调用。GitLab.com 托管 Runner 因为以特权模式运行,无须额外配置。自管理 Docker 执行器在非特权模式下可能出现权限错误,详见后文。无法改变 Runner 安全配置时,可使用 rootless Buildah。

此方式使用 moby/buildkit:rootless 镜像;通过 BUILDKITD_FLAGS: --oci-worker-no-process-sandbox 支持 rootless;由 buildctl-daemonless.sh 自动管理守护进程;不依赖 Docker 守护进程或特权构建容器,但须手动配置注册表认证。

容器注册表认证

GitLab CI/CD 通过预定义变量为 GitLab 容器注册表提供认证信息。rootless BuildKit 仍须手动创建 Docker 配置文件。

GitLab 容器注册表

变量 CI_REGISTRY、CI_REGISTRY_USER、CI_REGISTRY_PASSWORD 分别表示注册表 URL、用户名和密码。在任务中加入:

before_script:
  - mkdir -p ~/.docker
  - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json

多个注册表

在 before_script 中合并多个认证条目:

before_script:
  - mkdir -p ~/.docker
  - |
    echo "{
      \"auths\": {
        \"${CI_REGISTRY}\": {
          \"auth\": \"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"
        },
        \"docker.io\": {
          \"auth\": \"$(printf "%s:%s" "${DOCKER_HUB_USER}" "${DOCKER_HUB_PASSWORD}" | base64 | tr -d '\n')\"
        }
      }
    }" > ~/.docker/config.json

依赖代理

通过 GitLab 依赖代理拉取镜像时,在 before_script 中配置认证:

before_script:
  - mkdir -p ~/.docker
  - |
    echo "{
      \"auths\": {
        \"${CI_REGISTRY}\": {
          \"auth\": \"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"
        },
        \"$(echo -n $CI_DEPENDENCY_PROXY_SERVER | awk -F[:] '{print $1}')\": {
          \"auth\": \"$(printf "%s:%s" ${CI_DEPENDENCY_PROXY_USER} "${CI_DEPENDENCY_PROXY_PASSWORD}" | base64 | tr -d '\n')\"
        }
      }
    }" > ~/.docker/config.json

更多信息见 CI/CD 中的认证。

rootless 镜像构建

不依赖 Docker 守护进程的任务示例:

build-rootless:
  image:
    name: moby/buildkit:rootless
    entrypoint: [""]
  stage: build
  variables:
    BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
  before_script:
    - mkdir -p ~/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
  script:
    - |
      buildctl-daemonless.sh build \
        --frontend dockerfile.v0 \
        --local context=. \
        --local dockerfile=. \
        --output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true

必须覆盖 entrypoint: [""]。默认情况下,rootless 镜像启动长期运行的 BuildKit 守护进程;不覆盖入口时,任务会运行守护进程而非构建命令,一直挂起直到超时。

rootless 多平台构建

通过目标平台选项指定架构:

build-multiarch-rootless:
  image:
    name: moby/buildkit:rootless
    entrypoint: [""]
  stage: build
  variables:
    BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
  before_script:
    - mkdir -p ~/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
  script:
    - |
      buildctl-daemonless.sh build \
        --frontend dockerfile.v0 \
        --local context=. \
        --local dockerfile=. \
        --opt platform=linux/amd64,linux/arm64 \
        --output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true

rootless 缓存

配置注册表缓存的导入和导出,以加快后续构建:

build-cached-rootless:
  image:
    name: moby/buildkit:rootless
    entrypoint: [""]
  stage: build
  variables:
    BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
    CACHE_IMAGE: $CI_REGISTRY_IMAGE:cache
  before_script:
    - mkdir -p ~/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
  script:
    - |
      buildctl-daemonless.sh build \
        --frontend dockerfile.v0 \
        --local context=. \
        --local dockerfile=. \
        --export-cache type=registry,ref=$CACHE_IMAGE \
        --import-cache type=registry,ref=$CACHE_IMAGE \
        --output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true

rootless 注册表镜像

注册表镜像可加快拉取速度,并缓解速率限制或网络限制。用 buildkit.toml 指定镜像端点:

build-mirror-rootless:
  image:
    name: moby/buildkit:rootless
    entrypoint: [""]
  stage: build
  variables:
    BUILDKITD_FLAGS: --oci-worker-no-process-sandbox --config /tmp/buildkit.toml
  before_script:
    - mkdir -p ~/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
    - cat <<'EOF' > /tmp/buildkit.toml
      [registry."docker.io"]
        mirrors = ["mirror.example.com"]
      EOF
  script:
    - |
      buildctl-daemonless.sh build \
        --frontend dockerfile.v0 \
        --local context=. \
        --local dockerfile=. \
        --output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true

将 mirror.example.com 替换成自己的注册表镜像 URL。

代理设置

Runner 位于 HTTP(S) 代理后方时,在任务变量中配置代理:

build-behind-proxy:
  image:
    name: moby/buildkit:rootless
    entrypoint: [""]
  stage: build
  variables:
    BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
    http_proxy: <your-proxy>
    https_proxy: <your-proxy>
    no_proxy: <your-no-proxy>
  before_script:
    - mkdir -p ~/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
  script:
    - |
      buildctl-daemonless.sh build \
        --frontend dockerfile.v0 \
        --local context=. \
        --local dockerfile=. \
        --build-arg http_proxy=$http_proxy \
        --build-arg https_proxy=$https_proxy \
        --build-arg no_proxy=$no_proxy \
        --output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true

用实际代理配置替换 <your-proxy> 和 <your-no-proxy>。

自定义证书

向使用自定义 CA 的注册表推送时,在守护进程启动前通过 BuildKit 配置文件设置信任证书:

build-with-custom-certs:
  image:
    name: moby/buildkit:rootless
    entrypoint: [""]
  stage: build
  variables:
    BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
  before_script:
    - mkdir -p "$HOME/.docker"
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > "$HOME/.docker/config.json"
    - REG_HOST="${CI_REGISTRY%%/*}"
    - mkdir -p "$HOME/.config/buildkit/certs/$REG_HOST"
    - echo "$CA_CERT" > "$HOME/.config/buildkit/certs/$REG_HOST/ca.pem"
    - |
      cat > "$HOME/.config/buildkit/buildkitd.toml" << EOT
      [registry."$REG_HOST"]
        ca = ["$HOME/.config/buildkit/certs/$REG_HOST/ca.pem"]
      EOT
    - export SSL_CERT_FILE="$HOME/.config/buildkit/certs/$REG_HOST/ca.pem"
  script:
    - |
      buildctl-daemonless.sh build \
        --frontend dockerfile.v0 \
        --local context=. \
        --local dockerfile=. \
        --output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true

其中,REG_HOST="${CI_REGISTRY%%/*}" 提取注册表主机名;buildkitd.toml 为目标注册表配置可信 CA,BuildKit 自动从 $HOME/.config/buildkit/ 发现此文件;SSL_CERT_FILE 用于覆盖 BuildKit 守护进程完全初始化前发生的 TLS 连接。

新增 CA_CERT CI/CD 变量,提供包含根证书和所有中间证书的完整链。PEM 包含换行,因此变量值无法设为 masked。要隐藏值,可改用文件类型变量,并把 before_script 中的 echo "$CA_CERT" 替换为 cat "$CA_CERT"。如果目标注册表与 GitLab 实例使用相同 CA,且 Runner 已配置 tls-ca-file,可改用预定义变量 CI_SERVER_TLS_CA_FILE。

从 Kaniko 迁移

原文将 rootless BuildKit 描述为无需特权容器、提供更好性能、缓存和安全功能的 Kaniko 替代方案。

更新配置

原 Kaniko 配置:

build:
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  script:
    - /kaniko/executor
      --context $CI_PROJECT_DIR
      --dockerfile $CI_PROJECT_DIR/Dockerfile
      --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

改为 rootless BuildKit:

build:
  image:
    name: moby/buildkit:rootless
    entrypoint: [""]
  variables:
    BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
  before_script:
    - mkdir -p ~/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
  script:
    - |
      buildctl-daemonless.sh build \
        --frontend dockerfile.v0 \
        --local context=. \
        --local dockerfile=. \
        --output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true

自定义 CA 迁移

Kaniko 任务若使用自定义 CA,迁移时必须显式配置。与 Kaniko 不同,moby/buildkit:rootless 镜像不包含系统证书存储,须在守护进程启动前创建 BuildKit 配置。

  1. 在名为 CA_CERT 的 CI/CD 变量中保存完整证书链,包含根证书及中间证书。
  2. 更新任务,使其使用 buildkitd.toml 和 SSL_CERT_FILE。完整示例见上文自定义证书部分。

其他 BuildKit 方式

不需要 rootless 时,可以采用依赖 docker:dind 服务的方式,获得熟悉的工作流或高级控制功能。

Docker Buildx

Buildx 在熟悉的 Docker 命令基础上增加 BuildKit 功能,需要 docker:dind 服务。

基本镜像

配置服务并创建 builder:

variables:
  DOCKER_TLS_CERTDIR: "/certs"
build-image:
  image: docker:cli
  services:
    - docker:dind
  stage: build
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker buildx create --use --driver docker-container --name builder
    - docker buildx inspect --bootstrap
  script:
    - docker buildx build --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --push .
  after_script:
    - docker buildx rm builder

多平台镜像

单次命令可构建多种架构,生成的 manifest 支持多个平台,Docker 会为部署目标自动选择合适镜像。使用 --platform 指定架构:

variables:
  DOCKER_TLS_CERTDIR: "/certs"
build-multiplatform:
  image: docker:cli
  services:
    - docker:dind
  stage: build
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker buildx create --use --driver docker-container --name multibuilder
    - docker buildx inspect --bootstrap
  script:
    - docker buildx build
        --platform linux/amd64,linux/arm64
        --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
        --push .
  after_script:
    - docker buildx rm multibuilder

构建缓存

注册表缓存保存构建层供后续构建复用。mode=max 导出所有层,以获得最大复用机会:

variables:
  DOCKER_TLS_CERTDIR: "/certs"
  CACHE_IMAGE: $CI_REGISTRY_IMAGE:cache
build-with-cache:
  image: docker:cli
  services:
    - docker:dind
  stage: build
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker buildx create --use --driver docker-container --name cached-builder
    - docker buildx inspect --bootstrap
  script:
    - docker buildx build
        --cache-from type=registry,ref=$CACHE_IMAGE
        --cache-to type=registry,ref=$CACHE_IMAGE,mode=max
        --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
        --push .
  after_script:
    - docker buildx rm cached-builder

原生 BuildKit

直接使用 buildctl 获得更多控制。此方式需要 docker:dind,任务使用 BuildKit 镜像:

variables:
  DOCKER_TLS_CERTDIR: "/certs"
build-with-buildkit:
  image: moby/buildkit:latest
  services:
    - docker:dind
  stage: build
  before_script:
    - mkdir -p ~/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
  script:
    - |
      buildctl build \
        --frontend dockerfile.v0 \
        --local context=. \
        --local dockerfile=. \
        --output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true

故障排除

注册表认证错误

确认 CI_REGISTRY_USER 和 CI_REGISTRY_PASSWORD 可用;确认有目标注册表推送权限;使用外部注册表时,检查项目 CI/CD 变量中凭据配置是否正确。

rootless 权限错误

确认已设置 BUILDKITD_FLAGS: --oci-worker-no-process-sandbox,Runner 有足够资源,且 Dockerfile 没有尝试特权操作。Kubernetes Runner 的 AppArmor 挂载权限也可能阻止 rootless 容器,参阅 Kubernetes 执行器挂载错误。

如果出现以下错误,则 Runner 安全策略阻止了所需系统调用。

fork/exec /proc/self/exe: operation not permitted

Docker 执行器以非特权模式运行时可能出现:

could not connect to unix:///run/user/1000/buildkit/buildkitd.sock after 10 trials
[rootlesskit:parent] error: failed to start the child: fork/exec /proc/self/exe: operation not permitted

原因是 Runner 的 seccomp 配置阻止了 rootless BuildKit 所需调用。GitLab.com 托管 Runner 以特权模式运行,不受此问题影响。

自管理 Runner 应配置 Docker 执行器的 security_opt,仅允许 BuildKit 必需的调用。不要设置为 seccomp:unconfined:虽然可消除错误,却会禁用默认 seccomp 保护、降低隔离性。应使用只放行必要调用的自定义配置,或改用 rootless Buildah。

invalid local: stat path/to/image/Dockerfile: not a directory

此错误表示为 --local dockerfile= 传入了文件路径,BuildKit 需要包含 Dockerfile 的目录。使用 --local dockerfile=path/to/image,而非 --local dockerfile=path/to/image/Dockerfile。

多平台构建失败

检查基础镜像是否支持所有目标架构,检查各平台是否都有所需架构依赖,并考虑在 Dockerfile 中用条件语句处理架构专属逻辑。


来源:GitLab Inc. 与文档贡献者,官方原文。仓库许可明确将 doc/ 下正文按 CC BY-SA 4.0授权。本稿为中文翻译,保留全部 16 个代码块;翻译也按 CC BY-SA 4.0 提供,不代表 GitLab 背书,许可免责声明适用。此次只做静态文档核对,未运行构建或部署命令。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容