Partilhar via


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.

Observação

A cache de pipeline não é suportada em pipelines de lançamento clássicos.

Quando usar artefatos versus armazenamento em cache

Cache de pipeline e artefatos de pipeline executam funções semelhantes, mas são projetados para cenários distintos 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).

Observação

O cache de pipeline e os artefatos de pipeline são gratuitos para todos os níveis (gratuitos e pagos). Consulte o 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 à secçã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 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"restaurar cache" que não foi ignorada. Esta etapa é responsável por salvar o cache.

Observação

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

Configurar a tarefa Cache

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

  • caminho: o caminho da pasta para armazenamento em cache. Pode ser um caminho absoluto ou relativo. Os 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 guardar em cache; no entanto, curingas não são suportados.

  • chave: deve ser definida 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 ficheiro específico cujo conteúdo será submetido a 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.

    Dica

    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 sources
    • */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 arquivos que identificam exclusivamente o que está sendo armazenado em cache. Por exemplo, arquivos como package-lock.json, yarn.lock, Gemfile.lockou Pipfile.lock são comumente referenciados em uma chave de cache, pois todos 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:

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 yarn.lock que identifica exclusivamente o 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 em $(Pipeline.Workspace)/s/.yarn e 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: self, o repositório é colocado em $(Pipeline.Workspace)/s, e a sua pasta .yarn geralmente reside no próprio repositório.

Observação

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, pois 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 recorrer a outra chave caso um key não resulte num sucesso. Uma chave de restauração procura uma chave por prefixo e produz a entrada de cache mais recente criada como resultado. Isto é útil se o pipeline não conseguir encontrar uma correspondência exata, mas optar por utilizar um resultado parcial de cache. Para inserir várias chaves de restauração, delimite-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 tentadas será de cima para baixo.

Software necessário no agente auto-hospedado

Software de arquivo / Plataforma Windows Linux Mac
GNU Tar Necessário Necessário Não
Alcatrão BSD Não Não Necessário
7-Zip Recomendado 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 para agentes auto-hospedados.

Exemplo:

Eis um exemplo de como usar chaves de restauração com o 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 de restauração yarn | $(Agent.OS). Isso tenta procurar todas as chaves que correspondem exatamente a essa chave ou que tem essa chave como um prefixo. Um acerto de prefixo pode acontecer se houver um segmento de hash de yarn.lock diferente. Por exemplo, se a seguinte chave yarn | $(Agent.OS) | old-yarn.lock estivesse no cache onde o old-yarn.lock produziu um hash diferente do yarn.lock, a chave de restauração produziria um acerto parcial. Se houver uma falha na primeira chave de restauração, ele usará a próxima chave de restauração yarn que tentará encontrar qualquer chave que comece com yarn. Nos acertos de prefixo, o 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 diferentes pipelines e diferentes ramificações, cada cache pertence a um contentor lógico chamado escopo. Os escopos fornecem um limite de segurança que garante:

  1. Um trabalho de um pipeline não pode acessar os caches de um pipeline diferente, e
  2. Um trabalho de criação de uma RP tem acesso de leitura aos caches da ramificação de destino da RP (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 âmbitos visíveis para a tarefa 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. Veja abaixo mais detalhes.

CI, execuções manuais e programadas

Âmbito de aplicação Ler Escrever
Ramificação de origem Sim Sim
main filial Sim Não
master sucursal Sim Não

A solicitação pull é executada

Âmbito de aplicação Ler Escrever
Ramificação de origem Sim Não
Sucursal de destino Sim Não
Ramo intermediário (como refs/pull/1/merge) Sim Sim
main sucursal Sim Não
master filial Sim Não

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

Sucursal Ler Escrever
Sucursal de destino Sim Não
Ramo intermediário (como refs/pull/1/merge) Sim Sim
main sucursal Sim Não
master filial Sim Não

Dica

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

Condicionamento na recuperaçã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 de tarefa cacheHitVar. Definir essa entrada como o nome de uma variável de ambiente faz com que a variável seja definida como true quando há um acerto de cache, inexact em um acerto de cache de chave de restauração, caso contrário, ela é definida como false. Esta variável pode então ser referenciada numa condição de etapa ou a partir de um script.

No exemplo a seguir, 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

Empacotador

Para projetos Ruby usando Bundler, substitua a variável de ambiente BUNDLE_PATH usada pelo Bundler para definir o caminho de em que o Bundler procura 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 variável de ambiente CCACHE_DIR como um caminho em $(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 as 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'))
  • chave: (obrigatório) - um identificador exclusivo para o cache.
  • caminho: (obrigatório) - 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 para onde 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 de cache integrado da Gradle pode ter um impacto significativo no tempo de compilação. Para habilitar o cache de compilação, defina a variável de ambiente GRADLE_USER_HOME como um caminho em $(Pipeline.Workspace) e execute sua compilação com --build-cache ou adicione org.gradle.caching=true ao seu 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 reserva 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 (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 argumento restoreKeys 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 opção maven.repo.local como um caminho em $(Pipeline.Workspace) e faça o cache desta 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 do Maven, certifique-se de passar também a variável MAVEN_OPTS porque ela será substituída de outra forma:

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

.NET/NuGet

Se você usar o PackageReferences para gerenciar dependências do NuGet diretamente em seu 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 em cache esse diretório. Consulte a referência de pacote nos ficheiros 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

Essa abordagem também é válida para projetos .NET Core se seu projeto usa packages.lock.json para bloquear versões de pacote. Você pode habilitar isso definindo para no arquivo de Csproj do ou usando o seguinte comando: .

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 variável de ambiente npm_config_cache por um caminho em $(Pipeline.Workspace). Isso também garante que o cache seja acessível a partir de tarefas em contêineres e fora deles.

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 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 armazenar node_modules em cache ao chamar npm ci.

Node.js/Fio

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 variável de ambiente COMPOSER_CACHE_DIR 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 estiver a enfrentar problemas ao configurar o armazenamento em cache para o seu pipeline, verifique a lista de problemas abertos no repositório microsoft/azure-pipelines-tasks. Caso não veja o problema listado, crie um novo 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 forma a evitar quaisquer acertos em caches existentes. Por exemplo, altere a seguinte chave de cache a partir disso:

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

Para isso:

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 o último passo do seu pipeline, um cache será criado a partir do cache path e enviado. Consulte 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.