Cache de pipeline

Azure DevOps Services

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

O cache pode ser eficaz em aprimorar o tempo de build, 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 build.

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 os 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 aprimorar o tempo de build reutilizando arquivos de execuções anteriores (e a falta desses arquivos não afetará a capacidade da tarefa de ser executada).

Observação

O cache de pipeline e os artefatos de pipeline são gratuitos para todas as camadas (gratuitas e pagas). Confira Consumo de armazenamento de artefatos para obter mais detalhes.

Tarefa Cache: como ela funciona

O cache é adicionado a um pipeline usando a tarefa Cache. Essa tarefa funciona como qualquer outra tarefa e é adicionada à seção steps 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 no trabalho tiverem sido executadas e presumindo que um status de trabalho bem-sucedido seja obtido, uma etapa especial "Pós-trabalho: Cache" será adicionada e disparada automaticamente para cada etapa de "restaurar cache" que não tiver sido ignorada. Esta etapa é responsável por salvar o cache.

Observação

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

Configurar a tarefa Cache

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

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

Observação

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

  • key: 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, em que cada segmento é separado por um caractere |.
  • Cadeias de caracteres:
    Valor fixo (como o nome do cache ou um nome de ferramenta) ou obtido de uma variável de ambiente (como o sistema operacional atual ou o nome do trabalho atual)

  • Caminhos de arquivo:
    Caminho para um arquivo específico cujo conteúdo será submetido a hash. Esse arquivo precisa existir no momento em que a tarefa é executada. Tenha em mente que qualquer segmento de chave que "se parece com um caminho de arquivo" será tratado como um caminho de arquivo. Em particular, isso inclui segmentos que contêm um .. Isso poderá resultar na falha da tarefa quando esse "arquivo" não existir.

    Dica

    Para evitar que um segmento de cadeia de caracteres semelhante a um caminho seja tratado como um caminho de arquivo, coloque-o entre 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 precisa corresponder a pelo menos um arquivo. Por exemplo:

    • **/yarn.lock: todos os arquivos yarn.lock no diretório de fontes
    • */asset.json, !bin/**: todos os arquivos asset.json localizados em um diretório no diretório de fontes, exceto no diretório bin

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

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

Exemplo:

Veja 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, pois esse cache é exclusivo por sistema operacional e o hash do arquivo yarn.lock que identifica exclusivamente o conjunto de dependências no cache.

Na primeira execução após a adição da tarefa, a etapa de cache relatará uma "falha de cache", pois o cache identificado por essa chave não existe. Após a última etapa, um cache será criado com base nos arquivos em $(Pipeline.Workspace)/s/.yarn e carregado. Na próxima execução, a etapa de cache relatará uma "ocorrência no cache" e o conteúdo do cache será baixado e restaurado.

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

Observação

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

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

Chaves de restauração

restoreKeys pode ser usado se você desejar consultar várias chaves exatas ou prefixos de chave. Isso é usado para fazer fallback para outra chave no caso de um key não produzir uma ocorrência. Uma chave de restauração procurará uma chave por prefixo e produzirá a entrada de cache criada mais recente como resultado. Isso será útil se o pipeline não conseguir encontrar uma correspondência exata, mas quiser usar uma ocorrência parcial no cache. Para inserir várias chaves de restauração, simplesmente delimita-as usando uma nova linha para indicar a chave de restauração (consulte o exemplo para obter mais detalhes). A ordem em que as chaves de restauração serão testadas será de cima para baixo.

Software necessário no agente auto-hospedado

Plataforma/software de arquivo morto Windows Linux Mac
GNU Tar Obrigatório Obrigatório No
BSD Tar Não Não Obrigatório
7-Zip Recomendadas Não 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 a 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, ela tentará usar a primeira chave de restauração, yarn | $(Agent.OS). Isso tentará pesquisar todas as chaves que correspondem exatamente a essa chave ou que têm essa chave como um prefixo. Uma ocorrência de prefixo pode acontecer se tiver existido um segmento de hash yarn.lock diferente. Por exemplo, se a seguinte chave yarn | $(Agent.OS) | old-yarn.lock estava no cache em que old-yarn.lock produziu um hash diferente de yarn.lock, a chave de restauração produzirá um resultado parcial. Se houver uma não correspondência na primeira chave de restauração, ela usará a próxima chave de restauração yarn, que tentará encontrar qualquer chave que comece com yarn. Para ocorrências de prefixo, o resultado produzirá como resultado a chave de cache criada mais recentemente.

Observação

Um pipeline pode ter uma ou mais tarefas de cache. Não há limite para a capacidade de armazenamento em 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 branches 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 uma solicitação de pull tenha acesso de leitura aos caches do branch de destino da solicitação de pull (para o mesmo pipeline), mas não possa gravar (criar) caches no escopo do branch de destino.

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

execuções agendadas, manuais e de CI

Escopo Ler Gravar
Branch de origem Sim Sim
branch principal (branch padrão) Sim Não

Execuções de solicitação de pull

Escopo Ler Gravar
Branch de origem Sim Não
Branch de destino Sim Não
Branch intermediário (como refs/pull/1/merge) Sim Sim
branch principal (branch padrão) Sim Não

Execuções de fork de solicitação de pull

Branch Ler Gravar
Branch de destino Sim Não
Branch intermediário (como refs/pull/1/merge) Sim Sim
branch principal (branch padrão) Sim No

Dica

Como os caches já estão no escopo de um projeto, pipeline e branch, não é necessário incluir nenhum identificador de projeto, pipeline ou branch na chave de cache.

Condicionamento na restauração do 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 poderá ser ignorada se o cache tiver sido restaurado. Isso é possível usando a entrada da tarefa cacheHitVar. Definir essa entrada como o nome de uma variável de ambiente fará com que a variável seja definida como true quando houver uma ocorrência no cache, inexact em uma ocorrência no cache de chave de restauração, caso contrário, ela será definida como false. Essa variável pode ser referenciada em uma condição de etapa ou de dentro de um script.

No seguinte exemplo, a etapa install-deps.sh é 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

bundler

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

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

O Ccache é um cache do compilador para C/C++. Para usar o Ccache no pipeline, verifique se Ccache está instalado e, opcionalmente, adicionado ao seu PATH (consulte Modos de execução do Ccache). Defina a variável de ambiente CCACHE_DIR como um caminho em $(Pipeline.Workspace) e armazene esse diretório em cache.

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

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

Docker images

Armazenar imagens do Docker em cache reduz drasticamente o tempo necessário para execução do 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 (obrigatório): um identificador exclusivo para a entrada de cache.
  • path (obrigatório): o 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 variável GOCACHE ainda não estiver definida, defina-a como 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

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

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)

Observação

Os caches são imutáveis. Uma vez que um cache com uma chave específica é criado para um escopo específico (branch), o cache não pode ser atualizado. Isso significa que, se a chave for um valor fixo, todos os builds subsequentes para o mesmo branch não poderão atualizar o cache mesmo que o conteúdo do cache tenha sido alterado. Se você quiser usar um valor de chave fixo, precisará usar o argumento restoreKeys como uma opção de fallback.

Maven

O Maven tem um repositório local onde armazena downloads e artefatos criados. Para habilitá-lo, defina a opção maven.repo.local como um caminho em $(Pipeline.Workspace) e armazene essa pasta em cache.

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 do Maven, certifique-se de também passar a variável MAVEN_OPTS, porque se não o fizer, ela será substituída:

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

.NET/NuGet

Se você usar PackageReferences para gerenciar dependências do NuGet diretamente no arquivo de projeto e tiver um arquivo packages.lock.json, poderá habilitar o cache definindo a variável de ambiente NUGET_PACKAGES como um caminho em $(UserProfile) e armazenando esse diretório em cache. Confira 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 nenhuma alteração. O conteúdo de todos os arquivos packages.lock.json sofrerá 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á maneiras diferentes de habilitar o cache em um projeto Node.js, mas a maneira recomendada é armazenar em cache o diretório de cache compartilhado do npm. Esse diretório é gerenciado pelo npm e contém uma versão armazenada 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 variável de ambiente npm_config_cache por um caminho em $(Pipeline.Workspace). Isso também garante que o cache esteja acessível em 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 projeto não tiver um arquivo package-lock.json, faça referência ao arquivo package.json na entrada da chave de cache.

Dica

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

Node.js/Yarn

Assim 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 compartilhado do Yarn. Esse diretório é gerenciado pelo Yarne contém uma versão armazenada em cache de todos os pacotes baixados. Durante a instalação, o Yarn procura módulos nesse diretório primeiro (por padrão), 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 o cache de pipeline com ambientes do 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/Composer

Para projetos PHP que usam 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 ao configurar o cache para seu pipeline, verifique a lista de problemas pendentes no repositório microsoft/azure-pipelines-tasks. Se você não vir seu problema listado, crie um novo e forneça as informações necessárias sobre seu cenário.

Perguntas e respostas

P: Posso limpar um cache?

R: Atualmente, não há suporte para limpar um cache. No entanto, você pode adicionar um literal de cadeia de caracteres (como version2) à chave de cache existente para alterar a chave de uma maneira que evite ocorrências em caches existentes. Por exemplo, altere a seguinte chave de cache disto:

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

para isto:

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

P: Quando um cache expira?

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

P: Quando o cache é carregado?

R: Após a última etapa do pipeline, um cache será criado com base no cache path e carregado. Confira o exemplo para obter mais detalhes.

P: Há um limite para o tamanho de um cache?

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