Neste artigo, abordamos o sandbox no Bazel, a instalação do sandboxfs
e a depuração do seu 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, para que os compiladores e outras ferramentas não vejam os arquivos de origem que não podem acessar, a menos que conheçam 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 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 dele.
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. execroot/
contém todos os arquivos de entrada para a ação e serve como contêiner de qualquer saída gerada. 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 do execroot/
.
Motivos para o 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 recria a ação. Isso pode resultar em um build incremental incorreto.
A reutilização incorreta das entradas de cache cria problemas durante o armazenamento em cache remoto. Uma entrada de cache ruim em um cache compartilhado afeta cada desenvolvedor 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 versão funcionar bem com o sandbox, provavelmente também funcionará com a execução remota. Ao fazer a execução remota do upload de todos os arquivos necessários (incluindo ferramentas locais), você pode reduzir significativamente os custos de manutenção dos clusters de compilação, em vez de instalar as ferramentas em todas as máquinas do cluster sempre que quiser testar um novo compilador ou fazer uma mudança em uma ferramenta atual.
Qual estratégia de sandbox usar
Escolha qual tipo de sandbox será usado, 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,
preferenciando um sandbox específico do SO para o genérico menos hermético.
Os workers permanentes são executados em um sandbox genérico quando a sinalização --worker_sandboxing
é transmitida.
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, depois move os artefatos de saída conhecidos
do sandbox para o execroot e exclui o sandbox. Isso impede que a ação use acidentalmente quaisquer arquivos de entrada que não sejam declarados e que tornem o execroot em arquivos de saída desconhecidos.
A linux-sandbox
vai 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 de sandbox, de modo que a ação não pode modificar acidentalmente nada
no sistema de arquivos do host. Isso evita situações como um teste de bug ao colocar o diretório $HOME no lugar por engano. Também é possível impedir que a ação
acesse a rede. linux-sandbox
usa namespaces PID para impedir que a ação
veja outros processos e eliminar 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 fazer quase 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 do 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 o sandbox-exec
dentro de um
processo que já esteja no sandbox. Assim, nesses casos, o Bazel volta automaticamente a usar processwrapper-sandbox
.
Se você preferir receber um erro de build, por exemplo, 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 silenciosamente os workers permanentes no sandbox.
Desvantagens do sandbox
O sandbox gera 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 em sandbox raramente são algumas vezes mais lentas. Definir
--reuse_sandbox_directories
pode mitigar o custo de configuração e desmontagem.O sandbox desativa efetivamente qualquer cache que a ferramenta possa ter. É possível atenuar isso usando workers permanentes, ao custo de garantias de sandbox mais fracas.
Os workers multiplex exigem compatibilidade explícita com workers. Os workers que não são compatíveis com o sandbox multiplex são executados como workers simples em execução dinâmica, o que pode custar mais memória.
sandbox
sandboxfs
é um sistema de arquivos FUSE que expõe uma visão arbitrária do
sistema 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. Outras E/S em execroot/
podem
ser mais lentas devido à sobrecarga do FUSE.
Instalar sandboxfs
Use as etapas a seguir para instalar o sandboxfs
e executar uma versão do Bazel com ele:
Download
Faça o download e a instalação do sandboxfs
para que o binário sandboxfs
fique no PATH
.
Run sandboxfs
- (somente macOS) Instale o OSXFUSE.
(somente macOS) Executar:
sudo sysctl -w vfs.generic.osxfuse.tunables.allow_other=1
Faça isso depois da instalação e depois de cada reinicialização para garantir que os principais serviços do sistema macOS funcionem por meio do sandboxfs.
Execute um build do Bazel com
--experimental_use_sandboxfs
.bazel build target --experimental_use_sandboxfs
Solução de problemas
Se você vir local
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á-lo.
Depuração
Siga as estratégias abaixo para depurar problemas no sandbox.
Namespaces desativados
Em algumas plataformas, como o
Kubernetes Engine,
os nós de cluster ou o Debian, os namespaces dos usuários 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, você poderá ativar namespaces de usuário executando:
sudo sysctl kernel.unprivileged_userns_clone=1
Falhas na execução das regras
O sandbox pode não executar as regras devido à configuração do sistema. Se você vir 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
regras gerais 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 ele executou 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 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 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.