Cache dependencies in GitLab CI/CD

Cache dependencies in GitLab CI/CD

GitLab CI / CD 提供了一种缓存机制,可用于在作业运行时节省时间.

缓存是指通过重用先前作业的相同内容来加快执行作业的时间. 当您开发依赖于在构建期间通过 Internet 获取的其他库的软件时,此功能特别有用.

如果启用了缓存,则默认情况下从 GitLab 9.0 开始,它在项目级别的管道和作业之间共享. 缓存不跨项目共享.

确保您阅读了以了解.gitlab-ci.yml定义.

注意:如果您使用缓存和工件在作业中存储相同的路径时要小心,因为在覆盖工件和内容之前 ,将还原缓存 .

不要使用缓存在阶段之间传递工件,因为缓存旨在存储编译项目所需的运行时依赖项:

  • cache: 用于存储项目依赖项

    缓存用于通过存储下载的依赖项来加快后续作业在给定管道中的运行速度,这样就不必再次从 Internet 上获取它们(例如 npm 软件包,Go 供应商软件包等).配置为在阶段之间传递中间构建结果,则应该使用工件来完成.

  • artifacts: 用于在阶段之间传递的阶段结果.

    工件是由作业生成的文件,可以存储并上载,然后可以在同一管道的后续阶段中由作业获取和使用. 换句话说, 您不能在阶段 1 的 job-A 中创建工件,然后在阶段 1 的 job-B 中使用此工件 . 此数据在不同的管道中将不可用,但可以从 UI 下载.

artifacts的名称听起来像是仅在工作之外有用,例如用于下载最终图像,但是人工制品也可以在管道的后期阶段使用. 因此,如果通过下载所有必需的模块来构建应用程序,则可能需要将它们声明为工件,以便后续阶段可以使用它们. 有一些优化措施,例如声明这样您就不会将工件保留太长时间,或者使用依赖项来控制哪些作业会获取工件.

Caches:

  • 如果未全局定义或未按作业定义(使用cache: :),则禁用该功能.
  • 如果全局启用,则可用于.gitlab-ci.yml所有作业.
  • 可以由创建缓存的同一作业在后续管道中使用(如果未全局定义).
  • 如果 ,则将它们存储在 Runner 安装的位置上传到 S3.
  • 如果按作业定义,则使用:
    • 通过后续管道中的相同作业.
    • 如果它们具有相同的依赖关系,则由同一管道中的后续作业组成.

Artifacts:

  • 如果未按作业定义(使用artifacts: :),则将其禁用.
  • 只能针对每个作业启用,不能全局启用.
  • 在管道中创建,并且可以被当前活动管道的后续作业使用.
  • 始终上传到 GitLab(称为协调器).
  • 可以具有用于控制磁盘使用量的到期值(默认为 30 天).

注意:工件和缓存均定义了相对于项目目录的路径,并且无法链接到其外部的文件.

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 ,请使用以下一项或多项:

  • 为跑步者添加标签,并在共享其缓存的作业上使用标签.
  • 仅适用于特定项目的粘性运行器 .
  • 适合您的工作流key (例如,每个分支上的不同缓存). 为此,您可以利用 .

提示:在管道中使用相同的 Runner 是在一个阶段或管道中缓存文件,并以有保证的方式将该缓存传递到后续阶段或管道的最简单,最有效的方法.

从 Runner 的角度来看,为了使缓存有效运行,必须满足以下条件之一:

  • 为您的所有工作使用一个赛跑者.
  • 使用多个使用分布式缓存的 Runner(无论是否处于自动缩放模式),这些将缓存存储在 S3 存储桶中(例如 GitLab.com 上的共享 Runner).
  • 使用具有相同体系结构的多个运行程序(不在自动伸缩模式下)共享公共网络安装目录(使用 NFS 或类似方式),以存储缓存.

提示:了解缓存的以了解有关内部的更多信息,并更好地了解缓存的工作方式.

尽管这似乎可以防止意外覆盖缓存,但是这意味着合并请求的第一个流水线很慢,这可能会给开发人员带来糟糕的体验. 下次将新提交推送到分支时,将重新使用缓存.

To enable per-job and per-branch caching:

  1. cache:
  2. key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"

要启用每个分支和每个阶段的缓存:

  1. cache:
  2. key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"

Sharing caches across different branches

如果要缓存的文件需要在所有分支和所有作业之间共享,则可以对所有这些文件使用相同的密钥:

  1. cache:
  2. key: one-key-to-rule-them-all

要在分支之间共享相同的缓存,但按作业将它们分开:

  1. cache:
  2. key: ${CI_JOB_NAME}

Disabling cache on specific jobs

如果已全局定义了缓存,则意味着每个作业将使用相同的定义. 您可以按工作覆盖此行为,如果要完全禁用它,请使用空哈希:

Inherit global config, but override specific settings per job

您可以使用覆盖缓存设置,而无需覆盖全局缓存. 例如,如果要覆盖一项作业的policy

  1. cache: &global_cache
  2. key: ${CI_COMMIT_REF_SLUG}
  3. paths:
  4. - node_modules/
  5. - public/
  6. - vendor/
  7. policy: pull-push
  8. job:
  9. cache:
  10. # inherit all global cache settings
  11. <<: *global_cache
  12. # override the policy

要进行更精细的调整,还请阅读有关cache: policy .

缓存最常见的用例是在后续作业之间保留内容,以用于诸如依赖项和常用库(Node.js 包,PHP 包,rubygems,Python 库等)之类的东西,因此不必从公共互联网重新获取.

注意:有关更多示例,请查看我们的 .

假设您的项目正在使用npm安装 Node.js 依赖项,下面的示例将全局定义cache ,以便所有作业都继承它. 默认情况下,npm 将缓存数据存储在主文件夹~/.npm但是由于 ,因此我们告诉 npm 使用./.npm ,它按分支缓存:

  1. #
  2. # https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml
  3. #
  4. image: node:latest
  5. # Cache modules in between jobs
  6. cache:
  7. key: ${CI_COMMIT_REF_SLUG}
  8. paths:
  9. - .npm/
  10. before_script:
  11. - npm ci --cache .npm --prefer-offline
  12. test_async:
  13. script:
  14. - node ./specs/start.js ./specs/async.spec.js

Caching PHP dependencies

假设您的项目正在使用安装 PHP 依赖项,下面的示例将全局定义cache ,以便所有作业都继承它. PHP 库模块安装在vendor/并按分支缓存:

  1. #
  2. # https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
  3. #
  4. image: php:7.2
  5. # Cache libraries in between jobs
  6. cache:
  7. key: ${CI_COMMIT_REF_SLUG}
  8. paths:
  9. - vendor/
  10. before_script:
  11. # Install and run Composer
  12. - curl --show-error --silent https://getcomposer.org/installer | php
  13. - php composer.phar install
  14. test:
  15. script:
  16. - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never

Caching Python dependencies

假设您的项目正在使用安装 Python 依赖项,以下示例将全局定义cache ,以便所有作业都继承它. Python 库安装在venv/下的虚拟环境中,pip 的缓存在.cache/pip/下定义,并且两者均按分支缓存:

  1. #
  2. #
  3. image: python:latest
  4. # Change pip's cache directory to be inside the project directory since we can
  5. # only cache local items.
  6. variables:
  7. PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
  8. # Pip's cache doesn't store the python packages
  9. # https://pip.pypa.io/en/stable/reference/pip_install/#caching
  10. #
  11. # If you want to also cache the installed packages, you have to install
  12. # them in a virtualenv and cache it as well.
  13. cache:
  14. paths:
  15. - .cache/pip
  16. - venv/
  17. before_script:
  18. - python -V # Print out python version for debugging
  19. - pip install virtualenv
  20. - virtualenv venv
  21. - source venv/bin/activate
  22. test:
  23. script:
  24. - python setup.py test
  25. - pip install flake8
  26. - flake8 .

Caching Ruby dependencies

假设您的项目正在使用安装 gem 依赖项,以下示例将全局定义cache ,以便所有作业都继承它. 宝石安装在vendor/ruby/并按分支缓存:

假设您的项目正在使用Go Modules安装 Go 依赖项,以下示例在go-cache模板中定义了cache ,任何作业都可以扩展. Go 模块安装在${GOPATH}/pkg/mod/ ,并为所有go项目缓存:

  1. .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. test:
  10. image: golang:1.13
  11. extends: .go-cache
  12. script:
  13. - go test ./... -v -short

缓存是一种优化,但不能保证始终有效,因此您需要准备好在需要它们的每个作业中重新生成所有缓存的文件.

假设您已根据工作流程正确定义了cache ,则缓存的可用性最终取决于 Runner 的配置方式(执行程序类型以及是否使用不同的 Runner 在作业之间传递缓存).

Where the caches are stored

由于亚军是一个负责存储的缓存,这是必须要知道它的存储位置 . 在.gitlab-ci.yml中的作业下定义的所有缓存路径都存储在单个cache.zip文件中,并存储在 Runner 的配置缓存位置中. 默认情况下,它们存储在本地安装了 Runner 的计算机中,具体取决于执行程序的类型.

How archiving and extracting works

在最简单的情况下,请考虑仅使用一台安装了 Runner 的计算机,并且项目的所有作业都在同一主机上运行.

  1. stages:
  2. - test
  3. before_script:
  4. - echo "Hello"
  5. job A:
  6. stage: build
  7. script:
  8. - mkdir vendor/
  9. - echo "build" > vendor/hello.txt
  10. cache:
  11. key: build-cache
  12. paths:
  13. - vendor/
  14. after_script:
  15. - echo "World"
  16. stage: test
  17. script:
  18. - cat vendor/hello.txt
  19. cache:
  20. key: build-cache

这是幕后发生的事情:

  1. 管道开始.
  2. job A runs.
  3. 执行before_script .
  4. script已执行.
  5. after_script被执行.
  6. cache运行,并且vendor/目录被压缩到cache.zip . 然后根据和cache: key将该文件保存在目录中.
  7. job B runs.
  8. 提取缓存(如果找到).
  9. 执行before_script .
  10. script已执行.
  11. 管道完成.

通过在单台计算机上使用单个 Runner,就不会出现job B可能在不同于job A的 Runner 上执行的问题,从而保证了各个阶段之间的缓存. 仅当构建在同一 Runner /机器上从阶段buildtest ,此方法才有效,否则,您可能没有可用的缓存 .

在缓存过程中,还需要考虑以下几点:

  • 如果具有其他缓存配置的某些其他作业已将其缓存保存在同一 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 Ajob B重用其缓存.

  1. stages:
  2. - build
  3. - test
  4. job A:
  5. stage: build
  6. script: make build
  7. cache:
  8. key: same-key
  9. paths:
  10. - public/
  11. job B:
  12. stage: test
  13. script: make test
  14. cache:
  15. key: same-key
  16. paths:
  17. - vendor/
  1. job A runs.
  2. public/作为 cache.zip 缓存.
  3. job B runs.
  4. 先前的缓存(如果有)已解压缩.
  5. vendor/作为 cache.zip 缓存,并覆盖前一个.
  6. 下次job A运行时,它将使用job B的缓存,缓存不同,因此无效.

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

在另一种情况下,假设您为项目分配了多个运行器,但是未启用分布式缓存. 第二次运行管道时,我们希望job Ajob B重用其缓存(在这种情况下将有所不同):

  1. stages:
  2. - build
  3. - test
  4. job A:
  5. stage: build
  6. script: build
  7. cache:
  8. key: keyA
  9. paths:
  10. - vendor/
  11. job B:
  12. stage: test
  13. script: test
  14. cache:
  15. key: keyB
  16. paths:
  17. - vendor/

在这种情况下,即使key不同(不必担心覆盖),如果作业在后续管道中的不同运行器上运行,您也可能会在每个阶段之前”清理”缓存的文件.

GitLab Runners 使用通过重用现有数据来加快作业的执行速度. 但是,这有时可能导致行为不一致.

要从缓存的新副本开始,有两种方法可以做到这一点.

您所需要做的就是设置一个新的cache: key .gitlab-ci.yml . 在下一个管道运行中,缓存将存储在其他位置.

Clearing the cache manually

在 GitLab 10.4 中 .

如果要避免编辑.gitlab-ci.yml ,则可以通过 GitLab 的 UI 轻松清除缓存:

  1. 导航到项目的CI / CD>管道页面.
  2. 单击” 清除流道缓存”按钮以清理缓存.