サンドボックス化

この記事では、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 ディレクトリに rm -rf するなどの状況を防ぐことができます。必要に応じて、アクションがネットワークにアクセスできないようにすることもできます。linux-sandbox は PID 名前空間を使用して、アクションが他のプロセスを参照しないようにし、最後にすべてのプロセス(アクションによって生成されたデーモンも含む)を確実に強制終了します。

darwin-sandbox も同様ですが、macOS 用です。Apple の sandbox-exec ツールを使用して、Linux サンドボックスとほぼ同じ結果を得られます。

linux-sandboxdarwin-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/ を即座に生成するため、何千ものシステムコールを発行するコストを節約できます。FUSE のオーバーヘッドによって、execroot/ 内のその後の I/O は遅くなる可能性があります。

sandboxfs をインストールする

次の手順で sandboxfs をインストールし、Bazel ビルドを実行します。

ダウンロード

sandboxfs バイナリが PATH に格納されるように、sandboxfsダウンロードしてインストールします。

Run sandboxfs

  1. (macOS のみ)OSXFUSE をインストールします。
  2. (macOS のみ)次のコマンドを実行します。

    sudo sysctl -w vfs.generic.osxfuse.tunables.allow_other=1
    

    コア macOS システム サービスが sandboxfs を介して動作するように、インストール後と再起動のたびに行う必要があります。

  3. --experimental_use_sandboxfs を使用して Bazel ビルドを実行します。

    bazel build target --experimental_use_sandboxfs
    

トラブルシューティング

実行されるアクションのアノテーションとして、darwin-sandboxlinux-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 は、時間の経過とともにディスクがいっぱいになるため、積極的にデバッグしている場合を除き、無効にする必要があります。