沙盒

本文介绍了 Bazel 中的沙盒以及如何调试沙盒 环境。

沙盒是一种权限限制策略,可将进程与 彼此隔离或与系统中的资源隔离。对于 Bazel,这意味着限制文件 系统访问权限。

Bazel 的文件系统沙盒在仅 包含已知输入的工作目录中运行进程,这样,除非编译器和其他工具知道源文件的绝对路径,否则它们不会看到不应访问的源 文件。

沙盒不会以任何方式隐藏宿主环境。进程可以自由 访问文件系统上的所有文件。但是,在支持用户 命名空间的平台上,进程无法修改其工作目录之外的任何文件。 这可确保构建图没有可能 影响构建可重现性的隐藏依赖项。

更具体地说,Bazel 会为每个操作构建一个 execroot/ 目录, 该目录在执行时充当操作的工作目录。execroot/ 包含操作的所有输入文件,并充当任何 生成的输出的容器。然后,Bazel 使用操作系统提供的技术, Linux 上的容器和 sandbox-exec macOS 上的容器,将操作限制在 execroot/ 中。

使用沙盒的原因

  • 如果不使用操作沙盒,Bazel 就不知道工具是否使用了未声明的 输入文件(未在操作的依赖项中明确列出的 操作)。当其中一个未声明的输入文件发生更改时,Bazel 仍然 认为构建是最新的,并且不会重新构建操作。这可能会导致增量构建不正确。

  • 在远程缓存期间,不正确地重复使用缓存条目会产生问题。共享缓存中的错误缓存条目会影响项目中的每位开发者,而清除整个远程缓存并不是可行的解决方案。

  • 沙盒模拟远程执行的行为 - 如果构建在沙盒中运行良好 ,则很可能在远程执行中也能正常运行。通过让 远程执行上传所有必要的文件(包括本地工具),您可以 显著降低编译集群的维护费用,与 每次想要试用新编译器或更改现有工具时都 必须在集群中的每台机器上安装工具相比。

使用哪种沙盒策略

您可以使用 策略标志选择要使用的沙盒类型(如果有)。使用 sandboxed 策略可让 Bazel 选择下面列出的沙盒实现之一, 并优先选择特定于操作系统的沙盒,而不是不太严密的通用沙盒。 持久性工作器将在通用沙盒中运行,如果您传递 --worker_sandboxing标志。

local(也称为 standalone)策略不执行任何类型的沙盒。 它只是执行操作的命令行,并将工作目录设置为 工作区的 execroot。

processwrapper-sandbox 是一种沙盒策略,不需要任何 “高级”功能 - 它应该可以在任何 POSIX 系统上开箱即用。它 会构建一个沙盒目录,该目录由指向原始 源文件的符号链接组成,执行操作的命令行,并将工作目录设置 为此目录而不是 execroot,然后将已知的输出工件 移出沙盒并放入 execroot,并删除沙盒。这样可以防止 操作意外使用任何未声明的输入文件,并防止 execroot 中出现未知的输出文件。

linux-sandbox 更进一步,它基于 processwrapper-sandbox 构建。与 Docker 在后台执行的操作类似,它使用 Linux 命名空间(用户、挂载、PID、网络和 IPC 命名空间)将操作与宿主机隔离。也就是说,它使整个文件系统变为只读, 沙盒目录除外,因此操作无法意外修改 宿主机文件系统上的任何内容。这样可以防止出现类似错误测试意外 rm -rf 您的 $HOME 目录的情况。(可选)您还可以阻止操作访问网络。linux-sandbox 使用 PID 命名空间来阻止操作 查看任何其他进程,并在最后可靠地终止所有进程(甚至是操作 衍生的守护程序)。

darwin-sandbox 与此类似,但适用于 macOS。它使用 Apple 的 sandbox-exec 工具 来实现与 Linux 沙盒大致相同的功能。

由于操作系统提供的机制存在限制,linux-sandboxdarwin-sandbox 都无法在 "嵌套" 场景中运行。由于 Docker 也使用 Linux 命名空间来实现其容器魔法,因此您 无法轻松地在 Docker 容器内运行 linux-sandbox,除非您使用 docker run --privileged。在 macOS 上,您无法在已沙盒化的进程内运行 sandbox-exec inside a process that's already being sandboxed。因此,在这些情况下,Bazel 会自动回退到使用 processwrapper-sandbox

如果您希望收到构建错误(例如,避免意外使用 不太严格的执行策略进行构建),请明确修改 Bazel 尝试使用的执行 策略列表(例如,bazel build --spawn_strategy=worker,linux-sandbox)。

动态执行通常需要使用沙盒进行本地执行。如需选择停用, 请传递 --experimental_local_lockfree_output 标志。动态执行会以静默方式对 持久性工作器进行沙盒化。

沙盒的缺点

  • 沙盒会产生额外的设置和拆除费用。此费用的多少 取决于多种因素,包括构建的形状和 宿主机操作系统的性能。对于 Linux,沙盒化构建的运行速度很少会降低 几个百分点以上。设置 --reuse_sandbox_directories 可以 降低设置和拆除费用。

  • 沙盒实际上会停用工具可能拥有的任何缓存。您可以使用 持久性工作器来缓解此问题,但 代价是沙盒保证会减弱。

  • 多路复用工作器需要显式工作器支持 才能进行沙盒化。不支持多路复用沙盒的工作器在动态执行下作为 单路复用工作器运行,这可能会占用额外的内存。

调试

请按照以下策略调试沙盒问题。

已停用的命名空间

在某些平台(例如 Google Kubernetes Engine 集群节点或 Debian)上,出于 安全考虑,用户命名空间默认处于停用状态。如果 /proc/sys/kernel/unprivileged_userns_clone 文件 存在且包含 0,您可以运行以下命令来启用用户命名空间:

   sudo sysctl kernel.unprivileged_userns_clone=1

规则执行失败

由于系统设置,沙盒可能无法执行规则。如果您看到类似namespace-sandbox.c:633: execvp(argv[0], argv): No such file or directory的消息,请尝试使用--strategy=Genrule=local停用 genrule 的沙盒,并使用--spawn_strategy=local停用其他规则的沙盒。

构建失败的详细调试

如果构建失败,请使用 --verbose_failures--sandbox_debug 让 Bazel 显示构建失败时运行的确切命令,包括设置沙盒的部分 。

错误消息示例:

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)

现在,您可以检查生成的沙盒目录,查看 Bazel 创建了哪些文件,然后再次运行该命令以查看其行为。

请注意,当您使用 --sandbox_debug时,Bazel 不会删除沙盒目录。除非您正在积极调试,否则您应停用 --sandbox_debug,因为它会随着时间的推移占用您的磁盘空间。