沙箱機制

本文說明 Bazel 的沙箱機制、安裝 sandboxfs,以及對沙箱環境進行偵錯。

沙箱是一種權限限制策略,可將程序彼此分隔或與系統中的資源隔離。就 Bazel 而言,這表示限制檔案系統的存取權。

Bazel 的檔案系統沙箱會在僅包含已知輸入內容的工作目錄中執行處理程序,因此除非編譯器和其他工具知道其絕對路徑,否則無法看到他們不應存取的來源檔案。

沙箱不會以任何方式隱藏主機環境。程序可以自由存取檔案系統上的所有檔案。然而,在支援使用者命名空間的平台上,程序無法編輯工作目錄以外的任何檔案。這可確保建構圖沒有可能影響建構可重現的隱藏依附元件。

具體來說,Bazel 會為每個動作建構 execroot/ 目錄,做為執行時動作的工作目錄。execroot/ 包含動作的所有輸入檔案,可做為任何產生輸出內容的容器。然後,Bazel 會使用作業系統提供的技術 (Linux 上的容器和 macOS 上的 sandbox-exec),限制 execroot/ 內的動作。

採用沙箱機制的原因

  • 如果沒有動作沙箱,Bazel 不知道工具是否使用未宣告的輸入檔案 (未明確列於動作依附元件中的檔案)。當其中一個未宣告的輸入檔案變更時,Bazel 仍然認為建構作業是最新版本,而且不會重新建立動作。這可能會導致漸進式建構作業不正確。

  • 不正確重複使用快取項目會導致遠端快取期間發生問題。共用快取中的不良快取項目會影響專案的所有開發人員,而清除整個遠端快取並不可行。

  • 沙箱會模仿遠端執行的行為,如果版本能與沙箱搭配運作,或許也能與遠端執行搭配運作。只要讓遠端執行作業一起上傳所有必要檔案 (包括本機工具),就能大幅降低編譯叢集的維護成本,比起每次試用新編譯器或修改現有工具時,都必須在叢集內的每部機器上安裝工具。

該採用哪種沙箱策略

透過策略標記,您可以選擇要使用的沙箱機制 (如有)。而使用 sandboxed 策略時,Bazel 會挑選下列其中一種沙箱實作方式,並優先採用 OS 專用的沙箱,而非較籠統的沙箱。如果傳遞 --worker_sandboxing 旗標,永久工作站會在一般沙箱中執行。

local (又稱為 standalone) 策略不會執行任何沙箱機制。您只要將工作目錄設為工作區的執行根,即可執行動作的指令列。

processwrapper-sandbox 是一種沙箱策略,不需要任何「進階」功能,應該可在可立即使用的 POSIX 系統上運作。應用程式會建構沙箱目錄,其中包含指向原始來源檔案的符號連結,並使用設為此目錄的工作目錄 (而非 execroot) 執行動作的指令列,然後將已知的輸出構件從沙箱移至 execroot 中,並刪除沙箱。這可防止動作意外使用任何未宣告的輸入檔案,以及利用不明的輸出檔案破壞 execroot。

linux-sandbox 更進一步,以 processwrapper-sandbox 為基礎建構而成。與 Docker 的運作方式類似,它使用 Linux 命名空間 (User、Mount、PID、網路和 IPC 命名空間) 將動作與主機隔離。也就是說,除了沙箱目錄以外,它會將整個檔案系統設為唯讀狀態,因此操作不會意外修改主機檔案系統上的任何項目。這可防止發生錯誤測試不小心 rm-rf 對 $HOME 目錄下的時候。您也可以選擇禁止動作存取網路。linux-sandbox 會使用 PID 命名空間,避免動作看到任何其他程序,並在結束時妥善終止所有程序 (即使是動作產生的 Daemon)。

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)。

在本機執行作業時,通常需要使用沙箱機制。如要選擇退出,請傳遞 --experimental_local_lockfree_output 標記。動態執行功能會在沙箱中自動執行永久工作站

沙箱的缺點

  • 採用沙箱機制會產生額外的設定和卸除費用。此費用的多寡取決於許多因素,包括建構的型態和主機 OS 的效能。以 Linux 來說,沙箱版本的運作速度很少超過百分之百。設定 --reuse_sandbox_directories 可減少設定和卸除費用。

  • 沙箱作業會有效停用工具可能擁有的任何快取。您可以使用永久工作站緩解這種情況,但沙箱也會降低。

  • 多工工作站需要明確的工作站支援,不支援多工沙箱的工作站在動態執行下以單複數工作站的形式執行,因此可能會增加記憶體。

沙箱

sandboxfs 是 FUSE 檔案系統,可公開基礎檔案系統的任意檢視畫面,而不會遭到懲處。Bazel 會使用 sandboxfs 為每個動作立即產生 execroot/,以避免發出數千次系統呼叫的成本。請注意,由於 FUSE 負擔,在 execroot/ 中進一步的 I/O 速度可能會比較慢。

安裝沙箱

請按照下列步驟安裝 sandboxfs,並使用 Bazel 建構作業執行:

下載

下載並安裝 sandboxfs,讓 sandboxfs 二進位檔最終在 PATH 中。

執行 sandboxfs

  1. (僅限 macOS) 安裝 OSXFUSE
  2. (僅限 macOS) 執行:

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

    您必須在安裝完成後以及每次重新啟動時都執行此操作,確保核心 macOS 系統服務能透過沙箱機制運作。

  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 為 Genrules 停用沙箱,並針對其他規則使用 --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,因為這會隨著時間填滿磁碟。