Este artigo aborda o sandbox no Bazel e a depuração do seu ambiente de sandbox.
O sandbox é uma estratégia de restrição de permissões que isola processos uns dos outros ou de recursos em um sistema. Para o Bazel, isso significa restringir o acesso ao sistema de arquivos.
A sandbox do sistema de arquivos do Bazel executa processos em um diretório de trabalho que contém apenas entradas conhecidas. Assim, compiladores e outras ferramentas não veem arquivos de origem que não deveriam acessar, a menos que conheçam os caminhos absolutos deles.
O sandboxing não oculta o ambiente host de forma alguma. Os processos podem acessar livremente todos os arquivos no sistema de arquivos. No entanto, em plataformas que oferecem suporte a namespaces de usuário, os processos não podem modificar arquivos fora do diretório de trabalho. Isso garante que o gráfico de build não tenha dependências ocultas que possam afetar a reprodutibilidade do build.
Mais especificamente, o Bazel cria um diretório execroot/
para cada ação, que funciona como o diretório de trabalho da ação no momento da execução. O execroot/
contém todos os arquivos de entrada da 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 dentro de execroot/
.
Motivos para o isolamento em sandbox
Sem o sandbox de ações, o Bazel não sabe se uma ferramenta usa arquivos de entrada não declarados (arquivos que não estão listados explicitamente 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 reconstruir a ação. Isso pode resultar em uma criação incremental incorreta.
O uso incorreto de entradas de cache cria problemas durante o cache remoto. Uma entrada de cache ruim em um cache compartilhado afeta todos os desenvolvedores do projeto, e limpar 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 o sandbox, provavelmente ela também vai funcionar com a execução remota. Ao fazer com que a execução remota envie todos os arquivos necessários (incluindo ferramentas locais), é possível reduzir significativamente os custos de manutenção dos clusters de compilação em comparação com a instalação das ferramentas em todas as máquinas do cluster sempre que você quiser testar um novo compilador ou fazer uma mudança em uma ferramenta atual.
Qual estratégia de sandbox usar
Você pode escolher qual tipo de sandbox usar, se houver, com as flags de estratégia. Usar a estratégia sandboxed
faz com que o Bazel escolha uma das implementações de sandbox listadas abaixo, preferindo um sandbox específico do SO em vez de um genérico menos hermético.
Os workers persistentes são executados em um sandbox genérico se você transmitir a flag --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 seu espaço de trabalho.
processwrapper-sandbox
é uma estratégia de isolamento que não exige recursos "avançados". Ela funciona em qualquer sistema POSIX sem precisar de configuração. Ele
cria um diretório de sandbox composto por symlinks 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 do execroot, move os artefatos de saída conhecidos
do sandbox para o execroot e exclui o sandbox. Isso evita que a
ação use acidentalmente arquivos de entrada não declarados e que
polua o execroot com arquivos de saída desconhecidos.
linux-sandbox
vai um passo além e se baseia no processwrapper-sandbox
. Semelhante ao que o Docker faz nos bastidores, ele usa namespaces do Linux (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. Assim, a ação não pode modificar acidentalmente nada no
sistema de arquivos do host. Isso evita situações como um teste com bugs que acidentalmente executa rm
-rf no seu diretório $HOME. Também é possível impedir que a ação acesse a rede. O linux-sandbox
usa namespaces de PID para impedir que a ação veja outros processos e para encerrar todos os processos de maneira confiável (até mesmo daemons gerados pela ação) no final.
darwin-sandbox
é semelhante, mas para macOS. Ele usa a ferramenta sandbox-exec
da Apple
para alcançar aproximadamente o mesmo que o sandbox do Linux.
Tanto o linux-sandbox
quanto o 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 do contêiner, não é fácil executar linux-sandbox
em um contêiner do Docker, a menos que você use docker run --privileged
. No macOS, não é possível executar sandbox-exec
em um processo que já está em sandbox. Assim, nesses casos, o Bazel volta automaticamente a usar processwrapper-sandbox
.
Se você preferir receber um erro de build, por exemplo, para 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 exige isolamento em sandbox para execução local. Para desativar, transmita a flag --experimental_local_lockfree_output
. A execução dinâmica cria uma sandbox silenciosa para workers persistentes.
Desvantagens do uso de sandbox
O isolamento em sandbox gera custos extras de configuração e encerramento. O tamanho desse custo depende de muitos fatores, incluindo o formato do build e o desempenho do sistema operacional host. Para Linux, os builds em sandbox raramente são mais de alguns pontos percentuais mais lentos. Definir
--reuse_sandbox_directories
pode reduzir o custo de configuração e encerramento.O isolamento em sandbox desativa qualquer cache que a ferramenta possa ter. É possível atenuar isso usando workers persistentes, ao custo de garantias de sandbox mais fracas.
Os workers multiplexados exigem suporte explícito para serem isolados em sandbox. Os workers que não oferecem suporte à sandbox multiplexada são executados como workers simplexados na execução dinâmica, o que pode custar mais memória.
Depuração
Siga as estratégias abaixo para depurar problemas com o isolamento em sandbox.
Namespaces desativados
Em algumas plataformas, como nós de cluster do Google Kubernetes Engine ou Debian, os namespaces de usuário são desativados por padrão devido a problemas de segurança. Se o arquivo /proc/sys/kernel/unprivileged_userns_clone
existir e contiver um 0, execute o seguinte comando para ativar os namespaces de usuário:
sudo sysctl kernel.unprivileged_userns_clone=1
Falhas na execução de regras
A sandbox pode não executar regras devido à configuração do sistema. Se você encontrar uma mensagem como namespace-sandbox.c:633: execvp(argv[0], argv): No such file or
directory
, tente desativar a sandbox com --strategy=Genrule=local
para genrules e --spawn_strategy=local
para outras regras.
Depuração detalhada para falhas de build
Se o build falhar, use --verbose_failures
e --sandbox_debug
para fazer
o Bazel mostrar o comando exato que foi executado quando o build falhou, incluindo a parte
que configura a 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 de sandbox gerado e ver quais arquivos o Bazel criou e executar o comando novamente para ver como ele se comporta.
O Bazel não exclui o diretório de sandbox quando você usa
--sandbox_debug
. A menos que você esteja depurando ativamente, desative
--sandbox_debug
porque ele preenche o disco com o tempo.