沙盒

本文介绍了 Bazel 中的沙盒机制、安装 sandboxfs 以及调试沙盒环境的方法。

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

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

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

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

沙盒的原因

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

  • 如果缓存条目不正确,会导致远程缓存出现问题。共享缓存中的错误缓存条目会影响项目中的所有开发者,擦除整个远程缓存并不可行。

  • 沙盒模拟远程执行的行为 - 如果某个 build 支持沙盒,那么它可能也支持远程执行。通过使远程执行上传所有必要的文件(包括本地工具),您可以大大降低编译集群的维护费用,因为这无需每次都安装在集群中的每台机器上{101 想要试用新编译器或对现有工具进行更改。

使用哪些沙盒策略

您可以通过策略标志选择要使用的沙盒(如果有)。使用 sandboxed 策略可让 Bazel 选择下列沙盒沙盒实现之一,最好使用特定于操作系统的沙盒(而非不太封闭的通用沙盒)。如果您传递 --worker_sandboxing 标志,则持久性工作器在通用沙盒中运行。

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

processwrapper-sandbox 是一种沙盒策略,不需要任何“高级”功能,它可直接适用于任何 POSIX 系统。它会构建一个沙盒目录(其中包含指向原始源文件的符号链接),执行相应操作的命令行(将工作目录设置为此目录,而非 execroot),然后移动已知的输出工件{ 101}退出沙盒并进入 execroot,并删除沙盒。这样可以防止操作意外使用未声明的任何输入文件,并避免 execroot 使用未知输出文件。

linux-sandbox 在此基础上更进了一步,并以 processwrapper-sandbox 为基础。与 Docker 在后台执行的操作类似,它使用 Linux 命名空间(用户、装载、PID、网络和 IPC 命名空间)将操作与主机隔离开来。也就是说,除了沙盒目录外,整个文件系统都处于只读状态,因此该操作不会意外修改主机文件系统上的任何内容。这样可以防止发生意外测试,例如意外地从您的 $HOME 目录进行 rm-rf 测试。或者,您还可以阻止操作访问网络。linux-sandbox 使用 PID 命名空间来防止操作看到任何其他进程,并在最终可靠地终止所有进程(甚至是操作生成的守护程序)。

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

由于操作系统提供的机制的限制,linux-sandboxdarwin-sandbox 在“嵌套”场景中均无法使用。由于 Docker 还使用 Linux 命名空间来实现其容器魔法命令,因此除非您使用 docker run --privileged,否则无法轻松地在 Docker 容器内运行 linux-sandbox。在 macOS 中,您无法在已沙盒化的进程中运行 sandbox-exec。因此,在这些情况下,Bazel 会自动回退为使用 processwrapper-sandbox

如果您更喜欢遇到构建错误(例如,不慎使用更严格的执行策略进行构建),请明确修改 Bazel 尝试使用的执行策略列表(例如 bazel build --spawn_strategy=worker,linux-sandbox)。

Sandboxfs

sandboxfs 是一个 FUSE 文件系统,可公开底层文件系统的任意视图,而不会造成任何处罚。Bazel 会使用 sandboxfs 即时为每个操作生成 execroot/,从而避免发出数千次系统调用的费用。请注意,由于 FUSE 开销,execroot/ 内的进一步 I/O 速度可能会变慢。

安装 sandboxfs

请按以下步骤安装 sandboxfs 并使用它执行 Bazel 构建:

下载

下载并安装 sandboxfs,确保 sandboxfs 二进制文件最终出现在 PATH 中。

运行 sandboxfs

  1. (仅限 macOS)安装 OSXFUSE
  2. (仅限 macOS)运行以下命令:

    sudo sysctl -w vfs.generic.osxfuse.tunables.allow_other=1
    

    您必须在安装后和每次重新启动后执行此操作,以确保核心 macOS 系统服务通过 sandboxfs 运行。

  3. 使用 --experimental_use_sandboxfs 运行 Bazel 构建。

    bazel build target --experimental_use_sandboxfs
    

问题排查

如果您看到 local 而不是 darwin-sandboxlinux-sandbox 作为已执行操作的注释,则可能表示沙盒已停用。只需传递 --genrule_strategy=sandboxed --spawn_strategy=sandboxed 即可启用此功能。

调试

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

已停用的命名空间

在某些平台(例如 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 停用 gSandbox 的沙盒,使用其他 --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,因为它会逐渐填满磁盘。