Cache de pipeline

Serviços de DevOps do Azure

O cache de pipeline pode ajudar a reduzir o tempo de compilação, permitindo que as saídas ou dependências baixadas de uma execução sejam reutilizadas em execuções posteriores, reduzindo ou evitando assim o custo de recriar ou baixar novamente os mesmos arquivos novamente. O cache é especialmente útil em cenários em que as mesmas dependências são baixadas repetidamente no início de cada execução. Este é muitas vezes um processo demorado que envolve centenas ou milhares de chamadas de rede.

O cache pode ser eficaz para melhorar o tempo de compilação, desde que o tempo para restaurar e salvar o cache seja menor do que o tempo para produzir a saída novamente do zero. Por isso, o cache pode não ser eficaz em todos os cenários e pode realmente ter um impacto negativo no tempo de compilação.

Atualmente, há suporte para cache em trabalhos de CI e implantação, mas não em trabalhos de versão clássicos.

Quando usar artefatos versus cache

O cache de pipeline e os artefatos de pipeline executam funções semelhantes, mas são projetados para cenários diferentes e não devem ser usados de forma intercambiável.

  • Use artefatos de pipeline quando precisar pegar arquivos específicos produzidos em um trabalho e compartilhá-los com outros trabalhos (e esses outros trabalhos provavelmente falharão sem eles).

  • Use o cache de pipeline quando quiser melhorar o tempo de compilação reutilizando arquivos de execuções anteriores (e não ter esses arquivos não afetará a capacidade de execução do trabalho).

Nota

O cache de pipeline e os artefatos de pipeline são gratuitos para todos os níveis (gratuitos e pagos). consulte Consumo de armazenamento de artefatos para obter mais detalhes.

Tarefa de cache: como funciona

O cache é adicionado a um pipeline usando a tarefa Cache. Esta tarefa funciona como qualquer outra tarefa e é adicionada steps à seção de um trabalho.

Quando uma etapa de cache é encontrada durante uma execução, a tarefa restaura o cache com base nas entradas fornecidas. Se nenhum cache for encontrado, a etapa será concluída e a próxima etapa do trabalho será executada.

Depois que todas as etapas do trabalho tiverem sido executadas e assumindo um status de trabalho bem-sucedido , uma etapa especial "Pós-trabalho: Cache" é automaticamente adicionada e acionada para cada etapa de "restaurar cache" que não foi ignorada. Esta etapa é responsável por salvar o cache.

Nota

Os caches são imutáveis, o que significa que, uma vez que um cache é criado, seu conteúdo não pode ser alterado.

Configurar a tarefa Cache

A tarefa Cache tem dois argumentos necessários: chave e caminho:

  • caminho: o caminho da pasta para armazenar em cache. Pode ser um caminho absoluto ou relativo. Os caminhos relativos são resolvidos em relação a $(System.DefaultWorkingDirectory).

Nota

Você pode usar variáveis predefinidas para armazenar o caminho para a pasta que deseja armazenar em cache, no entanto, curingas não são suportados.

  • chave: deve ser definido como o identificador do cache que você deseja restaurar ou salvar. As chaves são compostas por uma combinação de valores de cadeia de caracteres, caminhos de arquivo ou padrões de arquivo, onde cada segmento é separado por um | caractere.
  • Cordas:
    Valor fixo (como o nome do cache ou o nome de uma ferramenta) ou retirado de uma variável de ambiente (como o SO atual ou o nome do trabalho atual)

  • Caminhos de arquivo:
    Caminho para um arquivo específico cujo conteúdo será colocado em hash. Esse arquivo deve existir no momento em que a tarefa é executada. Lembre-se de que qualquer segmento de chave que "se pareça com um caminho de arquivo" será tratado como um caminho de arquivo. Em particular, isto inclui segmentos que contêm um .. Isso pode resultar na falha da tarefa quando esse "arquivo" não existe.

    Gorjeta

    Para evitar que um segmento de cadeia de caracteres semelhante a um caminho seja tratado como um caminho de arquivo, envolva-o com aspas duplas, por exemplo: "my.key" | $(Agent.OS) | key.file

  • Padrões de arquivo:
    Lista separada por vírgulas do padrão curinga no estilo glob que deve corresponder a pelo menos um arquivo. Por exemplo:

    • **/yarn.lock: todos os arquivos yarn.lock sob o diretório de códigos-fonte
    • */asset.json, !bin/**: todos os arquivos asset.json localizados em um diretório sob o diretório sources, exceto sob o diretório bin

O conteúdo de qualquer arquivo identificado por um caminho de arquivo ou padrão de arquivo é colocado em hash para produzir uma chave de cache dinâmica. Isso é útil quando seu projeto tem arquivo(s) que identificam exclusivamente o que está sendo armazenado em cache. Por exemplo, arquivos como package-lock.json, yarn.lock, Gemfile.lock, ou Pipfile.lock são comumente referenciados em uma chave de cache, uma vez que todos eles representam um conjunto exclusivo de dependências.

Os caminhos de arquivo relativos ou padrões de arquivo são resolvidos em relação ao $(System.DefaultWorkingDirectory).

Exemplo:

Aqui está um exemplo mostrando como armazenar em cache dependências instaladas pelo 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

Neste exemplo, a chave de cache contém três partes: uma cadeia de caracteres estática ("yarn"), o sistema operacional em que o trabalho está sendo executado, uma vez que esse cache é exclusivo por sistema operacional, e o hash do arquivo que identifica exclusivamente o yarn.lock conjunto de dependências no cache.

Na primeira execução após a tarefa ser adicionada, a etapa de cache relatará uma "falha de cache", uma vez que o cache identificado por essa chave não existe. Após a última etapa, um cache será criado a partir dos arquivos e $(Pipeline.Workspace)/s/.yarn carregado. Na próxima execução, a etapa de cache relatará um "acerto de cache" e o conteúdo do cache será baixado e restaurado.

Ao usar checkout: selfo , é feito check-out do repositório no $(Pipeline.Workspace)/s, e sua .yarn pasta geralmente reside no próprio repositório.

Nota

Pipeline.Workspace é o caminho local no agente que executa o pipeline onde todos os diretórios são criados. Esta variável tem o mesmo valor que Agent.BuildDirectory.

Certifique-se de atualizar a variável YARN_CACHE_FOLDER se estiver usando algo diferente de checkout: self como isso deve apontar para o repositório onde .yarn reside.

Restaurar chaves

restoreKeys pode ser usado se alguém quiser consultar várias chaves exatas ou prefixos de chave. Isso é usado para cair de volta para outra chave no caso de um key não produzir um acerto. Uma chave de restauração procurará uma chave por prefixo e produzirá a última entrada de cache criada como resultado. Isso é útil se o pipeline não conseguir encontrar uma correspondência exata, mas quiser usar um acerto de cache parcial. Para inserir várias chaves de restauração, basta delimitá-las usando uma nova linha para indicar a chave de restauração (consulte o exemplo para obter mais detalhes). A ordem da qual as chaves de restauração serão julgadas será de cima para baixo.

Software necessário no agente auto-hospedado

Software de arquivo / Plataforma Windows Linux Mac
GNU Tar Obrigatório Obrigatório Não
Alcatrão BSD No Não Obrigatório
7-Zip Recomendado No Não

Os executáveis acima precisam estar em uma pasta listada na variável de ambiente PATH. Tenha em mente que os agentes hospedados vêm com o software incluído, isso só é aplicável para agentes auto-hospedados.

Exemplo:

Aqui está um exemplo de como usar chaves de restauração pelo 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

Neste exemplo, a tarefa de cache tenta localizar se a chave existe no cache. Se a chave não existir no cache, ele tentará usar a primeira chave yarn | $(Agent.OS)de restauração. Isso tentará procurar todas as chaves que correspondam exatamente a essa chave ou que tenham essa chave como um prefixo. Um acerto de prefixo pode acontecer se houver um segmento de hash diferente yarn.lock . Por exemplo, se a seguinte chave yarn | $(Agent.OS) | old-yarn.lock estava no cache onde o old-yarn.lock produziu um hash diferente do yarn.lock, a chave de restauração produzirá um acerto parcial. Se houver uma falha na primeira chave de restauração, ele usará a próxima chave yarn de restauração que tentará encontrar qualquer chave que comece com yarn. Para acertos de prefixo, o resultado produzirá a chave de cache criada mais recentemente como resultado.

Nota

Um pipeline pode ter uma ou mais tarefas de cache. Não há limite para a capacidade de armazenamento de cache, e trabalhos e tarefas do mesmo pipeline podem acessar e compartilhar o mesmo cache.

Isolamento e segurança do cache

Para garantir o isolamento entre caches de pipelines diferentes e ramificações diferentes, cada cache pertence a um contêiner lógico chamado escopo. Os escopos fornecem um limite de segurança que garante que um trabalho de um pipeline não possa acessar os caches de um pipeline diferente, e um trabalho que cria um PR tem acesso de leitura aos caches para a ramificação de destino do PR (para o mesmo pipeline), mas não pode gravar (criar) caches no escopo da ramificação de destino.

Quando uma etapa de cache é encontrada durante uma execução, o cache identificado pela chave é solicitado ao servidor. Em seguida, o servidor procura um cache com essa chave dos escopos visíveis para o trabalho e retorna o cache (se disponível). Ao salvar o cache (no final do trabalho), um cache é gravado no escopo que representa o pipeline e a ramificação. Consulte abaixo para mais detalhes

CI, execuções manuais e programadas

Âmbito Lida Escrita
Ramificação de origem Sim Sim
ramificação principal (ramificação padrão) Sim No

A solicitação pull é executada

Âmbito Lida Escrita
Ramificação de origem Sim No
Sucursal de destino Sim No
Sucursal intermédia (como refs/pull/1/merge) Sim Sim
ramificação principal (ramificação padrão) Sim No

A bifurcação de solicitação pull é executada

Filial Lida Escrita
Sucursal de destino Sim No
Sucursal intermédia (como refs/pull/1/merge) Sim Sim
ramificação principal (ramificação padrão) Sim No

Gorjeta

Como os caches já têm escopo para um projeto, pipeline e ramificação, não há necessidade de incluir nenhum projeto, pipeline ou identificadores de ramificação na chave de cache.

Condicionamento na restauração de cache

Em alguns cenários, a restauração bem-sucedida do cache deve fazer com que um conjunto diferente de etapas seja executado. Por exemplo, uma etapa que instala dependências pode ser ignorada se o cache foi restaurado. Isso é possível usando a entrada da cacheHitVar tarefa. Definir essa entrada como o nome de uma variável de ambiente fará com que a variável seja definida como true quando houver um acerto de cache, inexact em um acerto de cache de chave de restauração, caso contrário, ela será definida como false. Essa variável pode então ser referenciada em uma condição de etapa ou de dentro de um script.

No exemplo a seguir, a install-deps.sh etapa é ignorada quando o cache é restaurado:

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

Empacotador

Para projetos Ruby usando Bundler, substitua a BUNDLE_PATH variável de ambiente usada por Bundler para definir o caminho em que o Bundler procurará Gems.

Exemplo:

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 é um cache de compilador para C/C++. Para usar o Ccache em seu pipeline, verifique se Ccache está instalado e, opcionalmente, adicionado ao seu PATH (consulte Modos de execução do Ccache). Defina a CCACHE_DIR variável de ambiente como um caminho sob $(Pipeline.Workspace) e armazene em cache este diretório.

Exemplo:

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)"

Consulte Definições de configuração do Ccache para obter mais detalhes.

Imagens do Docker

O armazenamento em cache de imagens do Docker reduz drasticamente o tempo necessário para executar seu pipeline.

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: (required) - um identificador exclusivo para o cache.
  • path: (required) - caminho da pasta ou arquivo que você deseja armazenar em cache.

Golang

Para projetos Golang, você pode especificar os pacotes a serem baixados no arquivo go.mod . Se a GOCACHE variável ainda não estiver definida, defina-a para onde você deseja que o cache seja baixado.

Exemplo:

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

Usar o suporte de cache integrado do Gradle pode ter um impacto significativo no tempo de compilação. Para habilitar o cache de compilação, defina a variável de GRADLE_USER_HOME ambiente como um caminho abaixo $(Pipeline.Workspace) e execute sua compilação com --build-cache ou adicione org.gradle.caching=true ao seu gradle.properties arquivo.

Exemplo:

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: As chaves de fallback se a chave primária falhar (Opcional)

Nota

Os caches são imutáveis, uma vez que um cache com uma chave específica é criado para um escopo específico (ramificação), o cache não pode ser atualizado. Isso significa que, se a chave for um valor fixo, todas as compilações subsequentes para a mesma ramificação não poderão atualizar o cache, mesmo que o conteúdo do cache tenha sido alterado. Se quiser usar um valor de chave fixa, use o restoreKeys argumento como uma opção de fallback.

Maven

O Maven tem um repositório local onde armazena downloads e artefatos construídos. Para habilitar, defina a maven.repo.local opção como um caminho em $(Pipeline.Workspace) e armazene em cache esta pasta.

Exemplo:

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

Se você estiver usando uma tarefa Maven, certifique-se de passar também a MAVEN_OPTS variável porque ela será substituída caso contrário:

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

.NET/NuGet

Se você usar PackageReferences para gerenciar dependências do NuGet diretamente em seu arquivo de projeto e tiver um packages.lock.json arquivo, poderá habilitar o cache definindo a NUGET_PACKAGES variável de ambiente como um caminho sob $(UserProfile) e armazenando em cache esse diretório. Consulte Referência de pacote em arquivos de projeto para obter mais detalhes sobre como bloquear dependências. Se você quiser usar vários packages.lock.json, ainda poderá usar o exemplo a seguir sem fazer alterações. O conteúdo de todos os arquivos packages.lock.json será colocado em hash e, se um dos arquivos for alterado, uma nova chave de cache será gerada.

Exemplo:

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

Node.js/npm

Há diferentes maneiras de habilitar o cache em um projeto Node.js, mas a maneira recomendada é armazenar em cache o diretório de cache compartilhado do npm. Este diretório é gerenciado pelo npm e contém uma versão em cache de todos os módulos baixados. Durante a instalação, o npm verifica esse diretório primeiro (por padrão) em busca de módulos que possam reduzir ou eliminar chamadas de rede para o registro npm público ou para um registro privado.

Como o caminho padrão para o diretório de cache compartilhado do npm não é o mesmo em todas as plataformas, é recomendável substituir a npm_config_cache variável de ambiente por um caminho em $(Pipeline.Workspace). Isso também garante que o cache seja acessível a partir de trabalhos de contêiner e não contêiner.

Exemplo:

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

Se o seu projeto não tiver um package-lock.json arquivo, faça referência ao package.json arquivo na entrada da chave de cache.

Gorjeta

Como npm ci exclui a node_modules pasta para garantir que um conjunto consistente e repetível de módulos seja usado, você deve evitar o armazenamento em cache node_modules ao chamar npm ci.

Node.js/Fios

Como no npm, há diferentes maneiras de armazenar em cache pacotes instalados com o Yarn. A maneira recomendada é armazenar em cache a pasta de cache compartilhada do Yarn. Este diretório é gerenciado pelo Yarn e contém uma versão em cache de todos os pacotes baixados. Durante a instalação, o Yarn verifica esse diretório primeiro (por padrão) em busca de módulos, o que pode reduzir ou eliminar chamadas de rede para registros públicos ou privados.

Exemplo:

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

Configure seu cache de pipeline com ambientes Anaconda:

Exemplo

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/Compositor

Para projetos PHP usando o Composer, substitua a COMPOSER_CACHE_DIRvariável de ambiente usada pelo Composer.

Exemplo:

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

Problemas conhecidos e comentários

Se você estiver enfrentando problemas para configurar o cache para seu pipeline, verifique a lista de problemas abertos no microsoft/azure-pipelines-tasks repositório. Se não vir o problema listado, crie um novo problema e forneça as informações necessárias sobre o seu cenário.

Q&A

P: Posso limpar um cache?

R: No momento, não há suporte para limpar um cache. No entanto, você pode adicionar um literal de cadeia de caracteres (como version2) à sua chave de cache existente para alterar a chave de uma forma que evite quaisquer acertos em caches existentes. Por exemplo, altere a seguinte chave de cache a partir disso:

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

para este:

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

P: Quando um cache expira?

R: Os caches expiram após sete dias sem atividade.

P: Quando é que a cache é carregada?

R: Após a última etapa do seu pipeline, um cache será criado a partir do seu cache path e carregado. Veja o exemplo para obter mais detalhes.

P: Existe um limite para o tamanho de um cache?

R: Não há limite imposto para o tamanho de caches individuais ou o tamanho total de todos os caches em uma organização.