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.
Observação
O armazenamento em cache de Pipeline tem suporte em trabalhos de pool de agentes para pipelines YAML e Clássicos. No entanto, não há suporte para isso em pipelines de lançamento 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, 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 |
Ramificação main |
Sim | No |
Ramificação master |
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 |
Ramificação main |
Sim | No |
Ramificação master |
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 |
Ramificação main |
Sim | No |
Ramificação master |
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_DIR
variá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.
P e R
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 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 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.