极狐 GitLab

极狐GitLab CI/CD 中的缓存

Tier: 基础版,专业版,旗舰版

Offering: JihuLab.com,私有化部署

缓存是作业下载并保存的一个或多个文件。后续使用同一缓存的作业无需再次下载这些文件,因此执行速度更快。

要了解如何在.gitlab-ci.yml 文件中定义缓存,请参阅 cache 参考。

对于高级缓存键策略,您可以使用:

有关更多用例和示例,请参阅 CI/CD 缓存示例。

缓存与产物的区别#

将缓存用于依赖项,例如您从互联网下载的软件包。缓存存储在安装极狐GitLab Runner 的位置,如果启用了分布式缓存,则会上传到 S3。

使用产物在阶段之间传递中间构建结果。产物由作业生成,存储在极狐GitLab 中,并且可以下载。

产物和缓存都定义相对于项目目录的路径,并且不能链接到该目录之外的文件。

缓存#

  • 使用 cache 关键字为每个作业定义缓存。否则缓存处于禁用状态。
  • 后续流水线可以使用该缓存。
  • 同一流水线中的后续作业可以使用该缓存,前提是依赖项完全相同。
  • 不同项目不能共享缓存。
  • 默认情况下,受保护分支和非受保护分支不共享缓存。不过,您可以更改此行为。

产物#

  • 为每个作业定义产物。
  • 同一流水线后续阶段中的作业可以使用产物。
  • 产物默认在 30 天后过期。您可以定义自定义的过期时间。
  • 如果启用了保留最新产物,最新产物不会过期。
  • 使用依赖项控制哪些作业获取产物。

良好的缓存实践#

为确保缓存的最大可用性,请执行以下一项或多项操作:

要让 Runner 高效地使用缓存,您必须执行以下操作之一:

  • 为所有作业使用单个 Runner。
  • 使用多个具有 分布式缓存的 Runner, 其中缓存存储在 S3 存储桶中。JihuLab.com 上的实例 Runner 就是这种工作方式。这些 Runner 可以处于自动扩缩模式, 但并非必须如此。要管理缓存对象, 请应用生命周期规则,在一段时间后删除缓存对象。 生命周期规则在对象存储服务器上可用。
  • 使用多个具有相同架构的 Runner,并让这些 Runner 共享一个通用的网络挂载目录来存储缓存。此目录应使用 NFS 或类似方案。 这些 Runner 必须处于自动扩缩模式。

使用多个缓存#

每个作业最多可以有四个缓存:

yaml
1test-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...

如果多个缓存与回退缓存键组合使用,则每次找不到缓存时都会获取全局回退缓存。

使用回退缓存键#

每个缓存的回退键#

每个缓存条目最多支持五个回退键,使用 fallback_keys 关键字。当作业找不到缓存键时,作业会尝试改为检索回退缓存。回退键按顺序搜索,直到找到缓存。如果未找到任何缓存,作业将在不使用缓存的情况下运行。例如:

yaml
1test-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...

在此示例中:

  1. 作业查找 cache-$CI_COMMIT_REF_SLUG 缓存。
  2. 如果未找到 cache-$CI_COMMIT_REF_SLUG,作业会查找 cache-$CI_DEFAULT_BRANCH 作为回退选项。
  3. 如果也未找到 cache-$CI_DEFAULT_BRANCH,作业会查找 cache-default 作为第二个回退选项。
  4. 如果都未找到,作业将在不使用缓存的情况下下载所有 Ruby 依赖项, 但在作业完成时为 cache-$CI_COMMIT_REF_SLUG 创建新缓存。

回退键遵循与 cache:key 相同的处理逻辑:

全局回退键#

您可以使用$CI_COMMIT_REF_SLUG 预定义变量来指定您的 cache:key。例如,如果您的 $CI_COMMIT_REF_SLUG 是 test,您可以将作业设置为下载标记为 test的缓存。

如果找不到带有此标记的缓存,您可以使用 CACHE_FALLBACK_KEY 指定在不存在缓存时要使用的缓存。

在以下示例中,如果找不到$CI_COMMIT_REF_SLUG,作业将使用由 CACHE_FALLBACK_KEY 变量定义的键:

yaml
1variables: 2 CACHE_FALLBACK_KEY: fallback-key 3 4job1: 5 script: 6 - echo 7 cache: 8 key: "$CI_COMMIT_REF_SLUG" 9 paths: 10 - binaries/

缓存提取的顺序为:

  1. 尝试检索 cache:key
  2. 按顺序尝试检索 fallback_keys中的每个条目
  3. 尝试检索 CACHE_FALLBACK_KEY中的全局回退键

缓存提取过程在成功检索到第一个缓存后停止。

为特定作业禁用缓存#

如果您全局定义缓存,每个作业都会使用相同的定义。您可以为每个作业覆盖此行为。

要为某个作业完全禁用它,请使用空列表:

yaml
job: cache: []

继承全局配置,但为每个作业覆盖特定设置#

您可以使用锚点覆盖缓存设置,而无需覆盖全局缓存。例如,如果您想为一个作业覆盖 policy:

yaml
1default: 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 # inherit all global cache settings 13 <<: *global_cache 14 # override the policy 15 policy: pull

有关更多信息,请参阅 cache: policy。

缓存键名称#

缓存键会添加一个后缀,但全局回退缓存键除外。

如果流水线满足以下条件,缓存键会获得-protected 后缀:

  • 为受保护分支或标签运行。用户必须具有合并到 受保护分支的权限,或具有 创建受保护标签的权限。
  • 由具有维护者或所有者角色的用户启动。

在其他流水线中生成的键会获得 non_protected 后缀。

例如,如果:

  • cache:key 设置为$CI_COMMIT_REF_SLUG。
  • main 是受保护分支。
  • feature 是非受保护分支。
分支开发者角色缓存键维护者角色缓存键
mainmain-protectedmain-protected
featurefeature-non_protectedfeature-protected

此外,对于标签的流水线,后缀以标签的保护状态为准,而不是流水线执行所在的分支。此行为可确保一致的安全边界,因为触发引用决定了缓存访问权限。

例如,如果:

  • cache:key 设置为$CI_COMMIT_TAG。
  • main 是受保护分支。
  • feature 是非受保护分支。
  • 1.0.0 是受保护标签。
  • 1.1.1-rc1 是非受保护标签。
标签分支开发者角色缓存键维护者角色缓存键
1.0.0main1.0.0-protected1.0.0-protected
1.0.0feature1.0.0-protected1.0.0-protected
1.1.1-rc1main1.1.1-rc1-non_protected1.1.1-rc1-protected
1.1.1-rc1feature1.1.1-rc1-non_protected1.1.1-rc1-protected

为所有分支使用同一缓存#

如果您不想使用缓存键名称,可以让所有分支(受保护和非受保护)使用同一缓存。

使用缓存键名称进行缓存分离是一项安全功能,只应在所有具有开发者角色的用户都高度可信的环境中禁用它。

要为所有分支使用同一缓存:

  1. 在顶部栏中,选择搜索或跳转到,然后找到您的项目。
  2. 在左侧边栏中,选择设置 > CI/CD。
  3. 展开流水线通用设置。
  4. 清除为受保护分支使用单独的缓存复选框。
  5. 选择保存更改。

缓存的可用性#

缓存是一种优化,但并不能保证始终有效。您可能需要在需要缓存的每个作业中重新生成缓存文件。

在.gitlab-ci.yml中定义缓存后,缓存的可用性取决于:

  • Runner 的执行器类型。
  • 是否使用不同的 Runner 在作业之间传递缓存。

缓存的存储位置#

为作业定义的所有缓存都归档在单个 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(自动扩缩 Runner)与 Docker 执行器相同。

如果您在作业中使用缓存和产物存储同一路径,缓存可能会被覆盖,因为缓存会在产物之前恢复。

归档和提取工作流#

此示例展示了两个连续阶段中的两个作业:

yaml
1stages: 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,所有作业都在该主机上运行:

  1. 流水线开始。
  2. job A 运行。
  3. 提取缓存(如果找到)。
  4. 执行 before_script。
  5. 执行 script。
  6. 执行 after_script。
  7. cache 运行,vendor/目录被压缩为 cache.zip。 然后,此文件根据 Runner 的设置和 cache: key 保存到相应目录中。
  8. job B 运行。
  9. 提取缓存(如果找到)。
  10. 执行 before_script。
  11. 执行 script。
  12. 流水线结束。

通过在单台机器上使用单个 Runner,您不会遇到 job B 可能在不同于 job A的 Runner 上执行的问题。此设置可保证缓存可以在阶段之间重用。它仅在执行从 build 阶段到 test 阶段在同一 Runner/机器上进行时才有效。否则,缓存可能不可用。

存在此限制是因为为一个 Runner 创建的缓存在被另一个 Runner 使用时通常无效。不同的 Runner 可能使用不同的架构,因此缓存的二进制文件可能无法工作。Runner 也可能运行在不同的机器上,因此极狐GitLab 默认使用同一 Runner 来保持缓存可靠。

Rendering chart...

在缓存过程中,还要考虑以下事项:

  • 如果其他作业使用不同的缓存配置,将其 缓存保存到同一 zip 文件中,则该缓存会被覆盖。如果使用基于 S3 的共享缓存, 该文件还会根据缓存键上传到 S3 中的某个对象。因此,两个路径不同但缓存键相同的作业 会覆盖各自的缓存。
  • 从 cache.zip 提取缓存时,zip 文件中的所有内容都会 提取到作业的工作目录中(通常是拉取下来的代码仓库),并且 Runner 不介意 job A的归档 是否覆盖 job B 归档中的内容。

清除缓存#

Runner 使用缓存通过重用现有数据来加速作业执行。这有时可能导致行为不一致。

要从全新的缓存副本开始,请更改 cache:key 或手动清除它。

通过更改 cache:key 清除缓存#

更改.gitlab-ci.yml 文件中 cache: key的值。下次流水线运行时,缓存将存储在不同的位置。

手动清除缓存#

先决条件:

  • 您必须至少具有项目的开发者角色。

清除缓存会强制下一次流水线(包括受保护分支上的流水线)重新获取依赖项。 如果作业未固定其依赖项版本,该获取可能会解析到无人审查过的更新上游版本。 请固定您的依赖项版本,以便在清除缓存时流水线的内容不会发生变化。

您可以在极狐GitLab UI 中清除缓存:

  1. 在顶部栏中,选择搜索或跳转到,然后找到您的项目。
  2. 在左侧边栏中,选择构建 > 流水线。
  3. 在右上角,选择清除 Runner 缓存。

在下一次提交时,您的 CI/CD 作业将使用新缓存。

每次手动清除缓存时,内部缓存名称都会更新。该名称使用 cache-<index>格式,索引递增一。旧缓存不会被删除。您可以从 Runner 存储中手动删除这些文件。

故障排除#

缓存不匹配#

如果您遇到缓存不匹配,请按照以下步骤进行故障排除。

缓存不匹配的原因如何修复
您使用多个独立 Runner(不处于自动扩缩模式)连接到一个项目,但没有共享缓存。为您的项目仅使用一个 Runner,或使用多个启用了分布式缓存的 Runner。
您在自动扩缩模式下使用 Runner,但未启用分布式缓存。将自动扩缩 Runner 配置为使用分布式缓存。
安装 Runner 的机器磁盘空间不足,或者如果您设置了分布式缓存,存储缓存的 S3 存储桶空间不足。确保清理一些空间以允许存储新缓存。没有自动执行此操作的方法。
您为缓存不同路径的作业使用相同的 key。使用不同的缓存键,以便缓存归档存储到不同的位置,不会覆盖错误的缓存。
您尚未在 Runner 上启用分布式 Runner 缓存。设置 Shared = false 并重新预配您的 Runner。

缓存不匹配示例 1#

如果您的项目只分配了一个 Runner,缓存默认存储在 Runner 的机器上。

如果两个作业具有相同的缓存键但路径不同,缓存可能会被覆盖。例如:

yaml
1stages: 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/
  1. job A 运行。
  2. public/被缓存为 cache.zip。
  3. job B 运行。
  4. 解压之前的缓存(如果有)。
  5. vendor/被缓存为 cache.zip 并覆盖之前的缓存。
  6. 下次 job A 运行时,它会使用 job B的缓存,该缓存不同, 因此无效。

要解决此问题,请为每个作业使用不同的 keys。

缓存不匹配示例 2#

在此示例中,您的项目分配了多个 Runner,并且未启用分布式缓存。

第二次流水线运行时,您希望 job A 和 job B 重用它们的缓存(在此例中缓存不同):

yaml
1stages: 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 不同,如果作业在后续流水线中运行在不同的 Runner 上,缓存的文件也可能在每个阶段之前被“清理”。

并发 Runner 缺少本地缓存#

如果您使用 Docker 执行器配置了多个并发 Runner,本地缓存的文件可能不会如您预期的那样对并发运行的作业可用。缓存卷的名称是为每个 Runner 实例唯一构造的,因此一个 Runner 实例缓存的文件不会被另一个 Runner 实例在缓存中找到。

要在并发 Runner 之间共享缓存,您可以:

  • 使用 Runner 的 config.toml中的[runners.docker]部分,在主机上配置一个挂载点, 将其映射到每个容器中的/cache,例如 volumes = ["/mnt/gitlab-runner/cache-for-all-concurrent-jobs:/cache"]。 此方法可防止 Runner 为并发作业创建唯一的卷名称。
  • 使用分布式缓存。