沙箱機制

回報問題 查看原始碼

本文說明瞭 Bazel 中的沙箱機制,以及如何對沙箱環境進行偵錯。

「沙箱」是一項權限限制策略,可將不同程序彼此或系統中資源隔離。對 Bazel 而言,這表示限制檔案系統的存取。

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

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

更明確地說,Bazel 會為每項動作建構 execroot/ 目錄,這些目錄在執行期間會當做動作的工作目錄。execroot/ 包含該動作的所有輸入檔案,並做為任何已產生的輸出的容器。然後,Bazel 會使用由作業系統提供的技術 (Linux 上的容器和 macOS 上的 sandbox-exec) 將動作限制在 execroot/ 內。

採用沙箱機制的原因

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

  • 不正確重複使用快取項目會導致遠端快取發生問題。共用快取中的錯誤快取項目會影響專案的全體開發人員,因此清除整個遠端快取並非可行的解決方案。

  • 沙箱會模擬遠端執行的行為。如果建構作業能在沙箱機制中順利執行,那麼也可以使用遠端執行。只要讓遠端執行上傳所有必要檔案 (包括本機工具),就能大幅降低編譯叢集的維護成本,不必在每次要試用新的編譯器或變更現有工具時,在叢集內的每台機器上安裝相關工具。

該採用的沙箱策略為何

您可以使用策略標記選擇要使用的沙箱機制 (如果有的話)。使用 sandboxed 策略時,Bazel 會選擇下方列出的其中一個沙箱實作項目,並優先採用 OS 專屬的沙箱,而非密封的通用版本。如果您傳遞 --worker_sandboxing 標記,永久工作站會在一般沙箱中執行。

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

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

linux-sandbox 更進一步,並以 processwrapper-sandbox 為基礎進行建構。與 Docker 在內部執行的功能類似,它會使用 Linux 命名空間 (使用者、掛接、PID、網路及 IPC 命名空間) 將動作與主機區隔開來。也就是說,除了沙箱目錄外,整個檔案系統會處於唯讀狀態,因此動作無法不小心修改主機檔案系統中的任何內容。這樣可以避免在執行錯誤測試時不小心將 $HOME 目錄設為 $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 可能會減少設定和減少費用。

  • 沙箱會有效停用工具可能擁有的任何快取。您可以使用永久工作站緩解此情況,但代價較低,但沙盒保證較低。

  • 多重工作站需要明確工作站支援才能採用沙箱機制。不支援多工沙箱機制的工作站在動態執行下會以單一複雜工作站的形式執行,這可能會耗用額外的記憶體。

偵錯

請按照下列策略,使用沙箱機制來偵錯。

已停用的命名空間

在部分平台 (例如 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,因為它會隨著時間填滿磁碟。