
vpod v0.8.1
信頼できないプロセスを実行するための、軽量で安全なLinuxサンドボックス。ブラウザとサーバーの両方で動作します。
Vpod
vpod とは何か?
vpod は、信頼できないプロセスに即座にLinux環境を提供する、軽量でポータブルなサンドボックスです。RISC‑Vアーキテクチャを使用し、完全にWebAssembly内で動作します。
- 高速起動: 1秒未満で起動します。
- ポータブル: セットアップ不要でどこでも実行できます。
- 分離: すべての実行状態はWASMサンドボックス内に保持されます。
仕組み
vpod は、WebAssemblyにコンパイルされた完全なRISC‑Vシステム(RV64GC、シングルvCPU)を実行します。その内部で実際のLinuxカーネルと実際のユーザースペースが起動するため、シェル、ツール、デーモンはすべて実際のハードウェア上と同じように動作します。
スナップショット。 vpod はLinuxをゼロから起動する代わりに、スナップショット(起動直後にキャプチャされた保存済みマシン状態:CPUレジスタ、RAM、ファイルシステム)を復元します。復元には1秒もかかりません。サスペンドも同じ仕組みで逆方向に動作し、ダーティメモリページのみがディスクに書き戻されるため、サンドボックスを一時停止して後で再開できます(別のプロセスからでも可能です)。
事前翻訳(Ahead-of-time translation)。 純粋な命令単位のエミュレーションは遅く、WebAssemblyではランタイムJITが不可能です。そのため、スナップショットのビルド時に、最もホットなゲストコードパスがRISC‑Vからネイティブコードに翻訳され、それがWASMモジュール自体にコンパイルされます。実行時には、エミュレータはゲストコードが一致した場合にこれらの翻訳済みブロックにディスパッチし、一致しない場合はインタープリタにフォールバックします。これによりCPUバウンドな処理で約5倍の高速化が得られ、分離性にはまったく影響しません。翻訳済みコードはインタープリタコードと同じMMUおよびメモリチェックを通過します。
WASI境界。 WASMコンポーネントはWASI 0.2を通じてのみホストと通信します。ゲストがホストのファイルディスクリプタ、ソケット、メモリを見ることは決してありません。ファイルシステムアクセスは明示的にマウントされたディレクトリを通じて行われ、ネットワーキングはコンポーネント内部のユーザーモードネットワークスタックを通じて行われ、ホストに対してプレーンなアウトバウンドソケットのみを要求します。その他すべて(ゲストカーネル、プロセス、メモリ)はWASMリニアメモリ内に存在し、それとともに消滅します。
RV64GC仕様
G(汎用拡張)
- I: 基本64ビット整数命令セット。
- M: ハードウェア乗算・除算。ハッシュや暗号化に有用。
- A: スレッドセーフなプログラムのためのアトミック操作。
- F/D: 単精度および倍精度浮動小数点。科学計算やML推論に適しています。
C(圧縮命令) コードサイズを30%削減し、命令フェッチ速度とメモリ効率を向上させます。これは、メモリ制約のあるWASM環境内で完全なLinuxユーザースペースを実行する場合に重要です。
[!NOTE] V(ベクター)拡張は実装されていません。RVV命令はエミュレートされたRISC-Vとして実行されます。ホストCPUへのSIMDパススルーはありません。Vを追加すると、ベクターワークロードに対するパフォーマンス上の利点なしにエミュレーションオーバーヘッドが増加します。
はじめに
TypeScript SDK
npm install @capsule-run/vpod
import { Sandbox } from "@capsule-run/vpod";
const sandbox = await Sandbox.create();
// 状態は呼び出し間で保持されます
await sandbox.commands.run("export API_KEY=secret");
const key = await sandbox.commands.run("echo $API_KEY");
console.log(key.stdout); // secret
// Python REPL — 変数は保持されます
await sandbox.code.run("data = [1, 2, 3]");
const total = await sandbox.code.run("print(sum(data))");
console.log(total.text); // 6
await sandbox.close();
同じパッケージがブラウザタブでも動作し、その場合スナップショットはディスクではなくオリジンプライベートストレージにキャッシュされます。
[!IMPORTANT]
Sandbox.create()への最初の呼び出しで、デフォルトのスナップショット(alpine)がダウンロードされ、まだ存在しない場合はローカルにキャッシュされます。
Python SDK
pip install vpod
from vpod import Sandbox
# コマンドを実行
sandbox = Sandbox.create()
result = sandbox.commands.run("whoami")
print(result.stdout) # root
sandbox.close()
# 永続セッション — 状態は呼び出し間で保持されます
with Sandbox.create() as sandbox:
sandbox.commands.run("export API_KEY=secret")
result = sandbox.commands.run("echo $API_KEY")
print(result.stdout) # secret
# Python REPL — 変数は保持されます
with Sandbox.create() as sandbox:
sandbox.code.run("import requests")
sandbox.code.run("data = [1, 2, 3]")
result = sandbox.code.run("print(sum(data))")
print(result.text) # 6
CLI
curl -fsSL https://install.vpod.sh | sh
またはPowerShellでインストール(Windows)
irm https://install.vpod.sh | iex
# スナップショットを取得
vpod pull alpine:latest
# インタラクティブシェルを開始
vpod
ドキュメント
Vpodドキュメントをご覧ください。
制限事項
- エミュレーションオーバーヘッド: WebAssembly内にはハードウェア仮想化がないため、すべてのゲストコードがエミュレートされます。オーバーヘッドはワークロードに完全に依存します。I/Oバウンドおよびネットワークバウンドの処理はネイティブに近い速度で実行されますが、CPUバウンドの重い処理はAOT翻訳があっても著しく遅くなります。ワークロードが「ツールを実行し、ファイルを読み、APIを呼び出す」ことが中心であれば、違いに気づくことはないでしょう。
- GPUアクセスなし: CUDA、Metal、ハードウェアMLアクセラレータは利用できません。将来、wasi-nnでサポートが追加される可能性があります。
コントリビューション
バグレポートから新しいデバイスサポートまで、コントリビューションを歓迎します。何か重要なことを構築する前に、イシューを開いて議論してください。
前提条件
- Rust(最新の安定版)と
wasm32-wasip2ターゲット:rustup target add wasm32-wasip2 - Python SDK用のPython 3.10+
- TypeScript SDK用のNode 20+
- Zig(0.16)と bsdtar。スナップショットを自分でビルドする場合のみ必要です。
開発環境のセットアップ
# 一度だけ: AOTスタブを生成(新しいクローンには翻訳済みブロックがありません)
./scripts/aot-stub.sh
# WASMコンポーネントをビルド(ライブラリ + CLI)。両方のティアを sdks/python/vpod/ にコピーします
./scripts/build-wasm.sh
# ホストCLIをインストール
cargo install --path crates/vpod
# Python SDKを開発モードでインストール
pip install -e "sdks/python[dev]"
# TypeScript SDKをビルド。Python SDKディレクトリからコンポーネントを取得します
cd sdks/typescript && npm install && npm run build
npm run build はデフォルトで --tier aot を使用します。CIは --tier base を固定します。ビルドは crates/ 配下の最新ファイルより古いコンポーネントを拒否するため、エミュレータに触れた後は ./scripts/build-wasm.sh を再実行してください。エミュレータの変更はゲストを通じてのみ表面化するため、古いコンポーネントでもコンパイルされ、ほぼすべてのテストに合格します。
テストの実行
CIはすべてのPRでこれらを実行するため、プッシュ前に実行してください:
cargo fmt --all -- --check # フォーマット
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all # Rustテスト
# Python SDK統合テスト(WASMライブラリが必要です)
cp target/wasm32-wasip2/release/vpod_wasi_lib.wasm sdks/python/vpod/
pytest sdks/python/tests/ -v -m integration
# TypeScript SDK(sdks/typescript から)
npm run typecheck
npm test # ユニット
npm run test:all # ユニット + 統合、ローカルスナップショットが必要
npm run test:perf # ゲスト時間の回帰、正確な定数
TypeScriptテストは src/ ではなくビルド済みの dist/ をインポートするため、実行前にビルドしてください。共有キャッシュディレクトリ内のスナップショットを探します。VPOD_TEST_SNAPSHOT=/path/to/x.snap で別の場所を指定できます。
ブラウザをエンドツーエンドでテストするには、npm run dev でCOOP/COEPを有効にしてページを提供し、node dev/run-network.mjs --browser chrome でヘッドレスで駆動します。
ローカルでビルドしたスナップショットの使用
SDKはデフォルトで registry.vpod.sh から取得します。自分でビルドしたものを実行するには、レジストリ名の代わりに直接渡します:
// TypeScript: ディスク上のファイル(Node)、またはバイト(任意の場所)
await Sandbox.create({ snapshot: { path: "./dist/alpine-3.23.0-256mb.snap" } });
await Sandbox.create({ snapshot: { bytes, name: "alpine-3.23.0-256mb.snap" } });
# Python: VPOD_SNAPSHOT=/path/to/x.snap
どちらの場合も、エミュレータがそこからRAMサイズを読み取るため、ファイル名にRAMサイズを保持してください。
スナップショットのビルド
このプロジェクトは registry.vpod.sh のプリビルド済みAlpineスナップショットを使用するため、通常はこれを必要としません。ローカルでビルドするには:
./scripts/build-default-snapshot.sh # dist/alpine-3.23.0-256mb.snap
./scripts/build-data-snapshot.sh # numpy/pandas/scipyを含む512 MBバリアント
[!TIP] CLIでローカルにビルドしたスナップショットを使用するには、
crates/vpod/src/main.rsのresolve_snapshot()内の行のコメントを解除してください。
スナップショットビルドではAOTパス(scripts/aot-snapshot.sh <snapshot>)も実行でき、代表的なワークロードをトレースし、ホットブロックを翻訳して、それらを組み込んだエミュレータを再ビルドします。時間がかかります。aot-stub.sh のスタブは日常の開発には十分で、すべて同じように動作しますが、遅いだけです。
Dockerfileから(macOSおよびLinux)
カスタムスナップショットはDockerfileからもビルドできます。ビルダーはmacOSではAppleの container CLIを、LinuxではDocker Buildxを使用します。新しいmacOSインストールではランタイムを一度設定する必要があります。そうしないと、ビルドは起動しないビルダーを待って停止します:
container system kernel set --recommended
container builder start
Linuxでは、Buildxプラグイン付きのDockerをインストールし、ホストがクロスプラットフォームビルド用にまだ設定されていない場合は、riscv64エミュレーションを一度登録します:
docker run --privileged --rm tonistiigi/binfmt --install riscv64
./scripts/build-custom-snapshot.sh -f Dockerfile -n my-image # dist/my-image-256mb.snap
# オプション: --aot --trace-cmd '<イメージのホットコマンド>' でAOTブロックを組み込みます
Dockerfileは linux/riscv64 用にビルドされ(BuildKitはRUNステップをエミュレーション下で実行)、フラット化されたrootfsがAlpine minirootfsを置き換え、残りのパイプラインは同一です:vpodオーバーレイ、起動、--snapshot-save。
エクスポートで生き残るのはファイルシステムのみです。イメージ設定からの ENV、CMD、ENTRYPOINT は破棄されるため、RUN ステップで /etc/profile.d/ を通じて環境を永続化してください。/usr/bin または /bin に python3 を同梱するmuslベースのイメージには、Pythonのウォームスタートが自動的に適用されます。
プルリクエスト
- PRは焦点を絞ってください:1つのPRにつき1つの変更。
fmt、clippy、テストスイートが合格する必要があります(CIが3つすべてを強制します)。- エミュレータの実行パスまたはメモリパスに触れる場合は、正確性をどのように検証したかを説明してください(最低限テストスイート。微妙な変更の場合は、ゲスト内での起動と実際のワークロードが良い健全性チェックになります)。
ライセンス
このプロジェクトはApache License 2.0の下でライセンスされています。 詳細はLICENSEファイルをご覧ください。