极狐GitLab CI/CD 缓存
- Tier: 基础版,专业版,旗舰版
- Offering: JihuLab.com,私有化部署
缓存是一个或多个文件,作业下载并保存。使用相同缓存的后续作业不必再次下载这些文件,因此执行速度更快。
要了解如何在 .gitlab-ci.yml 文件中定义缓存,请参阅 cache 参考。
缓存与产物的区别
使用缓存来处理依赖项,例如从互联网下载的软件包。缓存存储在安装极狐GitLab Runner 的地方,并上传到 S3,如果 分布式缓存已启用。
使用产物在阶段之间传递中间构建结果。产物由作业生成,存储在极狐GitLab 中,并可以下载。
产物和缓存都根据项目目录定义它们的路径,不能链接到目录之外的文件。
缓存
- 使用 cache 关键字为每个作业定义缓存。否则它将被禁用。
- 后续流水线可以使用缓存。
- 同一流水线中的后续作业可以使用缓存,如果依赖项相同。
- 不同项目无法共享缓存。
- 默认情况下,受保护和非受保护分支 不共享缓存。但是,您可以 更改此行为。
产物
- 为每个作业定义产物。
- 同一流水线的后续阶段中的作业可以使用产物。
- 默认情况下,产物在 30 天后过期。您可以定义自定义过期时间。
- 如果启用了 保留最新产物,最新产物不会过期。
- 使用 依赖项 来控制哪些作业获取产物。
良好的缓存实践
为了确保缓存的最大可用性,请执行以下一项或多项操作:
- 标记您的 runners,并在共享缓存的作业上使用标签。
- 使用仅对特定项目可用的 runners。
- 使用符合您工作流程的 key。例如,您可以为每个分支配置不同的缓存。
为了使 runners 高效地使用缓存,您必须执行以下操作之一:
- 为所有作业使用单个 runner。
- 使用多个具有 分布式缓存 的 runners,其中缓存存储在 S3 存储桶中。极狐GitLab.com 上的实例 runners 以这种方式运行。这些 runners 可以处于自动扩展模式,但不必如此。要管理缓存对象,请应用生命周期规则以在一段时间后删除缓存对象。生命周期规则在对象存储服务器上可用。
- 使用具有相同架构的多个 runners,并让这些 runners 共享一个公共网络挂载目录来存储缓存。此目录应使用 NFS 或类似的东西。这些 runners 必须处于自动扩展模式。
使用多个缓存
您最多可以有四个缓存:
yaml1test-job: 2 stage: build 3 cache: 4 - key: 5 files: 6 - Gemfile.lock 7 paths: 8 - vendor/ruby 9 - key: 10 files: 11 - yarn.lock 12 paths: 13 - .yarn-cache/ 14 script: 15 - bundle config set --local path 'vendor/ruby' 16 - bundle install 17 - yarn install --cache-folder .yarn-cache 18 - echo Run tests...
如果多个缓存与回退缓存键结合使用,每次找不到缓存时都会提取全局回退缓存。
使用回退缓存键
每缓存回退键
History
- 引入于极狐GitLab 16.0。
每个缓存条目最多支持五个回退键,通过 fallback_keys 关键字。当作业找不到缓存键时,作业尝试检索回退缓存。回退键按顺序搜索,直到找到缓存。如果没有找到缓存,作业将在不使用缓存的情况下运行。例如:
yaml1test-job: 2 stage: build 3 cache: 4 - key: cache-$CI_COMMIT_REF_SLUG 5 fallback_keys: 6 - cache-$CI_DEFAULT_BRANCH 7 - cache-default 8 paths: 9 - vendor/ruby 10 script: 11 - bundle config set --local path 'vendor/ruby' 12 - bundle install 13 - echo Run tests...
在这个例子中:
- 作业查找 cache-$CI_COMMIT_REF_SLUG 缓存。
- 如果找不到 cache-$CI_COMMIT_REF_SLUG,作业将 cache-$CI_DEFAULT_BRANCH 作为回退选项。
- 如果 cache-$CI_DEFAULT_BRANCH 也找不到,作业将 cache-default 作为第二个回退选项。
- 如果没有找到,作业将在不使用缓存的情况下下载所有 Ruby 依赖项,但在作业完成时为 cache-$CI_COMMIT_REF_SLUG 创建新缓存。
回退键遵循与 cache:key 相同的处理逻辑:
- 如果您 手动清除缓存,每缓存回退键将像其他缓存键一样附加索引。
- 如果启用了 使用受保护分支的单独缓存 设置,每缓存回退键将附加 -protected 或 -non_protected。
全局回退键
您可以使用 $CI_COMMIT_REF_SLUG 预定义变量 来指定您的 cache:key。例如,如果您的 $CI_COMMIT_REF_SLUG 是 test,您可以设置一个作业来下载标记为 test 的缓存。
如果找不到带此标签的缓存,您可以使用 CACHE_FALLBACK_KEY 来指定在没有缓存时要使用的缓存。
在以下示例中,如果找不到 $CI_COMMIT_REF_SLUG,作业使用 CACHE_FALLBACK_KEY 变量定义的键:
yaml1variables: 2 CACHE_FALLBACK_KEY: fallback-key 3 4job1: 5 script: 6 - echo 7 cache: 8 key: "$CI_COMMIT_REF_SLUG" 9 paths: 10 - binaries/
缓存提取的顺序是:
- 尝试检索 cache:key
- 按顺序尝试检索 fallback_keys 中的每个条目
- 尝试检索 CACHE_FALLBACK_KEY 中的全局回退键
缓存提取过程在成功检索到第一个缓存后停止。
禁用特定作业的缓存
如果您全局定义了缓存,每个作业都使用相同的定义。您可以为每个作业覆盖此行为。
要完全禁用它,请使用空列表:
yamljob: cache: []
继承全局配置,但覆盖每个作业的特定设置
您可以使用 anchors 覆盖缓存设置,而不覆盖全局缓存。例如,如果您想覆盖一个作业的 policy:
yaml1default: 2 cache: &global_cache 3 key: $CI_COMMIT_REF_SLUG 4 paths: 5 - node_modules/ 6 - public/ 7 - vendor/ 8 policy: pull-push 9 10job: 11 cache: 12 # 继承所有全局缓存设置 13 <<: *global_cache 14 # 覆盖策略 15 policy: pull
有关更多信息,请参阅 cache: policy。
缓存的常见用例
通常,您使用缓存来避免每次运行作业时下载内容,例如依赖项或库。Node.js 软件包、PHP 软件包、Ruby gems、Python 库等可以被缓存。
在同一分支的作业之间共享缓存
为了让每个分支中的作业使用相同的缓存,请定义一个带有 key: $CI_COMMIT_REF_SLUG 的缓存:
yamlcache: key: $CI_COMMIT_REF_SLUG
此配置可防止您意外覆盖缓存。然而,合并请求的第一次流水线速度较慢。下一次将提交推送到分支时,缓存会被重用,作业运行速度更快。
要启用每个作业和每个分支的缓存:
yamlcache: key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
要启用每个阶段和每个分支的缓存:
yamlcache: key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
在不同分支的作业中共享缓存
要在所有分支和所有作业之间共享缓存,请为所有内容使用相同的键:
yamlcache: key: one-key-to-rule-them-all
要在分支之间共享缓存,但为每个作业拥有唯一的缓存:
yamlcache: key: $CI_JOB_NAME
使用变量控制作业的缓存策略
History
- 引入于极狐GitLab 16.1。
为了减少作业重复,其中唯一的区别是拉取策略,您可以使用 CI/CD 变量。
例如:
yaml1conditional-policy: 2 rules: 3 - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH 4 variables: 5 POLICY: pull-push 6 - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH 7 variables: 8 POLICY: pull 9 stage: build 10 cache: 11 key: gems 12 policy: $POLICY 13 paths: 14 - vendor/bundle 15 script: 16 - echo "This job pulls and pushes the cache depending on the branch" 17 - echo "Downloading dependencies..."
在这个例子中,作业的缓存策略为:
- 默认分支的更改为 pull-push。
- 其他分支的更改为 pull。
缓存 Node.js 依赖项
如果您的项目使用 npm 来安装 Node.js 依赖项,以下示例定义了一个默认的 cache,以便所有作业继承它。默认情况下,npm 将缓存数据存储在主文件夹 (~/.npm) 中。但是,您 不能缓存项目目录之外的东西。相反,告诉 npm 使用 ./.npm,并按分支缓存它:
yaml1default: 2 image: node:latest 3 cache: # 在作业之间缓存模块 4 key: $CI_COMMIT_REF_SLUG 5 paths: 6 - .npm/ 7 before_script: 8 - npm ci --cache .npm --prefer-offline 9 10test_async: 11 script: 12 - node ./specs/start.js ./specs/async.spec.js
从锁定文件计算缓存键
您可以使用 cache:key:files 来从 package-lock.json 或 yarn.lock 等锁定文件计算缓存键,并在许多作业中重用它。
yaml1default: 2 cache: # 使用锁定文件缓存模块 3 key: 4 files: 5 - package-lock.json 6 paths: 7 - .npm/
如果您使用 Yarn,您可以使用 yarn-offline-mirror 来缓存压缩的 node_modules tarballs。缓存生成更快,因为需要压缩的文件更少:
yaml1job: 2 script: 3 - echo 'yarn-offline-mirror ".yarn-cache/"' >> .yarnrc 4 - echo 'yarn-offline-mirror-pruning true' >> .yarnrc 5 - yarn install --frozen-lockfile --no-progress 6 cache: 7 key: 8 files: 9 - yarn.lock 10 paths: 11 - .yarn-cache/
缓存 PHP 依赖项
如果您的项目使用 Composer 来安装 PHP 依赖项,以下示例定义了一个默认的 cache,以便所有作业继承它。PHP 库模块安装在 vendor/ 中,并按分支缓存:
yaml1default: 2 image: php:latest 3 cache: # 在作业之间缓存库 4 key: $CI_COMMIT_REF_SLUG 5 paths: 6 - vendor/ 7 before_script: 8 # 安装并运行 Composer 9 - curl --show-error --silent "https://getcomposer.org/installer" | php 10 - php composer.phar install 11 12test: 13 script: 14 - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never
缓存 Python 依赖项
如果您的项目使用 pip 来安装 Python 依赖项,以下示例定义了一个默认的 cache,以便所有作业继承它。pip 的缓存定义在 .cache/pip/ 下,并按分支缓存:
yaml1default: 2 image: python:latest 3 cache: # Pip 的缓存不存储 python 软件包 4 paths: # https://pip.pypa.io/en/stable/topics/caching/ 5 - .cache/pip 6 before_script: 7 - python -V # 打印出 python 版本以进行调试 8 - pip install virtualenv 9 - virtualenv venv 10 - source venv/bin/activate 11 12variables: # 将 pip 的缓存目录更改为项目目录内部,因为我们只能缓存本地项目。 13 PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" 14 15test: 16 script: 17 - python setup.py test 18 - pip install ruff 19 - ruff --format=gitlab .
缓存 Ruby 依赖项
如果您的项目使用 Bundler 来安装 gem 依赖项,以下示例定义了一个默认的 cache,以便所有作业继承它。Gems 安装在 vendor/ruby/ 中,并按分支缓存:
yaml1default: 2 image: ruby:latest 3 cache: # 在构建之间缓存 gems 4 key: $CI_COMMIT_REF_SLUG 5 paths: 6 - vendor/ruby 7 before_script: 8 - ruby -v # 打印出 ruby 版本以进行调试 9 - bundle config set --local path 'vendor/ruby' # 安装指定 gems 的位置 10 - bundle install -j $(nproc) # 将依赖项安装到 ./vendor/ruby 11 12rspec: 13 script: 14 - rspec spec
如果您有需要不同 gems 的作业,请在全局 cache 定义中使用 prefix 关键字。此配置为每个作业生成不同的缓存。
例如,测试作业可能不需要与部署到生产环境的作业相同的 gems:
yaml1default: 2 cache: 3 key: 4 files: 5 - Gemfile.lock 6 prefix: $CI_JOB_NAME 7 paths: 8 - vendor/ruby 9 10test_job: 11 stage: test 12 before_script: 13 - bundle config set --local path 'vendor/ruby' 14 - bundle install --without production 15 script: 16 - bundle exec rspec 17 18deploy_job: 19 stage: production 20 before_script: 21 - bundle config set --local path 'vendor/ruby' # 安装指定 gems 的位置 22 - bundle install --without test 23 script: 24 - bundle exec deploy
缓存 Go 依赖项
如果您的项目使用 Go Modules 来安装 Go 依赖项,以下示例在 go-cache 模板中定义 cache,任何作业都可以扩展它。Go 模块安装在 ${GOPATH}/pkg/mod/ 中,并为所有 go 项目缓存:
yaml1.go-cache: 2 variables: 3 GOPATH: $CI_PROJECT_DIR/.go 4 before_script: 5 - mkdir -p .go 6 cache: 7 paths: 8 - .go/pkg/mod/ 9 10test: 11 image: golang:latest 12 extends: .go-cache 13 script: 14 - go test ./... -v -short
缓存的可用性
缓存是一种优化,但不能保证始终有效。您可能需要在每个需要它们的作业中重新生成缓存文件。
在 .gitlab-ci.yml 中定义 缓存 后,缓存的可用性取决于:
- runner 的执行器类型。
- 是否使用不同的 runners 在作业之间传递缓存。
缓存的存储位置
为作业定义的所有缓存都存档在一个 cache.zip 文件中。runner 配置定义文件的存储位置。默认情况下,缓存存储在安装极狐GitLab Runner 的机器上。位置还取决于执行器类型。
Runner 执行器 | 缓存的默认路径 |
---|---|
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 (自动扩展 runners) | 与 Docker 执行器相同。 |
如果您在作业中使用缓存和产物来存储相同的路径,缓存可能会被覆盖,因为缓存在产物之前恢复。
缓存键名称
History
- 引入于极狐GitLab 15.0。
一个后缀会被添加到缓存键中,除了 全局回退缓存键。
例如,假设 cache.key 设置为 $CI_COMMIT_REF_SLUG,并且我们有两个分支 main 和 feature,那么以下表格表示结果缓存键:
分支名称 | 缓存键 |
---|---|
main | main-protected |
feature | feature-non_protected |
为所有分支使用相同的缓存
History
- 引入于极狐GitLab 15.0。
如果您不想使用 缓存键名称,您可以让所有分支(受保护和未受保护)使用相同的缓存。
使用 缓存键名称 的缓存分离是一项安全功能,应仅在所有具有开发者角色的用户高度可信的环境中禁用。
要为所有分支使用相同的缓存:
- 在左侧边栏中,选择 搜索或转到 并找到您的项目。
- 选择 设置 > CI/CD。
- 展开 常规流水线。
- 清除 使用受保护分支的单独缓存 复选框。
- 选择 保存更改。
归档和提取的工作原理
这个例子展示了两个阶段中的两个作业:
yaml1stages: 2 - build 3 - test 4 5default: 6 cache: 7 key: build-cache 8 paths: 9 - vendor/ 10 before_script: 11 - echo "Hello" 12 13job A: 14 stage: build 15 script: 16 - mkdir vendor/ 17 - echo "build" > vendor/hello.txt 18 after_script: 19 - echo "World" 20 21job B: 22 stage: test 23 script: 24 - cat vendor/hello.txt
如果一台机器上安装了一个 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 上执行的问题。此设置保证缓存可以在阶段之间重用。它仅在从 build 阶段到 test 阶段的执行在同一个 runner/机器上时有效。否则,缓存可能 不可用。
在缓存过程中,还有一些需要考虑的事项:
- 如果某个作业,具有另一个缓存配置,将其缓存保存到相同的 zip 文件中,则会被覆盖。如果使用基于 S3 的共享缓存,文件会被另外上传到基于缓存键的 S3 对象。所以,两个作业具有不同的路径,但相同的缓存键,会覆盖它们的缓存。
- 从 cache.zip 提取缓存时,zip 文件中的所有内容都会在作业的工作目录中提取(通常是拉取下来的仓库),runner 不在意 job A 的存档是否覆盖了 job B 存档中的内容。
它以这种方式工作,因为为一个 runner 创建的缓存通常在不同的 runner 中使用时无效。不同的 runner 可能在不同的架构上运行(例如,当缓存包括二进制文件时)。此外,由于不同的步骤可能由在不同机器上运行的 runners 执行,这是一个安全的默认设置。
清除缓存
Runners 使用 缓存 来加速作业的执行,重用现有数据。这有时可能导致行为不一致。
有两种方法可以从缓存的全新副本开始。
通过更改 cache:key 清除缓存
更改 .gitlab-ci.yml 文件中 cache: key 的值。下次流水线运行时,缓存将存储在不同的位置。
手动清除缓存
您可以在极狐GitLab UI 中清除缓存:
- 在左侧边栏中,选择 搜索或转到 并找到您的项目。
- 选择 构建 > 流水线。
- 在右上角,选择 清除 runner 缓存。
在下一个提交中,您的 CI/CD 作业将使用新缓存。
每次手动清除缓存时,内部缓存名称 会更新。名称使用 cache-<index> 格式,索引递增一。旧缓存不会被删除。您可以从 runner 存储中手动删除这些文件。
故障排除
缓存不匹配
如果缓存不匹配,请按照以下步骤进行故障排除。
缓存不匹配原因 | 如何解决 |
---|---|
您使用多个独立的 runners(不在自动扩展模式下)连接到一个项目,没有共享缓存。 | 为您的项目使用一个 runner 或使用启用分布式缓存的多个 runners。 |
您在自动扩展模式下使用 runners,没有启用分布式缓存。 | 配置自动扩展 runner 以使用分布式缓存。 |
安装 runner 的机器磁盘空间不足,或者如果您已设置分布式缓存,缓存存储的 S3 存储桶空间不足。 | 确保清除一些空间以允许新缓存存储。没有自动方法可以做到这一点。 |
您为缓存不同路径的作业使用相同的 key。 | 使用不同的缓存键,以便缓存存档存储到不同位置,并不会覆盖错误的缓存。 |
您尚未在您的 runners 上启用 分布式 runner 缓存。 | 设置 Shared = false 并重新配置您的 runners。 |
缓存不匹配示例 1
如果您只为项目分配了一个 runner,缓存默认存储在 runner 的机器上。
如果两个作业具有相同的缓存键但不同的路径,缓存可能会被覆盖。例如:
yaml1stages: 2 - build 3 - test 4 5job A: 6 stage: build 7 script: make build 8 cache: 9 key: same-key 10 paths: 11 - public/ 12 13job B: 14 stage: test 15 script: make test 16 cache: 17 key: same-key 18 paths: 19 - vendor/
- job A 运行。
- public/ 被缓存为 cache.zip。
- job B 运行。
- 如果有,则提取之前的缓存。
- vendor/ 被缓存为 cache.zip,覆盖了之前的缓存。
- 下次运行 job A 时,它使用 job B 的缓存,这与 job A 不同,因此无效。
要解决此问题,请为每个作业使用不同的 keys。
缓存不匹配示例 2
在这个例子中,您为项目分配了多个 runners,并且没有启用分布式缓存。
第二次流水线运行时,您希望 job A 和 job B 重用它们的缓存(在这种情况下是不同的):
yaml1stages: 2 - build 3 - test 4 5job A: 6 stage: build 7 script: build 8 cache: 9 key: keyA 10 paths: 11 - vendor/ 12 13job B: 14 stage: test 15 script: test 16 cache: 17 key: keyB 18 paths: 19 - vendor/
即使 key 不同,如果作业在后续流水线中在不同的 runners 上运行,缓存文件可能在每个阶段之前被“清理”。
并发 runners 缺少本地缓存
如果您配置了多个并发 runners 使用 Docker 执行器,您可能会发现并发运行的作业未按预期找到本地缓存的文件。缓存卷的名称为每个 runner 实例唯一构建,因此一个 runner 实例缓存的文件不会在另一个 runner 实例的缓存中找到。
要在并发 runners 间共享缓存,您可以:
- 使用 runners 的 config.toml 的 [runners.docker] 部分配置主机上的单个挂载点,并映射到每个容器中的 /cache,防止 runner 创建唯一的卷名称。
- 使用分布式缓存。