Sandbox

Informar um problema Acessar fonte

Este artigo aborda o sandbox no Bazel e a depuração do ambiente de sandbox.

O sandbox é uma estratégia de restrição de permissões que isola os processos uns dos outros ou dos recursos em um sistema. Para o Bazel, isso significa restringir o acesso ao sistema de arquivos.

O sandbox do sistema de arquivos do Bazel executa processos em um diretório de trabalho que contém apenas entradas conhecidas. Assim, os compiladores e outras ferramentas não veem os arquivos de origem que não podem acessar, a menos que saibam os caminhos absolutos para eles.

O sandbox não oculta o ambiente do host de forma alguma. Os processos podem acessar livremente todos os arquivos no sistema de arquivos. No entanto, em plataformas compatíveis com namespaces de usuários, os processos não podem modificar arquivos fora do diretório de trabalho. Isso garante que o gráfico do build não tenha dependências ocultas que possam afetar a reprodutibilidade do build.

Mais especificamente, o Bazel constrói um diretório execroot/ para cada ação, que atua como o diretório de trabalho da ação no momento da execução. execroot/ contém todos os arquivos de entrada para a ação e serve como contêiner para todas as saídas geradas. Em seguida, o Bazel usa uma técnica fornecida pelo sistema operacional, contêineres no Linux e sandbox-exec no macOS, para restringir a ação em execroot/.

Motivos para colocar no sandbox

  • Sem o sandbox de ações, o Bazel não sabe se uma ferramenta usa arquivos de entrada não declarados, ou seja, arquivos que não estão explicitamente listados nas dependências de uma ação. Quando um dos arquivos de entrada não declarados muda, o Bazel ainda acredita que o build está atualizado e não vai recriar a ação. Isso pode resultar em um build incremental incorreto.

  • A reutilização incorreta de entradas de cache cria problemas durante o armazenamento em cache remoto. Uma entrada de cache inválido em um cache compartilhado afeta todos os desenvolvedores no projeto e excluir todo o cache remoto não é uma solução viável.

  • O sandbox imita o comportamento da execução remota. Se uma compilação funcionar bem com sandbox, ela provavelmente também funcionará com a execução remota. Ao fazer upload de todos os arquivos necessários da execução remota, incluindo as ferramentas locais, é possível reduzir significativamente os custos de manutenção para compilar clusters em comparação com a necessidade de instalar as ferramentas em todas as máquinas do cluster sempre que você quiser testar um novo compilador ou fazer uma alteração em uma ferramenta existente.

Qual estratégia de sandbox usar

É possível escolher qual tipo de sandbox usar, se houver, com as sinalizações de estratégia. O uso da estratégia sandboxed faz com que o Bazel escolha uma das implementações de sandbox listadas abaixo, priorizando um sandbox específico do SO em vez do genérico, menos hermético. Os workers permanentes serão executados em um sandbox genérico se você transmitir a sinalização --worker_sandboxing.

A estratégia local (também conhecida como standalone) não faz nenhum tipo de sandbox. Ele simplesmente executa a linha de comando da ação com o diretório de trabalho definido como o "execroot" do espaço de trabalho.

processwrapper-sandbox é uma estratégia de sandbox que não requer recursos "avançados". Ele funciona em qualquer sistema POSIX pronto para uso. Ele cria um diretório de sandbox que consiste em links simbólicos que apontam para os arquivos de origem originais, executa a linha de comando da ação com o diretório de trabalho definido para esse diretório em vez de execroot, depois move os artefatos de saída conhecidos para fora do sandbox para a execroot e exclui o sandbox. Isso evita que a ação use acidentalmente arquivos de entrada que não tenham sido declarados e desperdilhe o execroot com arquivos de saída desconhecidos.

O linux-sandbox vai um passo além e se baseia no processwrapper-sandbox. Semelhante ao que o Docker faz em segundo plano, ele usa namespaces do Linux (namespaces de usuário, montagem, PID, rede e IPC) para isolar a ação do host. Ou seja, ele torna todo o sistema de arquivos somente leitura, exceto o diretório do sandbox, de modo que a ação não possa modificar acidentalmente nada no sistema de arquivos do host. Isso evita situações como um teste com bugs acidentalmente rm -rf'ing seu diretório $HOME. Também é possível impedir que a ação acesse a rede. O linux-sandbox usa namespaces PID para impedir que a ação veja qualquer outro processo e para encerrar de maneira confiável todos os processos, até mesmo os daemons gerados pela ação.

O darwin-sandbox é semelhante, mas para o macOS. Ele usa a ferramenta sandbox-exec da Apple para realizar praticamente o mesmo que o sandbox do Linux.

Tanto linux-sandbox quanto darwin-sandbox não funcionam em um cenário "aninhado" devido a restrições nos mecanismos fornecidos pelos sistemas operacionais. Como o Docker também usa namespaces do Linux para a mágica de contêiner, não é possível executar facilmente linux-sandbox dentro de um contêiner do Docker, a menos que você use docker run --privileged. No macOS, não é possível executar sandbox-exec dentro de um processo que já está no sandbox. Nesses casos, o Bazel volta automaticamente a usar processwrapper-sandbox.

Se preferir receber um erro de build, como não criar acidentalmente com uma estratégia de execução menos rigorosa, modifique explicitamente a lista de estratégias de execução que o Bazel tenta usar (por exemplo, bazel build --spawn_strategy=worker,linux-sandbox).

A execução dinâmica geralmente requer sandbox para execução local. Para desativar, transmita a flag --experimental_local_lockfree_output. Sandboxes de execução dinâmica são trabalhadores persistentes de forma silenciosa.

Desvantagens do sandbox

  • O sandbox tem um custo adicional de configuração e desmontagem. O tamanho desse custo depende de muitos fatores, incluindo o formato do build e o desempenho do SO host. Para Linux, as versões em sandbox raramente são um pouco mais lentas. Definir --reuse_sandbox_directories pode reduzir o custo de configuração e eliminação.

  • O sandbox desativa efetivamente qualquer cache que a ferramenta possa ter. É possível atenuar isso com workers permanentes ao custo de garantias de sandbox mais fracas.

  • Os workers multiplex exigem suporte explícito dos workers para serem colocados no sandbox. Workers que não são compatíveis com sandbox multiplex são executados como workers singleton em execução dinâmica, o que pode custar memória extra.

Depuração

Siga as estratégias abaixo para depurar problemas com o sandbox.

Namespaces desativados

Em algumas plataformas, como os nós de cluster do Google Kubernetes Engine ou o Debian, os namespaces de usuário são desativados por padrão devido a questões de segurança. Se o arquivo /proc/sys/kernel/unprivileged_userns_clone existir e contiver um 0, ative os namespaces do usuário executando:

   sudo sysctl kernel.unprivileged_userns_clone=1

Falhas na execução de regras

O sandbox pode falhar ao executar regras devido à configuração do sistema. Se for exibida uma mensagem como namespace-sandbox.c:633: execvp(argv[0], argv): No such file or directory, tente desativar o sandbox com --strategy=Genrule=local para genrules e --spawn_strategy=local para outras regras.

Depuração detalhada de falhas de compilação

Se o build falhar, use --verbose_failures e --sandbox_debug para fazer o Bazel mostrar o comando exato executado quando o build falhou, incluindo a parte que configura o sandbox.

Exemplo de mensagem de erro:

ERROR: path/to/your/project/BUILD:1:1: compilation of rule
'//path/to/your/project:all' failed:

Sandboxed execution failed, which may be legitimate (such as a compiler error),
or due to missing dependencies. To enter the sandbox environment for easier
debugging, run the following command in parentheses. On command failure, a bash
shell running inside the sandbox will then automatically be spawned

namespace-sandbox failed: error executing command
  (cd /some/path && \
  exec env - \
    LANG=en_US \
    PATH=/some/path/bin:/bin:/usr/bin \
    PYTHONPATH=/usr/local/some/path \
  /some/path/namespace-sandbox @/sandbox/root/path/this-sandbox-name.params --
  /some/path/to/your/some-compiler --some-params some-target)

Agora você pode inspecionar o diretório do sandbox gerado e ver quais arquivos o Bazel criou e executar o comando novamente para ver como ele se comporta.

Observe que o Bazel não exclui o diretório do sandbox quando você usa --sandbox_debug. A menos que você esteja depurando ativamente, desative --sandbox_debug porque ele ocupa o disco com o tempo.