サンドボックス化

問題を報告 ソースを表示

この記事では、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 Namespace(ユーザー、マウント、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 はディスクをいっぱいにするため、無効にする必要があります。