この記事では、Bazel のサンドボックス化、sandboxfs
のインストール、サンドボックス環境のデバッグについて説明します。
サンドボックス化は、プロセスを相互に分離したり、システム内のリソースから分離したりする権限制限戦略です。Bazel の場合、これはファイル システムへのアクセスを制限することを意味します。
Bazel のファイル システム サンドボックスは、既知の入力のみを含む作業ディレクトリでプロセスを実行します。これにより、コンパイラやその他のツールは、アクセスすべきでないソースファイルの絶対パスを把握していない限り、そのファイルを見ることができません。
サンドボックス化では、ホスト環境が隠されることはありません。プロセスは、ファイル システム上のすべてのファイルに自由にアクセスできます。ただし、ユーザー Namespace をサポートするプラットフォームでは、プロセスは作業ディレクトリ外のファイルを変更できません。これにより、ビルドの再現性に影響する可能性のある隠れた依存関係がビルドグラフに存在しなくなります。
具体的には、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 Namespace(ユーザー、マウント、PID、ネットワーク、IPC Namespace)を使用して、アクションをホストから分離します。つまり、サンドボックス ディレクトリを除くファイル システム全体が読み取り専用になるため、アクションによってホスト ファイル システムが誤って変更されることはありません。これにより、バグのあるテストで $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
を設定すると、セットアップとテイクダウンの費用を軽減できます。サンドボックス化により、ツールのキャッシュが実質的に無効になります。これは、永続ワーカーを使用すると軽減できますが、その場合、サンドボックスの保証が弱くなります。
マルチプレックス ワーカーでは、ワーカーをサンドボックス化するための明示的なワーカー サポートが必要です。マルチプレックス サンドボックスをサポートしていないワーカーは、動的実行でシングルプレックス ワーカーとして実行されるため、追加のメモリが必要になる場合があります。
sandboxfs
sandboxfs
は、時間のペナルティなしで基盤となるファイル システムの任意のビューを公開する FUSE ファイル システムです。Bazel は sandboxfs
を使用して、アクションごとに execroot/
を即座に生成するため、数千のシステム呼び出しを行うコストを回避できます。execroot/
内の I/O は、FUSE オーバーヘッドが原因で遅くなる可能性があります。
sandboxfs をインストールする
次の手順で sandboxfs
をインストールし、それを使用して Bazel ビルドを実行します。
ダウンロード
sandboxfs
をダウンロードしてインストールし、sandboxfs
バイナリが PATH
に配置されるようにします。
Run sandboxfs
- (macOS のみ)OSXFUSE をインストールします。
(macOS のみ)次のコマンドを実行します。
sudo sysctl -w vfs.generic.osxfuse.tunables.allow_other=1
コア macOS システム サービスが sandboxfs を介して動作するようにするには、インストール後と再起動後にこの操作を行う必要があります。
--experimental_use_sandboxfs
を使用して Bazel ビルドを実行します。bazel build target --experimental_use_sandboxfs
トラブルシューティング
実行されたアクションのアノテーションとして darwin-sandbox
または linux-sandbox
ではなく local
が表示される場合は、サンドボックスが無効になっている可能性があります。有効にするには --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
のようなメッセージが表示された場合は、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
は時間の経過とともにディスクを埋め尽くすため、無効にする必要があります。