沙箱機制

回報問題 查看來源 Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

本文將介紹 Bazel 中的沙箱機制,以及如何偵錯沙箱環境。

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

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

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

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

沙盒化原因

  • 如果沒有動作沙箱機制,Bazel 就不知道工具是否使用未宣告的輸入檔案 (未明確列在動作依附元件中的檔案)。如果其中一個未宣告的輸入檔案發生變更,Bazel 仍會認為建構作業是最新狀態,不會重建動作。這可能會導致增量建構作業出錯。

  • 如果快取項目重複使用不當,遠端快取就會發生問題。共用快取中的錯誤快取項目會影響專案中的所有開發人員,因此清除整個遠端快取並非可行的解決方案。

  • 沙箱機制會模擬遠端執行的行為,如果建構作業能順利透過沙箱機制執行,可能也能透過遠端執行。只要讓遠端執行作業上傳所有必要檔案 (包括本機工具),您就不必每次想試用新編譯器或變更現有工具時,都得在叢集中的每部機器上安裝工具,因此與過去相比,編譯叢集的維護成本可大幅降低。

要使用的沙箱策略

您可以使用策略標記選擇要使用的沙箱類型 (如有)。使用 sandboxed 策略時,Bazel 會從下列沙箱實作項目中選取一個,並優先選擇 OS 專屬沙箱,而非較不嚴密的通用沙箱。如果您傳遞 --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。因此在這些情況下,Bazel 會自動改用 processwrapper-sandbox

如果您寧願收到建構錯誤 (例如,避免不小心使用較不嚴格的執行策略進行建構),請明確修改 Bazel 嘗試使用的執行策略清單 (例如 bazel build --spawn_strategy=worker,linux-sandbox)。

動態執行通常需要沙箱,才能在本機執行。如要停用,請傳遞 --experimental_local_lockfree_output 旗標。動態執行會以無聲無息的方式,將持續性工作站放入沙箱。

沙箱的缺點

  • 沙箱會產生額外的設定和拆除費用。這項成本的大小取決於許多因素,包括建構的形狀和主機 OS 的效能。對於 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 停用 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,因為這項功能會隨著時間填滿磁碟。