
vpod v0.8.1
Leichtgewichtige, sichere Linux-Sandboxes für nicht vertrauenswürdige Prozesse. Läuft im Browser und auf dem Server.
Vpod
Was ist ein vpod?
Ein vpod ist eine leichtgewichtige, portable Sandbox, die einem nicht vertrauenswürdigen Prozess eine sofortige Linux-Umgebung bietet. Sie verwendet eine RISC‑V-Architektur und läuft vollständig in WebAssembly.
- Schneller Start: Bootet in unter einer Sekunde.
- Portabel: Läuft überall ohne jegliche Einrichtung.
- Isoliert: Der gesamte Ausführungszustand bleibt in den WASM-Sandboxes.
So funktioniert es
Ein vpod führt ein vollständiges RISC‑V-System (RV64GC, einzelne vCPU) aus, das zu WebAssembly kompiliert wurde. Darin bootet ein echtes Linux-Kernel mit einem echten Userspace, sodass sich Shells, Tools und Daemons genau so verhalten wie auf echter Hardware.
Snapshots. Statt Linux von Grund auf zu booten, stellt ein vpod einen Snapshot wieder her: einen gespeicherten Maschinenzustand (CPU-Register, RAM, Dateisystem), der direkt nach dem Boot erfasst wurde. Die Wiederherstellung dauert deutlich unter einer Sekunde. Suspend funktioniert auf die gleiche Weise in umgekehrter Richtung – nur schmutzige Speicherseiten werden zurück auf die Festplatte geschrieben, sodass Sie eine Sandbox pausieren und später fortsetzen können, sogar aus einem anderen Prozess.
Ahead-of-time-Übersetzung. Reine Befehls-für-Befehls-Emulation ist langsam, und WebAssembly schließt einen Laufzeit-JIT aus. Daher werden beim Erstellen des Snapshots die heißesten Gast-Codepfade von RISC‑V in nativen Code übersetzt, der direkt in das WASM-Modul kompiliert wird. Zur Laufzeit leitet der Emulator in diese übersetzten Blöcke um, wenn der Gastcode übereinstimmt, und fällt auf den Interpreter zurück, wenn dies nicht der Fall ist. Das bringt etwa 5x bei CPU-gebundener Arbeit, ohne Auswirkungen auf die Isolation: Übersetzter Code durchläuft dieselben MMU- und Speicherprüfungen wie interpretierter Code.
Die WASI-Grenze. Die WASM-Komponente kommuniziert mit dem Host ausschließlich über WASI 0.2. Der Gast sieht niemals Host-Dateideskriptoren, Sockets oder Speicher: Dateisystemzugriff erfolgt über explizit gemountete Verzeichnisse, und Netzwerkzugriff läuft über einen User-Mode-Netzwerkstack innerhalb der Komponente, der den Host nur um einfache ausgehende Sockets bittet. Alles andere (Gast-Kernel, Prozesse, Speicher) lebt im linearen WASM-Speicher und stirbt mit ihm.
RV64GC-Spezifikation
G (Allzweck-Erweiterungen)
- I: Basis-Befehlssatz für 64-Bit-Ganzzahlen.
- M: Hardware-Multiplikation und -Division, nützlich für Hashing und Kryptografie.
- A: Atomare Operationen für threadsichere Programme.
- F/D: Gleitkommazahlen mit einfacher und doppelter Genauigkeit, geeignet für wissenschaftliches Rechnen und ML-Inferenz.
C (Komprimierte Befehle) Reduziert die Codegröße um 30 %, verbessert die Befehlsabrufgeschwindigkeit und die Speichereffizienz. Das ist wichtig, wenn ein vollständiger Linux-Userspace in unserer speicherbeschränkten WASM-Umgebung läuft.
[!NOTE] Die V-Erweiterung (Vektor) ist nicht implementiert. RVV-Befehle würden als emuliertes RISC-V ausgeführt; es gibt kein SIMD-Passthrough zur Host-CPU. Das Hinzufügen von V würde den Emulationsaufwand erhöhen, ohne einen Leistungsvorteil für vektorisierte Workloads zu bringen.
Erste Schritte
TypeScript-SDK
npm install @capsule-run/vpod
import { Sandbox } from "@capsule-run/vpod";
const sandbox = await Sandbox.create();
// Der Zustand bleibt über Aufrufe hinweg erhalten
await sandbox.commands.run("export API_KEY=secret");
const key = await sandbox.commands.run("echo $API_KEY");
console.log(key.stdout); // secret
// Python-REPL — Variablen bleiben erhalten
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();
Dasselbe Paket läuft in einem Browser-Tab, wo der Snapshot im origin-privaten Speicher statt auf der Festplatte zwischengespeichert wird.
[!IMPORTANT] Der erste Aufruf von
Sandbox.create()lädt den Standard-Snapshot (alpine) herunter und speichert ihn lokal zwischen, falls er nicht bereits vorhanden ist.
Python-SDK
pip install vpod
from vpod import Sandbox
# Einen Befehl ausführen
sandbox = Sandbox.create()
result = sandbox.commands.run("whoami")
print(result.stdout) # root
sandbox.close()
# Persistente Sitzung — Zustand bleibt über Aufrufe hinweg erhalten
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 — Variablen bleiben erhalten
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
Oder per PowerShell installieren (Windows)
irm https://install.vpod.sh | iex
# Einen Snapshot ziehen
vpod pull alpine:latest
# Eine interaktive Shell starten
vpod
Dokumentation
Besuchen Sie die Vpod-Dokumentation.
Einschränkungen
- Emulationsaufwand: Es gibt keine Hardware-Virtualisierung in WebAssembly, daher wird der gesamte Gastcode emuliert. Der Aufwand hängt vollständig vom Workload ab: I/O-gebundene und netzwerkgebundene Arbeit läuft nahezu in nativer Geschwindigkeit, während stark CPU-gebundene Arbeit auch mit AOT-Übersetzung spürbar langsamer läuft. Wenn Ihr Workload hauptsächlich aus „Tool ausführen, Datei lesen, API aufrufen“ besteht, werden Sie es nicht bemerken.
- Kein GPU-Zugriff: CUDA, Metal und Hardware-ML-Beschleuniger sind nicht verfügbar. Unterstützung könnte in Zukunft mit wasi-nn hinzugefügt werden.
Mitwirken
Beiträge sind willkommen, von Fehlerberichten bis zur Unterstützung neuer Geräte. Öffnen Sie ein Issue, um alles Wesentliche zu besprechen, bevor Sie es bauen.
Voraussetzungen
- Rust (aktuelles Stable) mit dem
wasm32-wasip2-Target:rustup target add wasm32-wasip2 - Python 3.10+ für das Python-SDK
- Node 20+ für das TypeScript-SDK
- Zig (0.16) und bsdtar, nur benötigt, wenn Sie Snapshots selbst bauen
Entwicklungseinrichtung
# Einmalig: AOT-Stub generieren (ein frischer Klon hat keine übersetzten Blöcke)
./scripts/aot-stub.sh
# Die WASM-Komponente bauen (Bibliothek + CLI). Kopiert beide Stufen in sdks/python/vpod/
./scripts/build-wasm.sh
# Die Host-CLI installieren
cargo install --path crates/vpod
# Das Python-SDK im Entwicklermodus installieren
pip install -e "sdks/python[dev]"
# Das TypeScript-SDK bauen. Übernimmt die Komponente aus dem Python-SDK-Verzeichnis
cd sdks/typescript && npm install && npm run build
npm run build verwendet standardmäßig --tier aot; CI pinnt --tier base. Der Build
verweigert eine Komponente, die älter ist als die neueste Datei unter crates/, führen Sie also
./scripts/build-wasm.sh erneut aus, nachdem Sie den Emulator berührt haben. Eine Emulatoränderung zeigt sich nur
über den Gast, daher kompiliert eine veraltete Komponente und besteht fast alles.
Tests ausführen
CI führt diese bei jedem PR aus, führen Sie sie also vor dem Pushen aus:
cargo fmt --all -- --check # Formatierung
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all # Rust-Tests
# Python-SDK-Integrationstests (benötigt die WASM-Bibliothek an Ort und Stelle)
cp target/wasm32-wasip2/release/vpod_wasi_lib.wasm sdks/python/vpod/
pytest sdks/python/tests/ -v -m integration
# TypeScript-SDK (aus sdks/typescript)
npm run typecheck
npm test # Unit
npm run test:all # Unit + Integration, benötigt einen lokalen Snapshot
npm run test:perf # Gastzeit-Regressionen, exakte Konstanten
Die TypeScript-Tests importieren das gebaute dist/, nicht src/, bauen Sie also vor dem
Ausführen. Sie suchen nach einem Snapshot im gemeinsamen Cache-Verzeichnis;
VPOD_TEST_SNAPSHOT=/pfad/zu/x.snap zeigt auf einen anderen Ort.
Um den Browser Ende-zu-Ende zu testen, dient npm run dev die Seite mit aktiviertem COOP/COEP
aus und node dev/run-network.mjs --browser chrome steuert sie headless.
Einen lokal gebauten Snapshot verwenden
Die SDKs ziehen standardmäßig von registry.vpod.sh. Um einen selbst gebauten zu verwenden,
übergeben Sie ihn direkt statt eines Registry-Namens:
// TypeScript: eine Datei auf der Festplatte (Node) oder Bytes (überall)
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=/pfad/zu/x.snap
Behalten Sie die RAM-Größe in jedem Fall im Dateinamen, da der Emulator sie daraus liest.
Snapshots bauen
Das Projekt verwendet vorgefertigte Alpine-Snapshots von registry.vpod.sh, daher benötigen Sie dies normalerweise nicht. Um einen lokal zu bauen:
./scripts/build-default-snapshot.sh # dist/alpine-3.23.0-256mb.snap
./scripts/build-data-snapshot.sh # 512-MB-Variante mit numpy/pandas/scipy
[!TIP] Um einen lokal gebauten Snapshot in der CLI zu verwenden, kommentieren Sie die Zeilen in
resolve_snapshot()incrates/vpod/src/main.rsaus.
Snapshot-Builds können auch den AOT-Durchlauf ausführen (scripts/aot-snapshot.sh <snapshot>), der einen repräsentativen Workload verfolgt, die heißen Blöcke übersetzt und den Emulator mit diesen eingebettet neu baut. Das dauert eine Weile; der Stub von aot-stub.sh ist für die tägliche Entwicklung ausreichend, alles funktioniert gleich, nur langsamer.
Aus einem Dockerfile (macOS und Linux)
Benutzerdefinierte Snapshots können auch aus einem Dockerfile gebaut werden. Der Builder verwendet
Apples container-CLI auf macOS und
Docker Buildx auf Linux. Eine frische macOS-Installation muss ihre Laufzeit einmal konfigurieren,
sonst wartet der Build auf einen Builder, der nie startet:
container system kernel set --recommended
container builder start
Auf Linux installieren Sie Docker mit dem Buildx-Plugin und registrieren die riscv64-Emulation einmal, wenn der Host nicht bereits für plattformübergreifende Builds konfiguriert ist:
docker run --privileged --rm tonistiigi/binfmt --install riscv64
./scripts/build-custom-snapshot.sh -f Dockerfile -n my-image # dist/my-image-256mb.snap
# optional: --aot --trace-cmd '<der heiße Befehl des Images>' zum Einbetten von AOT-Blöcken
Das Dockerfile wird für linux/riscv64 gebaut (BuildKit führt RUN-Schritte
unter Emulation aus), sein abgeflachtes Rootfs ersetzt das Alpine-Minirootfs,
und der Rest der Pipeline ist identisch: vpod-Overlay, Boot,
--snapshot-save.
Nur das Dateisystem überlebt den Export. ENV, CMD und ENTRYPOINT aus der
Image-Konfiguration werden verworfen, persistieren Sie die Umgebung also über /etc/profile.d/ in
einem RUN-Schritt. Der Python-Warmstart wird automatisch für musl-basierte Images angewendet,
die python3 in /usr/bin oder /bin mitbringen.
Pull Requests
- Halten Sie PRs fokussiert: eine Änderung pro PR.
fmt,clippyund die Testsuite müssen bestehen (CI erzwingt alle drei).- Wenn Sie die Ausführungs- oder Speicherpfade des Emulators berühren, geben Sie an, wie Sie die Korrektheit validiert haben (mindestens die Testsuite; bei subtilen Änderungen sind ein Boot plus ein realer Workload im Gast ein guter Sanity-Check).
Lizenz
Dieses Projekt ist unter der Apache License 2.0 lizenziert. Siehe die Datei LICENSE für Details.