使用 Docker Sandbox 对 Bazel Remote Execution 进行问题排查

报告问题 查看源代码

由于不会影响本地构建的限制和要求,在本地成功执行的 Bazel 构建在远程执行时可能会失败。为远程执行调整 Bazel 规则中介绍了此类失败的最常见原因。

本页面介绍如何使用 Docker 沙盒功能识别和解决远程执行中出现的最常见问题,该功能对构建施加的限制与远程执行相同。这样,您无需远程执行服务即可对构建进行问题排查。

Docker 沙盒功能模仿远程执行的限制,如下所示:

  • 构建操作在工具链容器中执行。您可以使用同一工具链容器,通过支持容器化远程执行的服务在本地和远程运行构建。

  • 没有无关数据会跨越容器边界。只有明确声明的输入和输出才能进入和离开容器,并且只能在关联的构建操作成功完成后才进入和退出。

  • 每个操作都在全新的容器中执行。系统会为每个生成的构建操作创建一个唯一的新容器。

您可以使用以下方法之一排查这些问题:

  • 以原生方式进行问题排查。使用此方法时,Bazel 及其构建操作在本地机器上以原生方式运行。Docker 沙盒功能会对构建施加与远程执行相同的限制。但是,此方法不会检测本地工具、状态和数据泄露到构建中的情况,否则会导致远程执行出现问题。

  • 在 Docker 容器中排查问题。 通过这种方法,Bazel 及其构建操作在 Docker 容器内运行,让您可以检测从本地机器到构建中泄露的工具、状态和数据,除了施加与远程执行相同的限制外。即使部分构建失败,此方法也能让您深入了解您的构建。此方法处于实验阶段,不受官方支持。

前提条件

在开始排查问题之前,请先执行以下操作(如果您尚未这样做):

  • 安装 Docker 并配置运行 Docker 所需的权限。
  • 安装 Bazel 0.14.1 或更高版本。较低版本不支持 Docker 沙盒功能。
  • 按照此处所述,将已固定到最新发布版本的 bazel-toolchains 代码库添加到 build 的 WORKSPACE 文件中。
  • .bazelrc 文件添加标志以启用该功能。在 Bazel 项目的根目录中创建该文件(如果不存在)。以下标志为参考示例。请查看 bazel-toolchains 代码库中的最新 .bazelrc 文件,并复制此处为配置 docker-sandbox 定义的标志的值。
# Docker Sandbox Mode
build:docker-sandbox --host_javabase=<...>
build:docker-sandbox --javabase=<...>
build:docker-sandbox --crosstool_top=<...>
build:docker-sandbox --experimental_docker_image=<...>
build:docker-sandbox --spawn_strategy=docker --strategy=Javac=docker --genrule_strategy=docker
build:docker-sandbox --define=EXECUTOR=remote
build:docker-sandbox --experimental_docker_verbose
build:docker-sandbox --experimental_enable_docker_sandbox

如果您的规则需要其他工具,请执行以下操作:

  1. 使用 Dockerfile 安装工具并在本地构建映像,以创建自定义 Docker 容器。

  2. 将上述 --experimental_docker_image 标志的值替换为自定义容器映像的名称。

以原生方式进行问题排查

此方法直接在本地机器上执行 Bazel 及其所有构建操作,可以切实有效地确认您的构建在远程执行时是否能够成功。

但是,如果使用此方法,本地安装的工具、二进制文件和数据可能会泄露到您的 build 中,尤其是当 build 使用配置样式的工作区规则时。此类泄露会导致远程执行出现问题;为了检测这些泄露,除了以原生方式进行问题排查外,还需要在 Docker 容器中进行问题排查

第 1 步:运行 build

  1. --config=docker-sandbox 标志添加到执行构建的 Bazel 命令中。例如:

    bazel --bazelrc=.bazelrc build --config=docker-sandbox target
    
  2. 运行构建并等待构建完成。由于 Docker 沙盒功能的原因,构建速度最多会比平常慢四倍。

您可能会遇到以下错误:

ERROR: 'docker' is an invalid value for docker spawn strategy.

如果出现这种情况,请使用 --experimental_docker_verbose 标志再次运行构建。此标志启用详细错误消息。此错误通常是由于 Docker 安装错误或缺少在当前用户帐号下执行 Docker 的权限而导致的。如需了解详情,请参阅 Docker 文档。如果问题仍然存在,请直接跳到在 Docker 容器中排查问题

第 2 步:解决检测到的问题

以下是最常见的问题及其解决方法。

  • 缺少 Bazel runfiles 树引用的文件、工具、二进制文件或资源。确认已明确声明受影响目标的所有依赖项。如需了解详情,请参阅管理隐式依赖项

  • 缺少绝对路径或 PATH 变量引用的文件、工具、二进制文件或资源。确认所有必需的工具都安装在工具链容器中,并使用工具链规则正确声明指向缺失资源的依赖项。如需了解详情,请参阅通过工具链规则调用构建工具

  • 二进制文件执行失败。其中一条构建规则引用了与执行环境(Docker 容器)不兼容的二进制文件。如需了解详情,请参阅管理依赖于平台的二进制文件。如果您无法解决问题,请与 bazel-discuss@google.com 联系以获取帮助。

  • @local-jdk 中的某个文件缺失或导致错误。本地机器上的 Java 二进制文件在与其不兼容时泄露到 build 中。在规则和目标中使用 java_toolchain,而不是 @local_jdk。如果您需要进一步的帮助,请发送电子邮件至 bazel-discuss@google.com

  • 其他错误。请联系 bazel-discuss@google.com 寻求帮助。

在 Docker 容器中进行问题排查

通过这种方法,Bazel 会在主机 Docker 容器内运行,Bazel 的构建操作则会在 Docker 沙盒功能生成的各个工具链容器内执行。沙盒会为每个构建操作生成一个全新的工具链容器,并且在每个工具链容器中仅执行一个操作。

此方法可对主机环境中安装的工具进行更精细的控制。通过将构建的执行与其构建操作分开并尽可能减少已安装的工具,您可以验证您的构建是否对本地执行环境有任何依赖项。

第 1 步:构建容器

  1. 创建一个 Dockerfile,用于创建 Docker 容器并使用一组最少的构建工具安装 Bazel:

    FROM debian:stretch
    
    RUN apt-get update && apt-get install -y apt-transport-https curl software-properties-common git gcc gnupg2 g++ openjdk-8-jdk-headless python-dev zip wget vim
    
    RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
    
    RUN add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"
    
    RUN apt-get update && apt-get install -y docker-ce
    
    RUN wget https://releases.bazel.build/<latest Bazel version>/release/bazel-<latest Bazel version>-installer-linux-x86_64.sh -O ./bazel-installer.sh && chmod 755 ./bazel-installer.sh
    
    RUN ./bazel-installer.sh
    
  2. 将容器构建为 bazel_container

    docker build -t bazel_container - < Dockerfile
    

第 2 步:启动容器

使用如下所示的命令启动 Docker 容器。在命令中,替换要构建的主机上源代码的路径。

docker run -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /tmp:/tmp \
  -v your source code directory:/src \
  -w /src \
  bazel_container \
  /bin/bash

此命令以 root 身份运行容器,映射 Docker 套接字,并装载 /tmp 目录。这样,Bazel 可以生成其他 Docker 容器,并使用 /tmp 下的目录与这些容器共享文件。您可以在容器内的 /src 中找到源代码。

该命令会有意从 debian:stretch 基础容器启动,该容器包含与用作工具链容器的 rbe-ubuntu16-04 容器不兼容的二进制文件。如果来自本地环境的二进制文件泄露到工具链容器中,则会导致构建错误。

第 3 步:测试容器

从 Docker 容器内部运行以下命令以对其进行测试:

docker ps
bazel version

第 4 步:运行构建

运行 build,如下所示。输出用户是 root,这样它就对应于可从运行 Bazel 的宿主容器内使用相同的绝对路径访问的目录;输出用户可通过 Docker 沙盒功能(其中运行 Bazel 的构建操作)生成的工具链容器;以及从运行主机和操作容器的本地机器访问。

bazel --output_user_root=/tmp/bazel_docker_root --bazelrc=.bazelrc \ build --config=docker-sandbox target

第 5 步:解决检测到的问题

您可以按如下方式解决构建失败问题:

  • 如果构建失败并显示“磁盘空间不足”错误,您可以通过使用 --memory=XX 标志启动主机容器来提高此限制,其中 XX 是分配的磁盘空间(以 GB 为单位)。这是一项实验性功能,可能会导致行为不可预测。

  • 如果构建在分析或加载阶段失败,则 WORKSPACE 文件中声明的一个或多个构建规则与远程执行不兼容。如需了解可能的原因和解决方法,请参阅调整 Remote Execution 的 Bazel 规则

  • 如果构建因任何其他原因而失败,请参阅第 2 步:解决检测到的问题中的问题排查步骤。