この記事では、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 の各名前空間)を使用して、アクションをホストから分離します。つまり、サンドボックス ディレクトリ以外のファイル システム全体が読み取り専用になるため、このアクションによってホストのファイル システム上の何かが誤って変更されることはありません。これにより、バグのあるテストで誤って $HOME ディレクトリに rm -rf が実行されるといった事態を防ぐことができます。必要に応じて、アクションがネットワークにアクセスできないようにすることもできます。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
を設定すると、セットアップと破棄の費用を軽減できます。サンドボックス化により、ツールが保持しているすべてのキャッシュが実質的に無効化されます。これは永続ワーカーを使用することで軽減できますが、サンドボックスの保証は弱くなります。
Multiplex ワーカーでは、明示的なワーカー サポートをサンドボックス化する必要があります。多重化サンドボックス化をサポートしていないワーカーは、動的実行でシングルプレックス ワーカーとして実行されるため、追加のメモリが消費される可能性があります。
デバッグ
サンドボックスの問題をデバッグするには、以下の戦略に沿って対応します。
非アクティブ化された名前空間
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
のようなメッセージが表示された場合は、genrules に --strategy=Genrule=local
を使用し、その他のルールに --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
は時間の経過とともにディスクがいっぱいになるため、無効にする必要があります。