当前位置: 首页 > 工具软件 > DIND > 使用案例 >

Gitlab CI DinD 中使用缓存加速 Docker 镜像(多阶段)构建过程

扶珂
2023-12-01

CSDN 中文章不一定能及时更新,欢迎关注我的博客查看最新版本:许盛的博客

参考:
https://andrewlock.net/caching-docker-layers-on-serverless-build-hosts-with-multi-stage-builds—target,-and—cache-from/
https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#make-docker-in-docker-builds-faster-with-docker-layer-caching

背景

gitlab ci 中使用 docker 容器作为 runner时,如果在命令中又需要调用 docker ,这就相当于在 Docker 容器中使用 Docker 了,就是 Docker In Docker,简称 dind

在功能上来说, dind 配置好之后,除了安全性和速度的缺点外,使用起来没有任何问题。

在官方文档上有这样一句描述:

Cache: Each job runs in a new environment. Concurrent jobs work fine, because every build gets its own instance of Docker engine and they don’t conflict with each other. However, jobs can be slower because there’s no caching of layers.

大概意思就是每个 job 都在新的环境中运行,都有自己的 docker 引擎实例,所以并没有层缓存。

这就导致我们在使用 docker build 命令的时候,每次都需要重新拉取 Dockerfile 中依赖的基础镜像,也无法利用上一次构建任务触发后遗留的镜像层缓存,因为宿主机上压根就没有缓存文件。

解决

参考:
Make Docker-in-Docker builds faster with Docker layer caching

最初的想法是,能不能在构建过程中,将镜像缓存层给存到宿主机上,在查阅文档的过程中,发现 docker build 提供了一个 —cache-from 参数,可以指定某个镜像,作为构建过程中的缓存源。

这样的话,思路就有了,因为每次构建完成后,虽然中间的缓存层以及打包出来的镜像,在宿主机上都没有了,但是最终的镜像上传到了公司内的 Harbor 上。

这样每次开始 docker build 之前,将上一次构建的镜像 pull 到本地,然后使用 --cache-from 参数指定为缓存源即可。

例如官方文档提供的 yaml 配置:

image: docker:19.03.12

services:
  - docker:19.03.12-dind

variables:
  # Use TLS https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#tls-enabled
  DOCKER_HOST: tcp://docker:2376
  DOCKER_TLS_CERTDIR: "/certs"

before_script:
  - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY

build:
  stage: build
  script:
    - docker pull $CI_REGISTRY_IMAGE:latest || true
    - docker build --cache-from $CI_REGISTRY_IMAGE:latest --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE:latest .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - docker push $CI_REGISTRY_IMAGE:latest

每一次构建开始前,都会将最近的镜像拉取下来,并指定为镜像源。

针对多阶段构建

以上这种方式,针对多阶段构建的情况,其实稍稍有点问题,因为在多阶段构建过程中,最终生成的镜像,只包含了最后一个阶段的镜像层,而之前阶段的镜像层,都丢弃掉了。

这样就算指定了最终的镜像作为缓存源,也无法在前置的构建阶段起到作用。

这时候 docker build—target 参数就起作用了。

使用 —target 参数,可以在构建过程中,指定构建哪个阶段的镜像,这样我们就可以针对单个阶段构建镜像,并 pushharbor 上,并在下次构建时 pull 下来作为缓存源使用。

示例如下:

build: # 构建镜像
  stage: build
  image: docker:stable
  script:
    # 将 builder 镜像拉下来,用作多阶段构建中第一阶段的缓存使用
    - docker pull $HARBOR:builder || true
    # 进行第一阶段构建,产出 builder 镜像,用作下一次的缓存
    - docker build --target builder --cache-from $HARBOR:builder -t $HARBOR:builder -f _ci/Dockerfile .
    # 将 latest 镜像拉下来,用作第二阶段的构建的缓存,根据 Dockerfile 实际情况判断是否需要这一步优化
    - docker pull $HARBOR:latest || true
    # 开始实际的构建
    - docker build --build-arg VERSION_TAG=$CI_COMMIT_TAG --build-arg COMMIT_ID=$CI_COMMIT_SHORT_SHA --cache-from $HARBOR:builder --cache-from $HARBOR:latest -f _ci/Dockerfile -t $HARBOR:$CI_COMMIT_SHORT_SHA .
    - docker push $HARBOR:$CI_COMMIT_SHORT_SHA
    # 上传这一次生成的 builder 镜像
    - docker push $HARBOR:builder
 类似资料: