Armazenamento em cache remoto

Informar um problema Ver código-fonte

Nesta página, abordamos o armazenamento em cache remoto, a configuração de um servidor para hospedar o cache e a execução de versões usando o cache remoto.

Um cache remoto é usado por uma equipe de desenvolvedores e/ou um sistema de integração contínua (CI, na sigla em inglês) para compartilhar saídas de versão. Se a versão puder ser reproduzida, as saídas de uma máquina poderão ser reutilizadas com segurança em outra, o que poderá tornar as criações significativamente mais rápidas.

Informações gerais

O Bazel divide uma versão em etapas discretas, chamadas de ações. Cada ação tem entradas, nomes de saída, uma linha de comando e variáveis de ambiente. As entradas necessárias e as saídas esperadas são declaradas explicitamente para cada ação.

É possível configurar um servidor para ser um cache remoto para saídas de compilação, que são essas saídas de ação. Essas saídas consistem em uma lista de nomes de arquivos de saída e os hashes do conteúdo deles. Com um cache remoto, é possível reutilizar as saídas de compilação a partir da compilação de outro usuário em vez de compilar cada nova saída localmente.

Para usar o armazenamento em cache remoto:

  • Configurar um servidor como back-end do cache
  • Configurar a versão do Bazel para usar o cache remoto
  • Usar o Bazel versão 0.10.0 ou posterior

O cache remoto armazena dois tipos de dados:

  • O cache de ação, que é um mapa de hashes de ação para metadados de resultado da ação.
  • Um armazenamento de arquivos de saída (CAS) de conteúdo endereçável.

Observe que o cache remoto também armazena stdout e stderr para todas as ações. Inspecionar a stdout/stderr do Bazel, portanto, não é um bom sinal para estimar ocorrências em cache.

Como uma versão usa o armazenamento em cache remoto

Depois que um servidor é configurado como o cache remoto, o cache é usado de várias maneiras:

  • Ler e gravar no cache remoto
  • Ler e/ou gravar no cache remoto, exceto destinos específicos
  • Ler somente a partir do cache remoto
  • Não usar o cache remoto

Quando você executa uma versão do Bazel que pode ler e gravar no cache remoto, ela segue estas etapas:

  1. O Bazel cria o gráfico de destinos que precisam ser criados e, em seguida, cria uma lista de ações necessárias. Cada uma dessas ações declarou entradas e nomes de arquivos de saída.
  2. O Bazel verifica sua máquina local em busca de saídas de compilação existentes e reutiliza qualquer que for encontrado.
  3. O Bazel verifica o cache quanto a saídas de compilação existentes. Se a saída for encontrada, o Bazel a recuperará. Essa é uma ocorrência em cache.
  4. Para as ações necessárias em que as saídas não foram encontradas, o Bazel executa as ações localmente e cria as saídas de compilação necessárias.
  5. Novas saídas de compilação são enviadas para o cache remoto.

Como configurar um servidor como back-end do cache

Você precisa configurar um servidor para atuar como o back-end do cache. Um servidor HTTP/1.1 pode tratar os dados do Bazel como bytes opacos e, portanto, muitos servidores existentes podem ser usados como back-end de armazenamento em cache remoto. O protocolo de armazenamento em cache HTTP do Bazel é compatível com o armazenamento em cache remoto.

Você é responsável por escolher, configurar e manter o servidor de back-end que armazenará as saídas armazenadas em cache. Ao escolher um servidor, considere:

  • Velocidade de rede. Por exemplo, se sua equipe estiver no mesmo escritório, convém executar seu próprio servidor local.
  • Segurança. O cache remoto terá seus binários e, portanto, precisa ser seguro.
  • Facilidade de gerenciamento. Por exemplo, o Google Cloud Storage é um serviço totalmente gerenciado.

Há muitos back-ends que podem ser usados para um cache remoto. Veja algumas opções:

nginx

O nginx é um servidor da Web de código aberto. Com o [módulo WebDAV], ele pode ser usado como um cache remoto para o Bazel. No Debian e no Ubuntu, é possível instalar o pacote nginx-extras. No macOS, o nginx está disponível no Homebrew:

brew tap denji/nginx
brew install nginx-full --with-webdav

Veja abaixo um exemplo de configuração do nginx. Altere /path/to/cache/dir para um diretório válido em que o nginx tenha permissão para gravar e ler. Poderá ser necessário alterar a opção client_max_body_size para um valor maior se você tiver arquivos de saída maiores. O servidor exigirá outras configurações, como autenticação.

Exemplo de configuração para a seção server em nginx.conf:

location /cache/ {
  # The path to the directory where nginx should store the cache contents.
  root /path/to/cache/dir;
  # Allow PUT
  dav_methods PUT;
  # Allow nginx to create the /ac and /cas subdirectories.
  create_full_put_path on;
  # The maximum size of a single file.
  client_max_body_size 1G;
  allow all;
}

Bazel-remote

O bazel-remote é um cache de compilação remota de código aberto que pode ser usado na infraestrutura. Ele tem sido usado com sucesso em produção em várias empresas desde o início de 2018. O projeto Bazel não fornece suporte técnico para o bazel-remote.

Esse cache armazena conteúdo no disco e também fornece a coleta de lixo para impor um limite de armazenamento superior e limpar os artefatos não utilizados. O cache está disponível como uma [imagem do Docker], e o código está disponível no GitHub. As APIs de cache remoto REST e gRPC são compatíveis.

Consulte a página do GitHub para instruções sobre como usá-la.

Google Cloud Storage

O [Google Cloud Storage] é um armazenamento de objetos totalmente gerenciado que fornece uma API HTTP compatível com o protocolo de armazenamento em cache remoto do Bazel. É necessário ter uma conta do Google Cloud com o faturamento ativado.

Para usar o Cloud Storage como o cache:

  1. Crie um bucket de armazenamento. Certifique-se de selecionar o local do bucket mais próximo, já que a largura de banda da rede é importante para o cache remoto.

  2. Crie uma conta de serviço para o Bazel se autenticar no Cloud Storage. Consulte Como criar uma conta de serviço.

  3. Gere uma chave JSON secreta e envie-a ao Bazel para autenticação. Armazene a chave com segurança, já que qualquer pessoa com ela pode ler e gravar dados arbitrários no bucket do GCS e a partir dele.

  4. Conecte-se ao Cloud Storage adicionando as seguintes sinalizações ao comando do Bazel:

    • Transmita o URL a seguir para o Bazel usando a sinalização --remote_cache=https://storage.googleapis.com/bucket-name, em que bucket-name é o nome do bucket de armazenamento.
    • Transmita a chave de autenticação usando a sinalização --google_credentials=/path/to/your/secret-key.json ou --google_default_credentials para usar a Autenticação de aplicativos.
  5. É possível configurar o Cloud Storage para excluir automaticamente arquivos antigos. Para fazer isso, consulte Como gerenciar ciclos de vida de objetos.

Outros servidores

É possível configurar qualquer servidor HTTP/1.1 compatível com PUT e GET como o back-end do cache. Os usuários relataram sucesso com back-ends de cache, como Hazelcast, Apache httpd e AWS S3.

Autenticação

A partir da versão 0.11.0, a compatibilidade com HTTP Basic foi adicionada ao Bazel. É possível passar um nome de usuário e uma senha para o Bazel por meio do URL de cache remoto. A sintaxe é https://username:password@hostname.com:port/path. Observe que a Autenticação básica HTTP transmite o nome de usuário e a senha em texto simples pela rede e, portanto, é essencial usá-la sempre com HTTPS.

Protocolo de cache HTTP

O Bazel é compatível com o armazenamento em cache remoto via HTTP/1.1. O protocolo é conceitualmente simples: os dados binários (BLOB) são enviados por meio de solicitações PUT e baixados por meio de solicitações GET. Os metadados de resultados da ação são armazenados no caminho /ac/ e os arquivos de saída são armazenados no caminho /cas/.

Por exemplo, considere um cache remoto em execução em http://localhost:8080/cache. Uma solicitação do Bazel para fazer o download dos metadados do resultado de uma ação com o hash SHA256 01ba4719... ficará assim:

GET /cache/ac/01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b HTTP/1.1
Host: localhost:8080
Accept: */*
Connection: Keep-Alive

Uma solicitação do Bazel para fazer upload de um arquivo de saída com o hash SHA256 15e2b0d3... para o CAS será semelhante a:

PUT /cache/cas/15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225 HTTP/1.1
Host: localhost:8080
Accept: */*
Content-Length: 9
Connection: Keep-Alive

0x310x320x330x340x350x360x370x380x39

Executar o Bazel usando o cache remoto

Depois que um servidor estiver configurado como o cache remoto, será necessário adicionar sinalizações ao comando do Bazel para usá-lo. Veja abaixo a lista de configurações e as sinalizações delas.

Talvez também seja necessário configurar a autenticação, que é específica do servidor escolhido.

Você pode adicionar essas sinalizações a um arquivo .bazelrc para não precisar especificá-las sempre que executar o Bazel. Dependendo da dinâmica do projeto e da equipe, é possível adicionar sinalizações a um arquivo .bazelrc que seja:

  • Na máquina local
  • No espaço de trabalho do seu projeto, compartilhado com a equipe
  • No sistema de CI

Ler e gravar no cache remoto

Tome cuidado com quem tem a capacidade de gravar no cache remoto. Talvez você queira que apenas o sistema de CI seja capaz de gravar no cache remoto.

Use a seguinte sinalização para ler e gravar no cache remoto:

build --remote_cache=http://your.host:port

Além de HTTP, os seguintes protocolos também são compatíveis: HTTPS, grpc, grpcs.

Use a sinalização a seguir, além da sinalização acima, para ler somente no cache remoto:

build --remote_upload_local_results=false

Impedir que destinos específicos usem o cache remoto

Para impedir que destinos específicos usem o cache remoto, marque o destino com no-remote-cache. Por exemplo:

java_library(
    name = "target",
    tags = ["no-remote-cache"],
)

Excluir conteúdo do cache remoto

A exclusão de conteúdo do cache remoto faz parte do gerenciamento do servidor. A maneira como você exclui conteúdo do cache remoto depende do servidor que você configurou como cache. Ao excluir saídas, exclua todo o cache ou exclua saídas antigas.

As saídas armazenadas em cache são armazenadas como um conjunto de nomes e hashes. Ao excluir conteúdo, não há como distinguir qual saída pertence a um build específico.

Você pode excluir o conteúdo do cache para:

  • Criar um cache limpo depois que um cache foi envenenado
  • Reduza a quantidade de armazenamento usada excluindo saídas antigas

Soquetes Unix

O cache HTTP remoto permite a conexão por soquetes de domínio Unix. O comportamento é semelhante à sinalização --unix-socket do curl. Use o código a seguir para configurar o soquete de domínio Unix:

   build --remote_cache=http://your.host:port
   build --remote_cache_proxy=unix:/path/to/socket

Esse recurso não é compatível com o Windows.

Cache em disco

O Bazel pode usar um diretório no sistema de arquivos como um cache remoto. Isso é útil para compartilhar artefatos de versão ao alternar ramificações e/ou trabalhar em vários espaços de trabalho do mesmo projeto, como várias finalizaçãos de compra. Como o Bazel não coleta o diretório, é recomendável automatizar uma limpeza periódica dele. Ative o cache de disco da seguinte maneira:

build --disk_cache=path/to/build/cache

É possível transmitir um caminho específico do usuário para a sinalização --disk_cache usando o alias ~. O Bazel substitui o diretório inicial do usuário atual. Isso é útil ao ativar o cache de disco para todos os desenvolvedores de um projeto por meio do arquivo .bazelrc verificado no projeto.

Problemas conhecidos

Modificação do arquivo de entrada durante uma compilação

Quando um arquivo de entrada é modificado durante uma compilação, o Bazel pode fazer upload de resultados inválidos para o cache remoto. É possível ativar uma detecção de alterações com a sinalização --experimental_guard_against_concurrent_changes. Não há problemas conhecidos e ele será ativado por padrão em uma versão futura. Consulte [problema 3360] para obter atualizações. Em geral, evite modificar arquivos de origem durante uma versão.

Variáveis de ambiente vazando em uma ação

Uma definição de ação contém variáveis de ambiente. Isso pode ser um problema para compartilhar ocorrências em cache remota entre as máquinas. Por exemplo, ambientes com variáveis $PATH diferentes não compartilham ocorrências em cache. Somente as variáveis de ambiente explicitamente incluídas na lista de permissões por meio de --action_env são incluídas em uma definição de ação. O pacote Debian/Ubuntu do Bazel usado para instalar /etc/bazel.bazelrc com uma lista de permissões de variáveis de ambiente, incluindo $PATH. Se você estiver recebendo menos ocorrências em cache do que o esperado, verifique se o ambiente não tem um arquivo /etc/bazel.bazelrc antigo.

O Bazel não rastreia ferramentas fora de um espaço de trabalho

No momento, o Bazel não rastreia ferramentas fora de um espaço de trabalho. Isso pode ser um problema se, por exemplo, uma ação usar um compilador de /usr/bin/. Em seguida, dois usuários com diferentes compiladores instalados compartilharão erroneamente as ocorrências em cache, porque as saídas são diferentes, mas têm o mesmo hash de ação. Consulte o problema 4558 para atualizações.

O estado incremental na memória é perdido ao executar versões dentro de contêineres do Docker. O Bazel usa a arquitetura de servidor/cliente mesmo quando executado em um único contêiner do Docker. No lado do servidor, o Bazel mantém um estado na memória que acelera as compilações. Ao executar versões dentro de contêineres do Docker, como em CI, o estado na memória é perdido e o Bazel precisa recriá-lo antes de usar o cache remoto.