Este artigo aborda o sandboxing no Bazel e a depuração do ambiente de sandboxing.
O sandbox é uma estratégia de restrição de permissão que isola processos uns dos outros ou de 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, de modo que os compiladores e outras ferramentas não veem arquivos de origem que não devem ser acessados, a menos que conheçam os caminhos absolutos.
O sandboxing não oculta o ambiente do host de nenhuma forma. 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 nenhum arquivo 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 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 da ação e serve como contêiner para todas as
saídas geradas. Em seguida, ele 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 usar o sandbox
Sem o sandbox de ação, 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 recria a ação. Isso pode resultar em um build incremental incorreto.
A reutilização incorreta de entradas de cache cria problemas durante o cache remoto. Uma entrada de cache inválida 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 simula o comportamento da execução remota. Se uma build funcionar bem com o sandbox, provavelmente também funcionará com a execução remota. Ao fazer a execução remota fazer upload de todos os arquivos necessários (incluindo ferramentas locais), é possível reduzir significativamente os custos de manutenção de clusters de compilação em comparação com a necessidade de instalar as ferramentas em cada máquina do cluster sempre que você quiser testar um novo compilador ou fazer uma mudança em uma ferramenta existente.
Qual estratégia de sandbox usar
Você pode escolher o tipo de sandbox a ser usado com as sinalizações de estratégia, se houver. 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 seu espaço de trabalho.
processwrapper-sandbox
é uma estratégia de sandbox que não requer recursos
"avançados", ou seja, ela funciona em qualquer sistema POSIX. 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, 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 que não foram declarados e que
preencha o execroot com arquivos de saída desconhecidos.
O linux-sandbox
vai além e é construído sobre o
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 de sandbox, para que a ação não modifique acidentalmente nada no
sistema de arquivos do host. Isso evita situações como um teste com bugs que acidentalmente remove
-rf o diretório $HOME. Opcionalmente, você também pode impedir que a ação
acessa a rede. O linux-sandbox
usa namespaces de PID para impedir que a ação
venha outros processos e para encerrar todos os processos (até mesmo os 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 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 o comando mágico de contêiner, não é fácil executar 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
em um
processo que já está em sandbox. Portanto, nesses casos, o Bazel
volta automaticamente a usar processwrapper-sandbox
.
Se você preferir receber um erro de build, como não criar acidentalmente com uma
estratégia de execução menos rígida, 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 o uso do sandbox para execução local. Para desativar,
transmita a flag --experimental_local_lockfree_output
. A execução dinâmica coloca workers persistentes em sandbox de forma silenciosa.
Desvantagens do sandbox
O sandbox gera custos extras de configuração e desativação. O tamanho desse custo depende de muitos fatores, incluindo a forma do build e o desempenho do SO host. No Linux, os builds em sandbox raramente são mais lentos do que alguns poucos por cento. A configuração de
--reuse_sandbox_directories
pode reduzir o custo de configuração e desmontagem.O sandbox desativa efetivamente qualquer cache que a ferramenta possa ter. É possível atenuar isso usando workers persistentes, mas com garantias de sandbox mais fracas.
Os workers multiplex exigem que o suporte explícito do worker seja colocado no sandbox. Os workers que não oferecem suporte ao sandbox multiplex são executados como workers singleplex em execução dinâmica, o que pode consumir mais memória.
Depuração
Siga as estratégias abaixo para depurar problemas com sandboxing.
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 preocupações de segurança. Se o arquivo /proc/sys/kernel/unprivileged_userns_clone
existir e contiver um 0, você poderá ativar os namespaces de 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 você receber 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 para falhas de build
Se o build falhar, use --verbose_failures
e --sandbox_debug
para fazer com que o Bazel mostre o comando exato que foi 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 é possível inspecionar o diretório de sandbox gerado e conferir quais arquivos o Bazel criou e executar o comando novamente para conferir 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 ao longo do tempo.