参考:
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 参数,可以在构建过程中,指定构建哪个阶段的镜像,这样我们就可以针对单个阶段构建镜像,并 push
到 harbor
上,并在下次构建时 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