BazelCon 2022 は、11 月 16 ~ 17 日にニューヨークとオンラインで開催されます。
今すぐご登録ください。

サンドボックス化

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

この記事では、Bazel でのサンドボックス化、sandboxfs のインストール、サンドボックス環境のデバッグについて説明します。

サンドボックス化は、プロセスを相互に、またはシステム内のリソースから分離する、権限制限戦略です。Bazel の場合は、ファイル システムへのアクセスを制限します。

Bazel のファイル システム サンドボックスは、既知の入力のみを含む作業ディレクトリでプロセスを実行します。これにより、コンパイラや他のツールは、絶対パスがわからない限り、アクセスすべきソースファイルを認識できません。

サンドボックス化によって、ホスト環境が隠れることはありません。プロセスは、ファイル システム上のすべてのファイルに自由にアクセスできます。ただし、ユーザーの名前空間をサポートするプラットフォームでは、プロセスは作業ディレクトリ外のファイルを変更できません。これにより、ビルドの再現性に影響する可能性のある隠し依存関係がビルドグラフになくなります。

具体的には、Bazel はアクションごとに execroot/ ディレクトリを作成します。このディレクトリは、実行時にアクションの作業ディレクトリとして機能します。execroot/ には、アクションへのすべての入力ファイルが含まれ、生成された出力のコンテナとして機能します。Bazel は、オペレーティング システムで提供される手法、Linux のコンテナ、macOS の sandbox-exec を使用して、execroot/ 内のアクションを制限します。

サンドボックス化の理由

  • アクションのサンドボックス化を行わないと、Bazel は、宣言されていない入力ファイル(アクションの依存関係に明示的にリストされていないファイル)をツールが使用するかどうかは認識しません。宣言されていない入力ファイルのいずれかが変更された場合、Bazel はビルドが最新であると判断し、アクションを再構築しません。その結果、増分ビルドが正しく行われない可能性があります。

  • キャッシュ エントリを再利用すると、リモート キャッシュ中に問題が発生する。共有キャッシュ内の不適切なキャッシュ エントリは、プロジェクトのすべてのデベロッパーに影響を与えるため、リモート キャッシュ全体をワイプすることは現実的ではありません。

  • サンドボックス化はリモート実行の動作を模倣します。ビルドがサンドボックスでうまく機能すれば、リモート実行でも機能する可能性があります。リモート実行に必要なすべてのファイル(ローカルツールを含む)をアップロードすると、新しいコンパイラを試したり既存のツールを変更したりするたびにクラスタ内のすべてのマシンにツールをインストールする場合と比較して、コンパイル クラスタのメンテナンス コストを大幅に削減できます。

使用するサンドボックス戦略

戦略フラグを使用すると、使用するサンドボックスの種類を選択できます(存在する場合)。Bazel は sandboxed 戦略を使用して、下記のサンドボックス実装のいずれかを選択し、OS 固有のサンドボックスを密閉型の汎用的なサンドボックスよりも優先させます。永続的なワーカーは、--worker_sandboxing フラグを渡すと、一般的なサンドボックスで実行されます。

localstandalone)戦略では、なんらかのサンドボックス化は行われません。ワークスペースの execroot に設定された作業ディレクトリで、action のコマンドラインを実行するだけです。

processwrapper-sandbox は「高度な機能」を必要としないサンドボックス化戦略です。すぐに使用できる POSIX システムで動作します。元のソースファイルを指すシンボリック リンクで構成されるサンドボックス ディレクトリが構築され、execroot ではなくこのディレクトリに作業ディレクトリを設定して action のコマンドラインが実行されます。その後、既知の出力アーティファクトがサンドボックスから execroot に移動し、サンドボックスが削除されます。これにより、アクションは、宣言されていない入力ファイルを誤って使用することを防ぎ、不明な出力ファイルが execroot に付いないようにします。

linux-sandbox は、さらに一歩進んで、processwrapper-sandbox の上に構築されています。Docker が内部で行うのと同様に、Linux Namespace(User、mount、PID、Network、IPC の Namespace)を使用してアクションをホストから分離します。つまり、サンドボックス ディレクトリを除き、ファイル システム全体が読み取り専用になるため、このアクションによってホストのファイル システム上のすべての項目が誤って変更されることはありません。これにより、バグのあるテストが誤って $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 の場合、サンドボックス化されたビルドの速度が数パーセント向上することはほとんどありません。--experimental_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-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 などのメッセージが表示された場合は、genrule の場合は --strategy=Genrule=local、その他のルールの場合は --spawn_strategy=local でサンドボックスを無効にしてみてください。

ビルドエラーの詳細なデバッグ

ビルドが失敗した場合は、--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 を無効にする必要があります。