En este artículo, se explica la zona de pruebas en Bazel y cómo depurar tu entorno de zona de pruebas.
La zona de pruebas es una estrategia de restricción de permisos que aísla los procesos entre sí o de los recursos de un sistema. En el caso de Bazel, esto significa restringir el acceso al sistema de archivos.
El sandbox del sistema de archivos de Bazel ejecuta procesos en un directorio de trabajo que solo contiene entradas conocidas, de modo que los compiladores y otras herramientas no ven los archivos fuente a los que no deberían acceder, a menos que conozcan sus rutas absolutas.
La zona de pruebas no oculta el entorno del host de ninguna manera. Los procesos pueden acceder libremente a todos los archivos del sistema de archivos. Sin embargo, en las plataformas que admiten espacios de nombres de usuarios, los procesos no pueden modificar ningún archivo fuera de su directorio de trabajo. Esto garantiza que el gráfico de compilación no tenga dependencias ocultas que puedan afectar la reproducibilidad de la compilación.
Más específicamente, Bazel construye un directorio execroot/
para cada acción, que actúa como el directorio de trabajo de la acción en el tiempo de ejecución. execroot/
contiene todos los archivos de entrada de la acción y sirve como contenedor para cualquier salida generada. Luego, Bazel usa una técnica proporcionada por el sistema operativo (contenedores en Linux y sandbox-exec
en macOS) para restringir la acción dentro de execroot/
.
Motivos para el aislamiento
Sin la zona de pruebas de acciones, Bazel no sabe si una herramienta usa archivos de entrada no declarados (archivos que no se enumeran de forma explícita en las dependencias de una acción). Cuando cambia uno de los archivos de entrada no declarados, Bazel sigue creyendo que la compilación está actualizada y no recompilará la acción. Esto puede generar una compilación incremental incorrecta.
El uso incorrecto de las entradas de caché crea problemas durante el almacenamiento en caché remoto. Una entrada de caché incorrecta en una caché compartida afecta a todos los desarrolladores del proyecto, y borrar toda la caché remota no es una solución factible.
La zona de pruebas imita el comportamiento de la ejecución remota. Si una compilación funciona bien con la zona de pruebas, es probable que también funcione con la ejecución remota. Si haces que la ejecución remota suba todos los archivos necesarios (incluidas las herramientas locales), puedes reducir significativamente los costos de mantenimiento de los clústeres de compilación en comparación con tener que instalar las herramientas en cada máquina del clúster cada vez que quieras probar un compilador nuevo o hacer un cambio en una herramienta existente.
Qué estrategia de zona de pruebas usar
Puedes elegir qué tipo de zona de pruebas usar, si es que usas alguna, con las marcas de estrategia. Usar la estrategia sandboxed
hace que Bazel elija una de las implementaciones de zona de pruebas que se indican a continuación, y prefiere una zona de pruebas específica del SO a la genérica menos hermética.
Los trabajadores persistentes se ejecutan en un entorno de pruebas genérico si pasas la marca --worker_sandboxing
.
La estrategia local
(también conocida como standalone
) no realiza ningún tipo de zona de pruebas.
Simplemente ejecuta la línea de comandos de la acción con el directorio de trabajo establecido en el directorio execroot de tu espacio de trabajo.
processwrapper-sandbox
es una estrategia de zona de pruebas que no requiere ninguna función "avanzada": debería funcionar en cualquier sistema POSIX de inmediato. Crea un directorio de zona de pruebas que consta de vínculos simbólicos que apuntan a los archivos fuente originales, ejecuta la línea de comandos de la acción con el directorio de trabajo establecido en este directorio en lugar de execroot, luego mueve los artefactos de salida conocidos de la zona de pruebas a execroot y borra la zona de pruebas. Esto evita que la acción use accidentalmente archivos de entrada que no se declararon y que llene el directorio execroot con archivos de salida desconocidos.
linux-sandbox
va un paso más allá y se basa en processwrapper-sandbox
. De manera similar a lo que hace Docker en segundo plano, usa espacios de nombres de Linux (espacios de nombres de usuario, de montaje, de PID, de red y de IPC) para aislar la acción del host. Es decir, hace que todo el sistema de archivos sea de solo lectura, excepto el directorio de zona de pruebas, por lo que la acción no puede modificar accidentalmente nada en el sistema de archivos del host. Esto evita situaciones como que una prueba con errores ejecute accidentalmente rm -rf en tu directorio $HOME. De manera opcional, también puedes evitar que la acción acceda a la red. linux-sandbox
usa espacios de nombres de PID para evitar que la acción vea otros procesos y para terminar de forma confiable todos los procesos (incluso los daemons generados por la acción) al final.
darwin-sandbox
es similar, pero para macOS. Utiliza la herramienta sandbox-exec
de Apple para lograr aproximadamente lo mismo que la zona de pruebas de Linux.
Tanto linux-sandbox
como darwin-sandbox
no funcionan en una situación "anidada" debido a las restricciones de los mecanismos que proporcionan los sistemas operativos. Dado que Docker también usa espacios de nombres de Linux para su magia de contenedores, no puedes ejecutar linux-sandbox
fácilmente dentro de un contenedor de Docker, a menos que uses docker run --privileged
. En macOS, no puedes ejecutar sandbox-exec
dentro de un proceso que ya se esté ejecutando en un entorno de pruebas. Por lo tanto, en estos casos, Bazel recurre automáticamente al uso de processwrapper-sandbox
.
Si prefieres recibir un error de compilación, por ejemplo, para no compilar accidentalmente con una estrategia de ejecución menos estricta, modifica de forma explícita la lista de estrategias de ejecución que Bazel intenta usar (por ejemplo, bazel build
--spawn_strategy=worker,linux-sandbox
).
Por lo general, la ejecución dinámica requiere un espacio aislado para la ejecución local. Para inhabilitar la función, pasa la marca --experimental_local_lockfree_output
. La ejecución dinámica aísla de forma silenciosa los trabajadores persistentes.
Desventajas de la zona de pruebas
El uso de zonas de pruebas genera costos adicionales de configuración y desmantelamiento. La magnitud de este costo depende de muchos factores, como la forma de la compilación y el rendimiento del SO host. En Linux, las compilaciones en zona de pruebas rara vez son más de un pequeño porcentaje más lentas. Establecer
--reuse_sandbox_directories
puede mitigar el costo de configuración y desinstalación.La zona de pruebas inhabilita de manera efectiva cualquier caché que pueda tener la herramienta. Puedes mitigar esto con trabajadores persistentes, a costa de garantías de zona de pruebas más débiles.
Los trabajadores de multiplexación requieren compatibilidad explícita con trabajadores para ejecutarse en un entorno de pruebas. Los trabajadores que no admiten el aislamiento de zona de pruebas de multiplexación se ejecutan como trabajadores de multiplexación única en la ejecución dinámica, lo que puede costar memoria adicional.
Depuración
Sigue las estrategias que se indican a continuación para depurar problemas relacionados con el aislamiento.
Espacios de nombres desactivados
En algunas plataformas, como los nodos de clúster de Google Kubernetes Engine o Debian, los espacios de nombres de usuario se desactivan de forma predeterminada debido a problemas de seguridad. Si el archivo /proc/sys/kernel/unprivileged_userns_clone
existe y contiene un 0, puedes activar los espacios de nombres del usuario ejecutando el siguiente comando:
sudo sysctl kernel.unprivileged_userns_clone=1
Fallas en la ejecución de reglas
Es posible que el sandbox no pueda ejecutar reglas debido a la configuración del sistema. Si ves un mensaje como namespace-sandbox.c:633: execvp(argv[0], argv): No such file or
directory
, intenta desactivar el sandbox con --strategy=Genrule=local
para genrules y --spawn_strategy=local
para otras reglas.
Depuración detallada para errores de compilación
Si falló tu compilación, usa --verbose_failures
y --sandbox_debug
para que Bazel muestre el comando exacto que ejecutó cuando falló tu compilación, incluida la parte que configura el sandbox.
Ejemplo de mensaje de error:
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)
Ahora puedes inspeccionar el directorio de zona de pruebas generado y ver qué archivos creó Bazel, y volver a ejecutar el comando para ver cómo se comporta.
Ten en cuenta que Bazel no borra el directorio de zona de pruebas cuando usas --sandbox_debug
. A menos que estés depurando de forma activa, debes inhabilitar --sandbox_debug
, ya que llena el disco con el tiempo.