本文將說明如何在 Bazel 中使用沙箱機制,以及如何對沙箱機制環境進行偵錯。
「沙箱」是一種權限限制策略,可將程序彼此獨立或與系統中的資源分開。對於 Bazel 來說,這表示限制檔案系統存取權。
Bazel 的檔案系統沙箱會在工作目錄中執行程序,該目錄只包含已知的輸入內容,因此編譯器和其他工具不會看到不應存取的來源檔案,除非它們知道來源檔案的絕對路徑。
沙箱不會以任何方式隱藏主機環境。程序可以自由存取檔案系統中的所有檔案。不過,在支援使用者命名空間的平台上,程序無法修改工作目錄以外的任何檔案。這可確保建構圖表沒有隱藏依附元件,也不會對建構作業的可重現性造成影響。
具體來說,Bazel 會為每個動作建構 execroot/
目錄,並在執行時充當動作的工作目錄。execroot/
包含動作的所有輸入檔案,並做為任何產生的輸出內容的容器。接著,Bazel 會使用作業系統提供的技術 (Linux 上的容器和 macOS 上的 sandbox-exec
),限制 execroot/
中的動作。
沙盒化的原因
如果沒有動作沙箱機制,Bazel 就不會知道工具是否使用未宣告的輸入檔案 (未在動作的依附元件中明確列出的檔案)。當其中一個未宣告的輸入檔案發生變更時,Bazel 仍會認為版本為最新狀態,因此不會重新建構動作。這可能會導致漸進式建構作業出錯。
若是以不正確的方式重複使用快取項目,將會在遠端快取期間發生問題。共用快取中的快取項目有誤會影響專案中的每位開發人員,因此清除整個遠端快取並非可行解決方案。
沙箱模擬遠端執行行為。如果建構作業與沙箱搭配運作良好,那麼也可能會與遠端執行搭配運作。透過讓遠端執行上傳所有必要檔案 (包括本機工具),您就能大幅減少編譯叢集的維護成本,比起每次嘗試新編譯器或變更現有工具時,都必須在叢集內的每部機器上安裝工具。
採用哪種沙箱策略
您可以使用策略標記,選擇要使用的沙箱類型 (如果有)。使用 sandboxed
策略可讓 Bazel 選取下列所列的其中一個沙箱實作,並優先採用特定作業系統的沙箱,而非較不密封的通用沙箱。如果您傳遞 --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-sandbox
和 darwin-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
,因為這樣會隨時間填滿磁碟。