この記事では、Bazel でのサンドボックス化と、サンドボックス環境のデバッグについて説明します。
サンドボックス化 は、プロセスを相互に、またはシステム内のリソースから分離する権限制限戦略です。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 Namespace を使用して、アクションが他のプロセスを参照できないようにし、最後にすべてのプロセス(アクションによって生成されたデーモンを含む)を確実に強制終了します。
darwin-sandbox は同様ですが、macOS 用です。Apple の sandbox-exec ツールを使用して、Linux サンドボックスとほぼ同じことを実現します。
linux-sandbox と darwin-sandbox はどちらも、オペレーティング システムが提供するメカニズムの制限により、ネストされたシナリオでは機能しません。Docker もコンテナ マジックに Linux Namespace を使用するため、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を設定すると、設定と破棄のコストを軽減できます。サンドボックス化により、ツールが持つ可能性のあるキャッシュが事実上無効になります。サンドボックスの保証が弱くなるというデメリットはありますが、永続ワーカーを使用することでこの問題を軽減できます。
多重ワーカーをサンドボックス化するには、明示的なワーカーのサポートが必要です 。多重サンドボックス化をサポートしていないワーカーは、動的実行で単一ワーカーとして実行されるため、追加のメモリが必要になることがあります。
デバッグ
サンドボックス化に関する問題をデバッグするには、次の戦略に従ってください。
無効化された Namespace
Google Kubernetes Engine クラスタノードや Debian などの一部のプラットフォームでは、
セキュリティ上の懸念から、ユーザー Namespace はデフォルトで無効になっています。/proc/sys/kernel/unprivileged_userns_clone ファイルが存在し、0 が含まれている場合は、次のコマンドを実行してユーザー Namespace を有効にできます。
sudo sysctl kernel.unprivileged_userns_clone=1ルールの実行エラー
システムの設定により、サンドボックスでルールを実行できない場合があります。
のようなメッセージが表示された場合は、namespace-sandbox.c:633: execvp(argv[0], argv): No such file or
directoryを使用してサンドボックスを無効にしてみてください。genrule の場合は --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 を無効にしてください。時間の経過とともにディスクがいっぱいになります。