この記事では、Bazel でのサンドボックス化、sandboxfs
のインストール、サンドボックス環境のデバッグについて説明します。
サンドボックスは、プロセスを相互に、またはシステム内のリソースから分離する、権限制限戦略です。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 ディレクトリに配置されないようにすることができます。必要に応じて、アクションがネットワークにアクセスできないようにすることもできます。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 ワーカーをサンドボックス化するには、明示的なワーカーのサポートが必要です。多重化サンドボックスをサポートしていないワーカーは、動的実行では単一複合ワーカーとして実行されるため、余分なメモリが発生する可能性があります。
sandboxfs
sandboxfs
は、時間をかけずに基盤となるファイル システムの任意のビューを公開する FUSE ファイル システムです。Bazel は、sandboxfs
を使用してアクションごとに execroot/
を瞬時に生成します。これにより、何千ものシステム呼び出しを発行するコストを回避できます。execroot/
内の I/O は、FUSE のオーバーヘッドが原因で遅くなる可能性があります。
sandboxfs をインストールする
次の手順で sandboxfs
をインストールし、Bazel ビルドを実行します。
オフライン
sandboxfs
バイナリが最終的に PATH
に収まるように、sandboxfs
をダウンロードしてインストールします。
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
を使用してサンドボックスを無効にしてみてください。
ビルドエラーの詳細のデバッグ
ビルドが失敗した場合は、サンドボックスを設定する部分を含め、ビルドが失敗した場合に実行されたコマンドを Bazel が正確に表示するために --verbose_failures
と --sandbox_debug
を使用します。
エラー メッセージの例:
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
を無効にする必要があります。