GitLab CI/CD 缓存
缓存是作业下载和保存的一个或多个文件。使用相同缓存的后续作业不必再次下载文件,因此它们执行得更快。
要了解如何在您的 .gitlab-ci.yml
文件中定义缓存,请参阅 cache
参考。
缓存与产物的不同之处
对依赖项使用缓存,例如您从 Internet 下载的包。 如果启用分布式缓存,则缓存存储在安装了 GitLab Runner 并上传到 S3 的位置。
使用产物在阶段之间传递中间构建结果。 产物由作业生成,存储在系统中,可以下载。
产物和缓存都定义了它们相对于项目目录的路径,并且不能链接到它之外的文件。
缓存
- 使用
cache
关键字定义每个作业的缓存。否则它被禁用。 - 后续流水线可以使用缓存。
- 如果依赖项相同,同一流水线中的后续作业可以使用缓存。
- 不同的项目不能共享缓存。
产物
- 定义每个作业的产物。
- 同一流水线后期的后续作业可以使用产物。
- 不同的项目不能共享产物。
- 默认情况下,产物会在 30 天后过期。您可以自定义到期时间。
- 如果启用了保留最新产物,则最新的产物不会过期。
- 使用依赖来控制哪些作业获取工件。
良好的缓存实践
要确保缓存的最大可用性,请执行以下一项或多项操作:
- 标记您的 runner 并在共享缓存的作业上使用标签。
-
使用仅适用于特定项目的 runner。
-
使用适合您工作流程的
key
。例如,您可以为每个分支配置不同的缓存。
为了让 runner 有效地使用缓存,您必须执行以下操作之一:
- 为您的所有工作使用一个 runner。
- 使用具有分布式缓存的多个 runner,其中缓存存储在 S3 存储桶中。这些 runner 可以处于自动缩放模式,但并非必须如此。
- 使用多个具有相同架构的 runner,并让这些 runner 共享一个公共的网络安装目录来存储缓存。这个目录应该使用 NFS 或类似的东西。这些 runner 必须处于自动缩放模式。
使用多个缓存
- 引入于 13.10 版本。
- 功能标志移除于 13.12 版本。
您最多可以有四个缓存:
test-job:
stage: build
cache:
- key:
files:
- Gemfile.lock
paths:
- vendor/ruby
- key:
files:
- yarn.lock
paths:
- .yarn-cache/
script:
- bundle config set --local path 'vendor/ruby'
- bundle install
- yarn install --cache-folder .yarn-cache
- echo Run tests...
如果多个缓存与回退缓存键组合,则每次未找到缓存时都会获取全局回退缓存。
使用回退缓存键
每个缓存的回退键
引入于 16.0 版本。
每个缓存条目最多支持 5 个回退键:
test-job:
stage: build
cache:
- key: cache-$CI_COMMIT_REF_SLUG
fallback_keys:
- cache-$CI_DEFAULT_BRANCH
- cache-default
paths:
- vendor/ruby
script:
- bundle config set --local path 'vendor/ruby'
- bundle install
- yarn install --cache-folder .yarn-cache
- echo Run tests...
回退键遵循与 cache:key
相同的处理逻辑,这意味着全名可能包括一个 -$index
(基于缓存清除)和 -protected
/-non_protected
(如果在受保护的分支上启用缓存分离)后缀。
全局回退键
引入于 GitLab Runner 13.4。
您可以使用 $CI_COMMIT_REF_SLUG
预定义变量来指定您的 cache:key
。例如,您的 $CI_COMMIT_REF_SLUG
是 test
,您可以设置一个作业来下载带有 test
标签的缓存。
如果没有找到带有这个标签的缓存,你可以使用 CACHE_FALLBACK_KEY
来指定一个缓存,当不存在缓存时使用。
在以下示例中,如果未找到 $CI_COMMIT_REF_SLUG
,作业将使用由 CACHE_FALLBACK_KEY
变量定义的键:
variables:
CACHE_FALLBACK_KEY: fallback-key
job1:
script:
- echo
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- binaries/
缓存提取的顺序是:
- 尝试检索
cache:key
- 在
fallback_keys
中按顺序尝试检索每个条目 - 尝试检索
CACHE_FALLBACK_KEY
中的全局回退键
在检索到第一个成功的缓存后,缓存提取过程将停止。
禁用特定作业的缓存
如果全局定义缓存,则每个作业都使用相同的定义。您可以为每个作业覆盖此行为。
要为作业完全禁用它,请使用空列表:
job:
cache: []
继承全局配置,但覆盖每个作业的特定设置
您可以使用锚点覆盖缓存设置而不覆盖全局缓存。例如,如果您想覆盖一项作业的 policy
:
default:
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
。
缓存的常见用例
通常,您每次运行作业时都使用缓存来避免下载内容,例如依赖项或库。可以缓存 Node.js 包、PHP 包、Ruby gems、Python 库等。
获取示例,查看 GitLab CI/CD 模板。
在同一分支中的作业之间共享缓存
要让每个分支中的作业使用相同的缓存,请使用 key: $CI_COMMIT_REF_SLUG
定义一个缓存:
cache:
key: $CI_COMMIT_REF_SLUG
此配置可防止您意外覆盖缓存。但是,合并请求的第一个流水线很慢。下一次提交被推送到分支时,缓存会被重用,作业运行得更快。
要启用每个作业和每个分支的缓存:
cache:
key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
要启用每个阶段和每个分支缓存:
cache:
key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
在不同分支的作业之间共享缓存
要在所有分支和所有作业之间共享缓存,请对所有内容使用相同的键:
cache:
key: one-key-to-rule-them-all
要在分支之间共享缓存,但为每个作业拥有唯一的缓存:
cache:
key: $CI_JOB_NAME
缓存 Node.js 依赖项
如果您的项目使用 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
从锁定文件计算缓存键
您可以使用 cache:key:files
从像 package-lock.json
或 yarn.lock
之类的锁文件中计算缓存键,然后在许多作业中重用。
# Cache modules using lock file
cache:
key:
files:
- package-lock.json
paths:
- .npm/
如果您使用 Yarn,您可以使用 yarn-offline-mirror
来缓存压缩的 node_modules
压缩包。缓存生成得更快,因为需要压缩的文件更少:
job:
script:
- echo 'yarn-offline-mirror ".yarn-cache/"' >> .yarnrc
- echo 'yarn-offline-mirror-pruning true' >> .yarnrc
- yarn install --frozen-lockfile --no-progress
cache:
key:
files:
- yarn.lock
paths:
- .yarn-cache/
缓存 PHP 依赖项
如果您的项目使用 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
缓存 Python 依赖项
如果您的项目使用 pip 安装 Python 依赖项,则以下示例全局定义了 cache
,以便所有作业都继承它。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
cache:
paths:
- .cache/pip
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 ruff
- ruff --format=gitlab .
缓存 Ruby 依赖项
如果您的项目使用 Bundler 安装 gem 依赖项,以下示例全局定义了 cache
,以便所有作业都继承它。 Gems 安装在 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 config set --local path 'vendor/ruby' # The location to install the specified gems to
- bundle install -j $(nproc) # Install dependencies into ./vendor/ruby
rspec:
script:
- rspec spec
如果您的作业需要不同的 gem,请在全局 cache
定义中使用 prefix
关键字。 此配置为每个作业生成不同的缓存。
例如,测试作业可能不需要与部署到生产的作业相同的 gem:
cache:
key:
files:
- Gemfile.lock
prefix: $CI_JOB_NAME
paths:
- vendor/ruby
test_job:
stage: test
before_script:
- bundle config set --local path 'vendor/ruby'
- bundle install --without production
script:
- bundle exec rspec
deploy_job:
stage: production
before_script:
- bundle config set --local path 'vendor/ruby' # The location to install the specified gems to
- bundle install --without test
script:
- bundle exec deploy
缓存 Go 依赖项
如果您的项目使用 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
缓存的可用性
缓存是一种优化,但不能保证始终有效。您可能要在需要它们的每个作业中重新生成缓存文件。
在你定义了一个 cache in .gitlab-ci.yml
之后,缓存的可用性取决于:
- runner 的 executor 类型。
- 是否使用不同的 runner 在作业之间传递缓存。
缓存的存储位置
为作业定义的所有缓存都存档在单个 cache.zip
文件中。
runner 配置定义了文件的存储位置。默认情况下,缓存存储在安装了 GitLab Runner 的机器上。位置还取决于 executor 的类型。
Runner executor | 缓存的默认路径 |
---|---|
Shell | 在本地,在 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 Machine(自动缩放 runner) | 与 Docker 执行器相同。 |
如果您使用缓存和产物在作业中存储相同的路径,缓存可能会被覆盖,因为缓存在产物之前恢复。
缓存键名称
引入于 15.0 版本。
除了回退缓存键之外,后缀被添加到缓存键中。
例如,假设 cache.key
设置为 $CI_COMMIT_REF_SLUG
,并且我们有两个分支 main
和 feature
,那么生成的缓存键如下表所述:
分支名称 | 缓存键 |
---|---|
main
| main-protected
|
feature
| feature-non_protected
|
对所有分支使用相同的缓存
引入于 15.0 版本。
如果您不想使用缓存键名称,您可以让所有分支(受保护和不受保护)使用相同的缓存。
根据缓存键名称的缓存分离是一项安全功能,仅应在所有具有开发者角色的用户都受高度信任的环境中禁用。
为所有分支使用相同的缓存:
- 在顶部栏上,选择 主菜单 > 项目 并找到您的项目。
- 在左侧边栏,选择 设置 > CI/CD。
- 展开 流水线通用设置。
- 清除 为受保护的分支使用单独的缓存 复选框。
- 选择 保存修改。
归档和提取的工作原理
此示例显示了两个连续阶段中的两个作业:
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
paths:
- vendor/
如果一台机器安装了一个 runner,那么您项目的所有作业都在同一台主机上运行:
- 流水线启动。
-
job A
运行。 -
before_script
被执行。 -
script
被执行。 -
after_script
被执行。 -
cache
运行,vendor/
目录被压缩到cache.zip
中。这个文件然后被保存在基于 runner 设置和cache: key
的目录中。 -
job B
运行。 - 提取缓存(如果找到)。
-
before_script
被执行。 -
script
被执行。 - 流水线完成。
通过在单台机器上使用单个 runner,您不会遇到 job B
可能在不同于 job A
的 runner 上执行的问题。这种设置保证缓存可以在阶段之间重用。只有在同一 runner/机器中执行从 build
阶段到 test
阶段时,它才有效。否则,缓存可能不可用。
在缓存过程中,还需要考虑以下几点:
- 如果具有其它缓存配置的其它作业,将其缓存保存在同一个 zip 文件中,则会被覆盖。如果使用基于 S3 的共享缓存,文件将额外上传到 S3,到基于缓存键的对象。因此,具有不同路径但具有相同缓存键的两个作业会覆盖它们的缓存。
- 从
cache.zip
中提取缓存时,zip 文件中的所有内容都被提取到作业的工作目录(通常是下拉的仓库)中,并且 runner 不介意job A
的存档是否覆盖job B
存档中的内容。
它以这种方式工作,因为为一个 runner 创建的缓存在被另一个 runner 使用时通常无效。不同的 runner 可能会在不同的架构上运行(例如,当缓存包含二进制文件时)。此外,因为不同的步骤可能由在不同机器上运行的 runner 执行,所以这是一个安全的默认值。
清除缓存
Runners 使用 cache 通过重用现有数据来加速作业的执行。这有时会导致不一致的行为。
有两种方法可以从缓存的新副本开始。
通过 cache:key
清除缓存
更改 .gitlab-ci.yml
文件中 cache: key
的值。
下次流水线运行时,缓存将存储在不同的位置。
手动清除缓存
您可以在 UI 中清除缓存:
- 在顶部栏上,选择 主菜单 > 项目 并找到您的项目。
- 在左侧边栏上,选择 CI/CD > 流水线 页面。
- 在右上角,选择 清除 Runner 缓存。
在下一次提交时,您的 CI/CD 作业使用新的缓存。
cache-<index>
,索引递增 1。旧缓存不会被删除。您可以从 runner 存储中手动删除这些文件。故障排查
缓存不匹配
如果缓存不匹配,请按照以下步骤进行故障排查。
缓存不匹配的原因 | 如何修复 |
---|---|
您使用附加到一个项目的多个独立 runner(不在自动缩放模式下),而没有共享缓存。 | 为您的项目仅使用一个 runner,或使用多个启用了分布式缓存的 runner。 |
您在未启用分布式缓存的情况下,使用自动缩放模式 runner。 | 将自动缩放 runner 配置为使用分布式缓存。 |
安装 runner 的机器磁盘空间不足,或者,如果您设置了分布式缓存,则存储缓存的 S3 存储桶没有足够的空间。 | 确保清除一些空间以允许存储新缓存。 没有自动的方法来做到这一点。 |
对于缓存不同路径的作业,您可以使用相同的 key 。
| 使用不同的缓存键,将缓存存档存储到不同的位置,并且不会覆盖错误的缓存。 |
您未启用 runner 上的分布式 runner 缓存。 | 设置 Shared = false 并重新配置您的跑步者。
|
缓存不匹配示例 1
如果您的项目只分配了一个 runner,则缓存默认存储在 runner 的机器上。
如果两个作业具有相同的缓存键但路径不同,则可以覆盖缓存。 例如:
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
运行。 -
public/
缓存为cache.zip
。 -
job B
运行。 - 解压之前的缓存,如果有的话。
-
vendor/
缓存为cache.zip
,覆盖上一个。 - 下一次
job A
运行时,它使用的是不同的job B
缓存,因此无效。
要解决此问题,请为每个作业使用不同的 keys
。
缓存不匹配示例 2
在此示例中,您为项目分配了多个 runner,并且未启用分布式缓存。
流水线第二次运行时,您希望 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
不同,如果作业在后续流水线中的不同 runner 上运行,则缓存文件可能会在每个阶段之前被“清理”。