Este artigo aborda o sandbox no Bazel, a instalação de sandboxfs
e a depuração do ambiente de sandbox.
O sandbox é uma estratégia de restrição de permissões que isola processos entre si ou dos recursos de 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. 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 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 o sandbox
Sem o sandbox de ação, 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 incorreta em um cache compartilhado afeta todos os desenvolvedores no projeto. Excluir todo o cache remoto não é uma solução viável.
O sandbox imita o comportamento da execução remota. Se uma versão funcionar bem com o sandbox, provavelmente ela também vai funcionar com a execução remota. Ao fazer upload de todos os arquivos necessários pela execução remota (incluindo ferramentas locais), é possível reduzir significativamente os custos de manutenção para clusters de compilação em comparação com a necessidade de instalar as ferramentas em cada máquina no cluster sempre que você quiser testar um novo compilador ou fazer uma alteração em uma ferramenta existente.
Qual estratégia de sandbox usar
Você pode escolher que 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 ao menos genérico.
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". Ela 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 do execroot, move os artefatos de saída conhecidos
para fora do sandbox para o execroot e exclui o sandbox. Isso evita que a
ação use acidentalmente arquivos de entrada que não tenham sido declarados e
polua o execroot com arquivos de saída desconhecidos.
O linux-sandbox
vai além e se baseia na
processwrapper-sandbox
. Assim como 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, isso torna todo o sistema de arquivos somente leitura, exceto
o diretório do 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 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 eles (até mesmo os daemons
gerados pela ação) no final.
O darwin-sandbox
é parecido, mas para o macOS. Ele usa a ferramenta sandbox-exec
da Apple
para alcançar 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 o comando mágico de contêiner, não é possível executar linux-sandbox
facilmente 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. Portanto, 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 a execução local. Para desativar,
transmita a sinalização --experimental_local_lockfree_output
. A execução dinâmica coloca em sandbox
os workers persistentes de maneira silenciosa.
Desvantagens do sandbox
O sandbox tem custos extras 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 no modo sandbox raramente são mais lentas do que alguns por cento. Definir
--reuse_sandbox_directories
pode mitigar a configuração e o custo de eliminação.O sandbox desativa efetivamente qualquer cache que a ferramenta possa ter. É possível mitigar isso usando trabalhadores persistentes, ao custo de garantias de sandbox mais fracas.
Os Workers multiplex exigem que o suporte explícito deles seja colocado no sandbox. Os workers que não são compatíveis com o sandbox multiplex são executados como workers singleplex em execução dinâmica, o que pode custar mais memória.
sandbox
sandboxfs
é um sistema de arquivos FUSE que expõe uma visualização arbitrária do
sistema de arquivos subjacente sem penalidades de tempo. O Bazel usa sandboxfs
para
gerar execroot/
instantaneamente para cada ação, evitando o custo de
emitir milhares de chamadas do sistema. Mais E/S no execroot/
pode
ser mais lenta devido à sobrecarga do FUSE.
Instalar sandboxfs
Use as etapas abaixo para instalar o sandboxfs
e executar um build do Bazel com
ele:
Fazer o download
Faça o download e instale
sandboxfs
para que o binário sandboxfs
fique no seu PATH
.
Run sandboxfs
- (somente macOS) Instale o OSXFUSE.
(somente macOS) Execute:
sudo sysctl -w vfs.generic.osxfuse.tunables.allow_other=1
Você vai precisar fazer isso após a instalação e após cada reinicialização para garantir que os serviços principais do sistema macOS funcionem com o sandboxfs.
Execute um build do Bazel com
--experimental_use_sandboxfs
.bazel build target --experimental_use_sandboxfs
Solução de problemas
Se local
for mostrado em vez de darwin-sandbox
ou linux-sandbox
como uma
anotação para as ações executadas, isso pode significar que o sandbox está
desativado. Transmita --genrule_strategy=sandboxed --spawn_strategy=sandboxed
para
ativá-la.
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 do 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 0, será possível ativar os namespaces do usuário executando:
sudo sysctl kernel.unprivileged_userns_clone=1
Falhas na execução das regras
O sandbox pode não executar regras devido à configuração do sistema. Se aparecer 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 compilação
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 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 ao longo do tempo.