管道缓存

Azure DevOps Services

管道缓存允许在以后的运行中重复使用先前某个运行中的输出或下载的依赖项,这样就减少或避免重新创建或重新下载相同文件的成本,从而缩短生成时间。 如果每次运行开始时都会重复下载同样的依赖项,在这种情况下,缓存特别有用。 这通常是一个耗时的过程,涉及数百或数千个网络调用。

如果还原和保存缓存的时间少于从头开始重新生成输出的时间,则缓存可以极大缩短生成时间。 因此,缓存可能并非在所有情况下都有效,并且实际上也可能会对生成时间产生负面影响。

注意

经典发布管道不支持管道缓存。

工件与缓存的不同适用场景

管道缓存和管道工件执行类似的功能,但专为不同的方案而设计,不应互换使用。

  • 当你需要获取一个作业中生成的特定文件并将其与其他作业共享时(且如果没有它们,这些其他作业很可能会失败),请使用管道工件

  • 如果你想要通过重用以前运行中的文件来缩短生成时间(且没有这些文件不会影响作业运行的能力),请使用管道缓存

注意

管道缓存和管道工件适用于所有层级(免费和付费)。 有关更多详细信息,请参阅 Artifacts 存储消耗。

缓存任务:工作原理

使用缓存任务将缓存添加到管道。 此任务的工作方式与任何其他任务类似,并已添加到作业的 steps 部分。

在运行过程中遇到缓存步骤时,任务会根据提供的输入还原缓存。 如果未找到缓存,则步骤完成,并运行作业中的下一步骤。

作业中的所有步骤都已运行并假定作业状态为“成功”后,会自动为每个未跳过的“还原缓存”步骤添加并触发一个特殊的“作业后:缓存”步骤。 此步骤负责保存缓存。

注意

缓存是不可变的,这意味着创建缓存后,其内容不可更改。

配置缓存任务

缓存任务有两个必需参数:key 和 path:

  • path:要缓存的文件夹的路径。 可以是绝对路径或相对路径。 针对 $(System.DefaultWorkingDirectory) 解析相对路径。

注意

可以使用预定义的变量来存储要缓存的文件夹的路径,但不支持通配符。

  • key:应设置为要还原或保存的缓存的标识符。 键由字符串值、文件路径或文件模式的组合组成,其中每个段用 | 字符分隔。
  • 字符串
    固定值(如缓存名称或工具名称)或是获取自环境变量(如当前操作系统或当前作业名称)

  • 文件路径
    要对其内容进行哈希处理的特定文件的路径。 在任务运行时,此文件必须存在。 请记住,任何“看起来像是文件路径”的 key 段都将被视为文件路径。 具体而言,这包括包含 . 的段。 如果此“文件”不存在,可能会导致任务失败。

    提示

    要避免将类似路径的字符串段视为文件路径,请用双引号将其括起来,例如:"my.key" | $(Agent.OS) | key.file

  • 文件模式
    必须至少匹配一个文件的 glob 样式通配符模式列表(以逗号分隔)。 例如:

    • **/yarn.lock:源目录下的所有 yarn.lock 文件
    • */asset.json, !bin/**:位于源目录下某个目录中的所有 asset.json 文件,bin 目录下的文件除外

对由文件路径或文件模式标识的任何文件的内容,将进行哈希处理以生成动态缓存键。 如果项目具有唯一标识所缓存内容的文件,这非常有用。 例如,package-lock.jsonyarn.lockGemfile.lockPipfile.lock 等文件通常在缓存键中引用,因为它们都表示一组唯一的依赖项。

将根据 $(System.DefaultWorkingDirectory) 来解析相对文件路径或文件模式。

示例

以下示例演示如何缓存 Yarn 安装的依赖项:

variables:
  YARN_CACHE_FOLDER: $(Pipeline.Workspace)/s/.yarn

steps:
- task: Cache@2
  inputs:
    key: '"yarn" | "$(Agent.OS)" | yarn.lock'
    restoreKeys: |
       "yarn" | "$(Agent.OS)"
       "yarn"
    path: $(YARN_CACHE_FOLDER)
  displayName: Cache Yarn packages

- script: yarn --frozen-lockfile

在此示例中,缓存键包含三个部分:静态字符串 yarn,运行作业的操作系统(因为此缓存对于每个操作系统是唯一的)以及唯一标识缓存中依赖项集的文件 yarn.lock 的哈希值。

在添加任务后的第一次运行时,缓存步骤将报告“缓存未命中”,因为此键标识的缓存不存在。 完成最后一个步骤后,将通过 $(Pipeline.Workspace)/s/.yarn 中的文件创建缓存并上传缓存。 下次运行时,缓存步骤将报告“缓存命中”,缓存的内容将下载并还原。

使用 checkout: self 时,存储库签出到 $(Pipeline.Workspace)/s,文件夹 .yarn 通常驻留在存储库本身中。

注意

Pipeline.Workspace 是运行管道的代理上的本地路径,在其中创建了所有目录。 此变量的值与 Agent.BuildDirectory 相同。

如果使用的是 checkout: self 以外的任何内容,请确保更新变量 YARN_CACHE_FOLDER,因为该变量应指向 .yarn 驻留的存储库。

还原键

如果想要查询多个精确键或键前缀,可以使用 restoreKeys。 用于在 key 不会产生命中的情况下回退到另一个键。 还原键将按前缀搜索键,并生成最新创建的缓存条目作为结果。 如果管道找不到完全匹配项,但希望改用部分缓存命中,还原键将非常有用。 要插入多个还原键,使用换行符进行分隔以指示还原键(有关详细信息,请参见示例)。 尝试还原键的顺序是从上到下。

自托管代理上所需的软件

存档软件/平台 Windows Linux Mac
GNU Tar 必需 必需
BSD Tar 必需
7-Zip 建议

上述可执行文件需要位于 PATH 环境变量中列出的文件夹中。 切记,托管代理随附的软件,这仅适用于自托管代理。

示例

下面是如何通过 Yarn 使用还原键的示例:

variables:
  YARN_CACHE_FOLDER: $(Pipeline.Workspace)/.yarn

steps:
- task: Cache@2
  inputs:
    key: '"yarn" | "$(Agent.OS)" | yarn.lock'
    restoreKeys: |
       yarn | "$(Agent.OS)"
       yarn
    path: $(YARN_CACHE_FOLDER)
  displayName: Cache Yarn packages

- script: yarn --frozen-lockfile

在此示例中,缓存任务尝试查找缓存中是否存在该键。 如果缓存中不存在键,则会尝试使用第一个还原键 yarn | $(Agent.OS)。 这会尝试搜索与该键完全匹配或以该键作为前缀的所有键。 如果存在不同的 yarn.lock 哈希段,可能会发生前缀命中。 例如,如果以下yarn | $(Agent.OS) | old-yarn.lock键在缓存中,其中old-yarn.lock产生的哈希值与 yarn.lock不同,则恢复键将产生部分命中。 如果未命中第一个还原键,则将使用下一个还原键 yarn,该将将尝试查找以 yarn 开头的任何键。 对于前缀命中,结果将生成最近创建的缓存键作为结果。

注意

一个管道可以有一个或多个缓存任务。 缓存存储容量没有限制,来自同一管道的作业和任务可以访问和共享同一缓存。

缓存隔离和安全性

为了确保不同管道和不同分支的缓存之间隔离,每个缓存都属于一个名为作用域的逻辑容器。 范围提供安全边界,以确保:

  1. 一个管道中的作业无法访问另一个管道中的缓存,并且
  2. 构建 PR 的作业具有对 PR 目标分支(针对同一管道)的缓存的读取权限,但不能在目标分支的范围内写入(创建)缓存。

在运行期间遇到缓存步骤时,将从服务器请求由键标识的缓存。 然后,服务器从作业可见的作用域中寻找具有此键的缓存,并返回缓存(如果可用)。 在缓存保存时(作业结束时),缓存被写入表示管道和分支的作用域。 有关详细信息,请参阅下文。

CI、手动和计划运行

作用域 读取 写入
源分支
main 分支
main 分支

拉取请求运行

作用域 读取 写入
源分支
目标分支
中间分支(如 refs/pull/1/merge
main 分支
main 分支

拉取请求分叉运行

分支 读取 写入
目标分支
中间分支(如 refs/pull/1/merge
main 分支
main 分支

提示

由于缓存已限定为项目、管道和分支,因此无需在缓存密钥中包含任何项目、管道或分支标识符。

对缓存还原进行调节

在某些情况下,成功还原缓存会导致运行一组不同的步骤。 例如,如果缓存已还原,则可能会跳过安装依赖项的步骤。 可以使用 cacheHitVar 任务输入来实现。 将此输入设置为某个环境变量的名称后,当发生缓存命中时,该变量会被设为 true;在还原密钥缓存命中时设为 inexact,否则设为 false。 然后可以在步骤条件中或从脚本中引用此变量。

在以下示例中,还原缓存时将跳过 install-deps.sh 步骤:

steps:
- task: Cache@2
  inputs:
    key: mykey | mylockfile
    restoreKeys: mykey
    path: $(Pipeline.Workspace)/mycache
    cacheHitVar: CACHE_RESTORED

- script: install-deps.sh
  condition: ne(variables.CACHE_RESTORED, 'true')

- script: build.sh

Bundler

对于使用 Bundler 的 Ruby 项目,覆盖 Bundler 使用的 BUNDLE_PATH 环境变量,设置捆绑程序用来寻找 Gem 的路径

示例

variables:
  BUNDLE_PATH: $(Pipeline.Workspace)/.bundle

steps:
- task: Cache@2
  displayName: Bundler caching
  inputs:
    key: 'gems | "$(Agent.OS)" | Gemfile.lock'
    path: $(BUNDLE_PATH)
    restoreKeys: | 
      gems | "$(Agent.OS)"
      gems   

Ccache (C/C++)

Ccache 是 C/C++ 的编译器缓存。 要在管道中使用 Ccache,请确保已安装 Ccache 并选择性地添加到 PATH(请参阅 Ccache 运行模式)。 将 CCACHE_DIR 环境变量设置为 $(Pipeline.Workspace) 下面的路径,并缓存此目录。

示例

variables:
  CCACHE_DIR: $(Pipeline.Workspace)/ccache

steps:
- bash: |
    sudo apt-get install ccache -y    
    echo "##vso[task.prependpath]/usr/lib/ccache"
  displayName: Install ccache and update PATH to use linked versions of gcc, cc, etc

- task: Cache@2
  displayName: Ccache caching
  inputs:
    key: 'ccache | "$(Agent.OS)" | $(Build.SourceVersion)'
    path: $(CCACHE_DIR)
    restoreKeys: | 
      ccache | "$(Agent.OS)"

有关更多详细信息,请参阅 Ccache 配置设置

Docker 映像

缓存 Docker 映像可显著减少运行管道所需的时间。

variables:
  repository: 'myDockerImage'
  dockerfilePath: '$(Build.SourcesDirectory)/app/Dockerfile'
  tag: '$(Build.BuildId)'

pool:
  vmImage: 'ubuntu-latest'
steps:
  - task: Cache@2
    displayName: Cache task
    inputs:
      key: 'docker | "$(Agent.OS)" | cache'
      path: $(Pipeline.Workspace)/docker
      cacheHitVar: CACHE_RESTORED                #Variable to set to 'true' when the cache is restored
    
  - script: |
      docker load -i $(Pipeline.Workspace)/docker/cache.tar
    displayName: Docker restore
    condition: and(not(canceled()), eq(variables.CACHE_RESTORED, 'true'))

  - task: Docker@2
    displayName: 'Build Docker'
    inputs:
      command: 'build'
      repository: '$(repository)'
      dockerfile: '$(dockerfilePath)'
      tags: |
        '$(tag)'

  - script: |
      mkdir -p $(Pipeline.Workspace)/docker
      docker save -o $(Pipeline.Workspace)/docker/cache.tar $(repository):$(tag)
    displayName: Docker save
    condition: and(not(canceled()), not(failed()), ne(variables.CACHE_RESTORED, 'true'))
  • key:(必需) - 缓存的唯一标识符。
  • path:(必需) - 要缓存的文件夹或文件的路径。

Golang

对于 Golang 项目,可以指定要在 go.mod 文件中下载的包。 如果尚未设置 GOCACHE 变量,请将其设置为要下载缓存的位置。

示例

variables:
  GO_CACHE_DIR: $(Pipeline.Workspace)/.cache/go-build/

steps:
- task: Cache@2
  inputs:
    key: 'go | "$(Agent.OS)" | go.mod'
    restoreKeys: | 
      go | "$(Agent.OS)"
    path: $(GO_CACHE_DIR)
  displayName: Cache GO packages

Gradle

使用 Gradle 的 内置缓存支持可能会对生成时间产生重大影响。 要启用生成缓存,请将 GRADLE_USER_HOME 环境变量设置为 $(Pipeline.Workspace) 下的路径,并使用 --build-cache 运行生成或将 org.gradle.caching=true 添加到 gradle.properties 文件。

示例

variables:
  GRADLE_USER_HOME: $(Pipeline.Workspace)/.gradle

steps:
- task: Cache@2
  inputs:
    key: 'gradle | "$(Agent.OS)" | **/build.gradle.kts' # Swap build.gradle.kts for build.gradle when using Groovy
    restoreKeys: |
      gradle | "$(Agent.OS)"
      gradle
    path: $(GRADLE_USER_HOME)
  displayName: Configure gradle caching

- task: Gradle@2
  inputs:
    gradleWrapperFile: 'gradlew'
    tasks: 'build'
    options: '--build-cache'
  displayName: Build

- script: |   
    # stop the Gradle daemon to ensure no files are left open (impacting the save cache operation later)
    ./gradlew --stop    
  displayName: Gradlew stop
  • restoreKeys:在主键失败的情况下使用的回退键(可选)

注意

缓存是不可变的,一旦为特定范围(分支)创建具有特定键的缓存,则无法更新缓存。 这意味着,如果键是固定值,则同一分支的所有后续生成都将无法更新缓存,即使缓存的内容已更改。 如果要使用固定键值,则必须使用 restoreKeys 参数作为回退选项。

Maven

Maven 有一个本地存储库,用于存储下载和生成工件。 要启用,请将 maven.repo.local 选项设置为 $(Pipeline.Workspace) 下的路径并缓存此文件夹。

示例

variables:
  MAVEN_CACHE_FOLDER: $(Pipeline.Workspace)/.m2/repository
  MAVEN_OPTS: '-Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)'

steps:
- task: Cache@2
  inputs:
    key: 'maven | "$(Agent.OS)" | **/pom.xml'
    restoreKeys: |
      maven | "$(Agent.OS)"
      maven
    path: $(MAVEN_CACHE_FOLDER)
  displayName: Cache Maven local repo

- script: mvn install -B -e

如果使用 Maven 任务,请确保同时传递 MAVEN_OPTS 变量,如若不然,此变量会被覆盖:

- task: Maven@4
  inputs:
    mavenPomFile: 'pom.xml'
    mavenOptions: '-Xmx3072m $(MAVEN_OPTS)'

.NET/NuGet

如果使用 PackageReferences 直接在项目文件中管理 NuGet 依赖项并且具有 packages.lock.json 文件,则可以按如下方式来启用缓存:将 NUGET_PACKAGES 环境变量设置为 $(UserProfile) 下面的路径并缓存此目录。 有关如何锁定依赖项的更多详细信息,请参阅项目文件中的包引用。 如果要使用多个 packages.lock.json,仍可使用以下示例,而无需进行任何更改。 将对所有 packages.lock.json 文件的内容进行哈希处理,如果其中一个文件发生更改,将生成新的缓存键。

示例

variables:
  NUGET_PACKAGES: $(Pipeline.Workspace)/.nuget/packages

steps:
- task: Cache@2
  inputs:
    key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/**/packages.lock.json'
    restoreKeys: |
       nuget | "$(Agent.OS)"
       nuget
    path: $(NUGET_PACKAGES)
  displayName: Cache NuGet packages

如果项目使用 packages.lock.json 锁定包版本,此方法也适用于 .NET Core 项目。 通过在 Csproj 文件中将 RestorePackagesWithLockFile 设置为 True,或者使用以下命令来启用此功能:dotnet restore --use-lock-file

Node.js/npm

可通过不同的方法在 Node.js 项目中启用缓存,但建议的方法是缓存 npm 的共享缓存目录。 此目录由 npm 管理,并包含所有已下载模块的缓存版本。 在安装过程中,npm 默认首先检查此目录中是否存在模块,这可以减少或消除对公共 npm 注册表或专用注册表的网络调用。

由于 npm 共享缓存目录的默认路径在所有平台上各不相同,因此建议将 npm_config_cache 环境变量改为 $(Pipeline.Workspace) 下面的某个固定路径。 这还可以确保从容器和非容器作业都能访问缓存。

示例

variables:
  npm_config_cache: $(Pipeline.Workspace)/.npm

steps:
- task: Cache@2
  inputs:
    key: 'npm | "$(Agent.OS)" | package-lock.json'
    restoreKeys: |
       npm | "$(Agent.OS)"
    path: $(npm_config_cache)
  displayName: Cache npm

- script: npm ci

如果项目没有 package-lock.json 文件,请改为在缓存键输入中引用 package.json 文件。

提示

由于 npm ci 删除 node_modules 文件夹以确保使用一组一致且可重复的模块,因此在调用 npm ci 时应避免缓存 node_modules

Node.js/Yarn

与 npm 一样,可通过不同的方式缓存随 Yarn 一起安装的包。 建议的方法是缓存 Yarn 的共享缓存文件夹。 此目录由 Yarn 管理,包含所有已下载包的缓存版本。 在安装过程中,Yarn 默认情况下首先检查此目录是否有模块,这可以减少或消除对公共或专用注册表的网络调用。

示例

variables:
  YARN_CACHE_FOLDER: $(Pipeline.Workspace)/.yarn

steps:
- task: Cache@2
  inputs:
    key: 'yarn | "$(Agent.OS)" | yarn.lock'
    restoreKeys: |
       yarn | "$(Agent.OS)"
       yarn
    path: $(YARN_CACHE_FOLDER)
  displayName: Cache Yarn packages

- script: yarn --frozen-lockfile

Python/Anaconda

使用 Anaconda 环境设置管道缓存:

示例

variables:
  CONDA_CACHE_DIR: /usr/share/miniconda/envs

# Add conda to system path
steps:
- script: echo "##vso[task.prependpath]$CONDA/bin"
  displayName: Add conda to PATH

- bash: |
    sudo chown -R $(whoami):$(id -ng) $(CONDA_CACHE_DIR)
  displayName: Fix CONDA_CACHE_DIR directory permissions

- task: Cache@2
  displayName: Use cached Anaconda environment
  inputs:
    key: 'conda | "$(Agent.OS)" | environment.yml'
    restoreKeys: | 
      python | "$(Agent.OS)"
      python
    path: $(CONDA_CACHE_DIR)
    cacheHitVar: CONDA_CACHE_RESTORED

- script: conda env create --quiet --file environment.yml
  displayName: Create Anaconda environment
  condition: eq(variables.CONDA_CACHE_RESTORED, 'false')
  • Windows

    - task: Cache@2
      displayName: Cache Anaconda
      inputs:
        key: 'conda | "$(Agent.OS)" | environment.yml'
        restoreKeys: | 
          python | "$(Agent.OS)"
          python
        path: $(CONDA)/envs
        cacheHitVar: CONDA_CACHE_RESTORED
    
    - script: conda env create --quiet --file environment.yml
      displayName: Create environment
      condition: eq(variables.CONDA_CACHE_RESTORED, 'false')
    

PHP/Composer

对于使用 Composer 的 PHP 项目,请替代 Composer 使用的 COMPOSER_CACHE_DIR环境变量

示例

variables:
  COMPOSER_CACHE_DIR: $(Pipeline.Workspace)/.composer

steps:
- task: Cache@2
  inputs:
    key: 'composer | "$(Agent.OS)" | composer.lock'
    restoreKeys: |
      composer | "$(Agent.OS)"
      composer
    path: $(COMPOSER_CACHE_DIR)
  displayName: Cache composer

- script: composer install

已知问题和反馈

如果在为管道设置缓存时遇到问题,请检查 microsoft/azure-pipelines-tasks 存储库中未解决问题的列表。 如果未看到问题列出,请创建一个新问题,并提供有关你的场景的必要信息。

问答

问:是否可以清除缓存?

答:目前不支持清除缓存。 但是,可以将字符串文本(如 version2)添加到现有缓存键,以避免对现有缓存进行任何命中的方式更改密钥。 例如,从中更改以下缓存密钥:

key: 'yarn | "$(Agent.OS)" | yarn.lock'

更改为:

key: 'version2 | yarn | "$(Agent.OS)" | yarn.lock'

问:缓存何时过期?

答:如果缓存七天无活动,则会过期。

问:何时会上传缓存?

答:在管道的最后一个步骤结束之后,将从缓存 path 创建缓存并上传缓存。 有关更多详细信息,请参阅示例

问:缓存的大小是否有限制?

答:对单个缓存的大小以及组织中所有缓存的总大小没有强制限制。