Cache dependencies in GitLab CI/CD
- Cache vs artifacts
- Good caching practices
- Common use cases
- Availability of the cache
- Clearing the cache
Cache dependencies in GitLab CI/CD
GitLab CI / CD 提供了一种缓存机制,可用于在作业运行时节省时间.
缓存是指通过重用先前作业的相同内容来加快执行作业的时间. 当您开发依赖于在构建期间通过 Internet 获取的其他库的软件时,此功能特别有用.
如果启用了缓存,则默认情况下从 GitLab 9.0 开始,它在项目级别的管道和作业之间共享. 缓存不跨项目共享.
确保您阅读了cache
参考以了解.gitlab-ci.yml
定义.
Cache vs artifacts
注意:如果您使用缓存和工件在作业中存储相同的路径时要小心,因为在覆盖工件和内容之前 ,将还原缓存 .
不要使用缓存在阶段之间传递工件,因为缓存旨在存储编译项目所需的运行时依赖项:
cache
: 用于存储项目依赖项缓存用于通过存储下载的依赖项来加快后续作业在给定管道中的运行速度,这样就不必再次从 Internet 上获取它们(例如 npm 软件包,Go 供应商软件包等).配置为在阶段之间传递中间构建结果,则应该使用工件来完成.
artifacts
: 用于在阶段之间传递的阶段结果.工件是由作业生成的文件,可以存储并上载,然后可以在同一管道的后续阶段中由作业获取和使用. 换句话说, 您不能在阶段 1 的 job-A 中创建工件,然后在阶段 1 的 job-B 中使用此工件 . 此数据在不同的管道中将不可用,但可以从 UI 下载.
artifacts
的名称听起来像是仅在工作之外有用,例如用于下载最终图像,但是人工制品也可以在管道的后期阶段使用. 因此,如果通过下载所有必需的模块来构建应用程序,则可能需要将它们声明为工件,以便后续阶段可以使用它们. 有一些优化措施,例如声明到期时间,这样您就不会将工件保留太长时间,或者使用依赖项来控制哪些作业会获取工件.
Caches:
- 如果未全局定义或未按作业定义(使用
cache:
:),则禁用该功能. - 如果全局启用,则可用于
.gitlab-ci.yml
所有作业. - 可以由创建缓存的同一作业在后续管道中使用(如果未全局定义).
- 如果启用了分布式缓存 ,则将它们存储在 Runner 安装的位置并上传到 S3.
- 如果按作业定义,则使用:
- 通过后续管道中的相同作业.
- 如果它们具有相同的依赖关系,则由同一管道中的后续作业组成.
Artifacts:
- 如果未按作业定义(使用
artifacts:
:),则将其禁用. - 只能针对每个作业启用,不能全局启用.
- 在管道中创建,并且可以被当前活动管道的后续作业使用.
- 始终上传到 GitLab(称为协调器).
- 可以具有用于控制磁盘使用量的到期值(默认为 30 天).
注意:工件和缓存均定义了相对于项目目录的路径,并且无法链接到其外部的文件.
Good caching practices
We have the cache from the perspective of the developers (who consume a cache within the job) and the cache from the perspective of the Runner. Depending on which type of Runner you are using, cache can act differently.
从开发人员的角度来看,要确保高速缓存的最大可用性,在作业中声明cache
,请使用以下一项或多项:
提示:在管道中使用相同的 Runner 是在一个阶段或管道中缓存文件,并以有保证的方式将该缓存传递到后续阶段或管道的最简单,最有效的方法.
从 Runner 的角度来看,为了使缓存有效运行,必须满足以下条件之一:
- 为您的所有工作使用一个赛跑者.
- 使用多个使用分布式缓存的 Runner(无论是否处于自动缩放模式),这些缓存将缓存存储在 S3 存储桶中(例如 GitLab.com 上的共享 Runner).
- 使用具有相同体系结构的多个运行程序(不在自动伸缩模式下)共享公共网络安装目录(使用 NFS 或类似方式),以存储缓存.
提示:了解缓存的可用性,以了解有关内部的更多信息,并更好地了解缓存的工作方式.
Sharing caches across the same branch
使用key: ${CI_COMMIT_REF_SLUG}
定义一个缓存,以便每个分支的作业始终使用相同的缓存:
cache:
key: ${CI_COMMIT_REF_SLUG}
尽管这似乎可以防止意外覆盖缓存,但是这意味着合并请求的第一个流水线很慢,这可能会给开发人员带来糟糕的体验. 下次将新提交推送到分支时,将重新使用缓存.
To enable per-job and per-branch caching:
cache:
key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
要启用每个分支和每个阶段的缓存:
cache:
key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
Sharing caches across different branches
如果要缓存的文件需要在所有分支和所有作业之间共享,则可以对所有这些文件使用相同的密钥:
cache:
key: one-key-to-rule-them-all
要在分支之间共享相同的缓存,但按作业将它们分开:
cache:
key: ${CI_JOB_NAME}
Disabling cache on specific jobs
如果已全局定义了缓存,则意味着每个作业将使用相同的定义. 您可以按工作覆盖此行为,如果要完全禁用它,请使用空哈希:
job:
cache: {}
Inherit global config, but override specific settings per job
您可以使用定位符覆盖缓存设置,而无需覆盖全局缓存. 例如,如果要覆盖一项作业的policy
:
cache: &global_cache
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- public/
- vendor/
policy: pull-push
job:
cache:
# inherit all global cache settings
<<: *global_cache
# override the policy
policy: pull
要进行更精细的调整,还请阅读有关cache: policy
.
Common use cases
缓存最常见的用例是在后续作业之间保留内容,以用于诸如依赖项和常用库(Node.js 包,PHP 包,rubygems,Python 库等)之类的东西,因此不必从公共互联网重新获取.
注意:有关更多示例,请查看我们的GitLab CI / CD 模板 .
Caching Node.js dependencies
假设您的项目正在使用npm安装 Node.js 依赖项,下面的示例将全局定义cache
,以便所有作业都继承它. 默认情况下,npm 将缓存数据存储在主文件夹~/.npm
但是由于您无法缓存项目目录之外的内容 ,因此我们告诉 npm 使用./.npm
,它按分支缓存:
#
# https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml
#
image: node:latest
# Cache modules in between jobs
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .npm/
before_script:
- npm ci --cache .npm --prefer-offline
test_async:
script:
- node ./specs/start.js ./specs/async.spec.js
Caching PHP dependencies
假设您的项目正在使用Composer安装 PHP 依赖项,下面的示例将全局定义cache
,以便所有作业都继承它. PHP 库模块安装在vendor/
并按分支缓存:
#
# https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
#
image: php:7.2
# Cache libraries in between jobs
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- vendor/
before_script:
# Install and run Composer
- curl --show-error --silent https://getcomposer.org/installer | php
- php composer.phar install
test:
script:
- vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never
Caching Python dependencies
假设您的项目正在使用pip安装 Python 依赖项,以下示例将全局定义cache
,以便所有作业都继承它. Python 库安装在venv/
下的虚拟环境中,pip 的缓存在.cache/pip/
下定义,并且两者均按分支缓存:
#
# https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates/Python.gitlab-ci.yml
#
image: python:latest
# Change pip's cache directory to be inside the project directory since we can
# only cache local items.
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
# Pip's cache doesn't store the python packages
# https://pip.pypa.io/en/stable/reference/pip_install/#caching
#
# If you want to also cache the installed packages, you have to install
# them in a virtualenv and cache it as well.
cache:
paths:
- .cache/pip
- venv/
before_script:
- python -V # Print out python version for debugging
- pip install virtualenv
- virtualenv venv
- source venv/bin/activate
test:
script:
- python setup.py test
- pip install flake8
- flake8 .
Caching Ruby dependencies
假设您的项目正在使用Bundler安装 gem 依赖项,以下示例将全局定义cache
,以便所有作业都继承它. 宝石安装在vendor/ruby/
并按分支缓存:
#
# https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
#
image: ruby:2.6
# Cache gems in between builds
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- vendor/ruby
before_script:
- ruby -v # Print out ruby version for debugging
- bundle install -j $(nproc) --path vendor/ruby # Install dependencies into ./vendor/ruby
rspec:
script:
- rspec spec
Caching Go dependencies
假设您的项目正在使用Go Modules安装 Go 依赖项,以下示例在go-cache
模板中定义了cache
,任何作业都可以扩展. Go 模块安装在${GOPATH}/pkg/mod/
,并为所有go
项目缓存:
.go-cache:
variables:
GOPATH: $CI_PROJECT_DIR/.go
before_script:
- mkdir -p .go
cache:
paths:
- .go/pkg/mod/
test:
image: golang:1.13
extends: .go-cache
script:
- go test ./... -v -short
Availability of the cache
缓存是一种优化,但不能保证始终有效,因此您需要准备好在需要它们的每个作业中重新生成所有缓存的文件.
假设您已根据工作流程在.gitlab-ci.yml
正确定义了cache
,则缓存的可用性最终取决于 Runner 的配置方式(执行程序类型以及是否使用不同的 Runner 在作业之间传递缓存).
Where the caches are stored
由于亚军是一个负责存储的缓存,这是必须要知道它的存储位置 . 在.gitlab-ci.yml
中的作业下定义的所有缓存路径都存储在单个cache.zip
文件中,并存储在 Runner 的配置缓存位置中. 默认情况下,它们存储在本地安装了 Runner 的计算机中,具体取决于执行程序的类型.
GitLab Runner 执行器 | 缓存的默认路径 |
---|---|
Shell | 在本地,存储在gitlab-runner 用户的主目录下: /home/gitlab-runner/cache/<user>/<project>/<cache-key>/cache.zip home/ gitlab-runner user>/< gitlab-runner < gitlab-runner /home/gitlab-runner/cache/<user>/<project>/<cache-key>/cache.zip . |
Docker | 在本地存储在Docker 卷下 :/ /var/lib/docker/volumes/<volume-id>/_data/<user>/<project>/<cache-key>/cache.zip docker / volumes / <卷 /var/lib/docker/volumes/<volume-id>/_data/<user>/<project>/<cache-key>/cache.zip _ /var/lib/docker/volumes/<volume-id>/_data/<user>/<project>/<cache-key>/cache.zip . |
Docker 机器 (自动缩放赛跑者) | 行为与 Docker 执行器相同. |
How archiving and extracting works
在最简单的情况下,请考虑仅使用一台安装了 Runner 的计算机,并且项目的所有作业都在同一主机上运行.
让我们看下面两个属于两个连续阶段的作业的示例:
stages:
- build
- test
before_script:
- echo "Hello"
job A:
stage: build
script:
- mkdir vendor/
- echo "build" > vendor/hello.txt
cache:
key: build-cache
paths:
- vendor/
after_script:
- echo "World"
job B:
stage: test
script:
- cat vendor/hello.txt
cache:
key: build-cache
这是幕后发生的事情:
- 管道开始.
job A
runs.- 执行
before_script
. script
已执行.after_script
被执行.cache
运行,并且vendor/
目录被压缩到cache.zip
. 然后根据Runner 的设置和cache: key
将该文件保存在目录中.job B
runs.- 提取缓存(如果找到).
- 执行
before_script
. script
已执行.- 管道完成.
通过在单台计算机上使用单个 Runner,就不会出现job B
可能在不同于job A
的 Runner 上执行的问题,从而保证了各个阶段之间的缓存. 仅当构建在同一 Runner /机器上从阶段build
到test
,此方法才有效,否则,您可能没有可用的缓存 .
在缓存过程中,还需要考虑以下几点:
- 如果具有其他缓存配置的某些其他作业已将其缓存保存在同一 zip 文件中,则它将被覆盖. 如果使用了基于 S3 的共享缓存,则还会根据缓存密钥将文件另外上传到 S3 到对象. 因此,路径不同但缓存键相同的两个作业将覆盖其缓存.
- 从
cache.zip
提取缓存时,zip 文件中的所有内容都提取到作业的工作目录(通常是下拉的存储库)中,并且 Runner 不在乎job A
的存档是否覆盖了job B
之所以以这种方式工作,是因为为一个 Runner 创建的缓存通常在由可以在不同体系结构上运行的缓存使用时才无效(例如,当缓存包含二进制文件时). 而且由于不同的步骤可能由运行在不同计算机上的运行程序执行,因此这是安全的默认设置.
Cache mismatch
在下表中,您可以看到可能导致缓存不匹配的一些原因以及一些解决方法.
缓存不匹配的原因 | 如何修复 |
---|---|
您使用多个独立运行器(不在自动缩放模式下)附加到一个项目,而没有共享缓存 | 您的项目仅使用一个 Runner 或使用启用了分布式缓存的多个 Runner |
您在未启用分布式缓存的自动缩放模式下使用 Runners | 配置自动缩放运行器以使用分布式缓存 |
安装了 Runner 的计算机磁盘空间不足,或者,如果设置了分布式缓存,则存储缓存的 S3 存储桶空间不足 | 确保清除一些空间以允许存储新的缓存. 当前,没有自动的方法可以做到这一点. |
对于作业中缓存不同路径的作业,请使用相同的key . |
Use different cache keys to that the cache archive is stored to a different location and doesn’t overwrite wrong caches. |
让我们探索一些例子.
Examples
假设您只为项目分配了一个 Runner,因此默认情况下缓存将存储在 Runner 的计算机中. 如果两个作业 A 和 B 具有相同的缓存密钥,但是它们缓存不同的路径,则即使它们的paths
不匹配,缓存 B 也会覆盖缓存 A:
当管道第二次运行时,我们希望job A
和job B
重用其缓存.
stages:
- build
- test
job A:
stage: build
script: make build
cache:
key: same-key
paths:
- public/
job B:
stage: test
script: make test
cache:
key: same-key
paths:
- vendor/
job A
runs.public/
作为 cache.zip 缓存.job B
runs.- 先前的缓存(如果有)已解压缩.
vendor/
作为 cache.zip 缓存,并覆盖前一个.- 下次
job A
运行时,它将使用job B
的缓存,缓存不同,因此无效.
要解决此问题,请为每个作业使用不同的keys
.
在另一种情况下,假设您为项目分配了多个运行器,但是未启用分布式缓存. 第二次运行管道时,我们希望job A
和job B
重用其缓存(在这种情况下将有所不同):
stages:
- build
- test
job A:
stage: build
script: build
cache:
key: keyA
paths:
- vendor/
job B:
stage: test
script: test
cache:
key: keyB
paths:
- vendor/
在这种情况下,即使key
不同(不必担心覆盖),如果作业在后续管道中的不同运行器上运行,您也可能会在每个阶段之前”清理”缓存的文件.
Clearing the cache
GitLab Runners 使用缓存通过重用现有数据来加快作业的执行速度. 但是,这有时可能导致行为不一致.
要从缓存的新副本开始,有两种方法可以做到这一点.
Clearing the cache by changing cache:key
您所需要做的就是设置一个新的cache: key
.gitlab-ci.yml
. 在下一个管道运行中,缓存将存储在其他位置.
Clearing the cache manually
在 GitLab 10.4 中引入 .
如果要避免编辑.gitlab-ci.yml
,则可以通过 GitLab 的 UI 轻松清除缓存:
在后台,这可以通过在数据库中增加一个计数器来实现,该计数器的值用于通过将整数附加到-1
, -2
等来为高速缓存创建密钥.密钥已生成,旧的缓存不再有效.