返回更新列表
新发布Sep 3, 2026

vpod v0.8.1

轻量级、安全的 Linux 沙箱,用于隔离不受信任的进程。可在浏览器和服务器上运行。

分享

Vpod

什么是 vpod

vpod 是一个轻量级、可移植的沙箱,可为不受信任的进程提供即时的 Linux 环境。它采用 RISC‑V 架构,并完全运行在 WebAssembly 内部。

  • 快速启动:不到一秒即可完成启动。
  • 可移植:无需任何安装配置即可在任何地方运行。
  • 隔离性:所有执行状态都保留在 WASM 沙箱内部。

工作原理

vpod 运行一个完整的 RISC‑V 系统(RV64GC,单 vCPU),该系统被编译为 WebAssembly。其内部启动一个真实的 Linux 内核和真实的用户空间,因此 shell、工具和守护进程的行为与在真实硬件上完全一致。

快照。 vpod 并非从头启动 Linux,而是恢复一个快照:即在启动后立即捕获的已保存机器状态(CPU 寄存器、内存、文件系统)。恢复快照耗时远低于一秒。挂起(Suspend)以相反的方式工作,仅将脏内存页写回磁盘,因此您可以暂停沙箱并在之后恢复它,甚至可以从另一个进程恢复。

提前(AOT)翻译。 纯逐指令模拟速度较慢,而 WebAssembly 又排除了运行时 JIT 的可能性。因此,在快照构建时,最热门的客户机代码路径会从 RISC‑V 翻译为原生代码,并编译进 WASM 模块本身。在运行时,当客户机代码匹配时,模拟器会分派到这些翻译后的代码块中执行;不匹配时则回退到解释器。这对于 CPU 密集型工作负载大约有 5 倍的性能提升,且对隔离性零影响:翻译后的代码与解释执行的代码一样,都要经过相同的 MMU 和内存检查。

WASI 边界。 WASM 组件仅通过 WASI 0.2 与宿主机通信。客户机永远看不到宿主机的文件描述符、套接字或内存:文件系统访问通过显式挂载的目录进行,网络访问则通过组件内部的用户态网络栈实现,该网络栈仅向宿主机请求普通的出站套接字。其他所有内容(客户机内核、进程、内存)都位于 WASM 线性内存中,并随其消亡。

RV64GC 规范

G(通用扩展)

  • I:基础 64 位整数指令集。
  • M:硬件乘法和除法,适用于哈希和加密。
  • A:原子操作,用于线程安全程序。
  • F/D:单精度和双精度浮点,适用于科学计算和机器学习推理。

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();

同一个包也可在浏览器标签页中运行,此时快照缓存在源私有存储(origin-private storage)中,而非磁盘上。

[!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

# 启动交互式 shell
vpod

文档

访问 Vpod 文档

限制

  • 模拟开销:WebAssembly 内部没有硬件虚拟化,因此所有客户机代码都是模拟执行的。开销完全取决于工作负载:I/O 密集型和网络密集型工作负载的运行速度接近原生速度,而重度 CPU 密集型工作负载即使有 AOT 翻译也会明显变慢。如果您的工作负载主要是“运行工具、读取文件、调用 API”,您不会注意到差异。
  • 无 GPU 访问:CUDA、Metal 和硬件机器学习加速器不可用。未来可能会通过 wasi-nn 添加支持。

参与贡献

欢迎各种形式的贡献,从错误报告到新设备支持。在构建任何实质性内容之前,请先开启一个 issue 进行讨论。

前置要求

  • Rust(最新稳定版),带有 wasm32-wasip2 目标:rustup target add wasm32-wasip2
  • Python 3.10+,用于 Python SDK
  • Node 20+,用于 TypeScript SDK
  • 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 测试导入的是构建后的 dist/,而非 src/,因此请先构建再运行测试。它们会在共享缓存目录中查找快照;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 大小,因为模拟器会从文件名中读取该信息。

构建快照

该项目使用来自 registry.vpod.sh 的预构建 Alpine 快照,因此您通常不需要此操作。要在本地构建一个:

./scripts/build-default-snapshot.sh   # dist/alpine-3.23.0-256mb.snap
./scripts/build-data-snapshot.sh      # 512 MB 变体,包含 numpy/pandas/scipy

[!TIP] 要在 CLI 中使用本地构建的快照,请取消注释 crates/vpod/src/main.rsresolve_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 步骤),其扁平化的根文件系统替换 Alpine minirootfs,其余流程完全相同:vpod overlay、启动、--snapshot-save

只有文件系统会在导出后保留。镜像配置中的 ENVCMDENTRYPOINT 会被丢弃,因此请通过 RUN 步骤中的 /etc/profile.d/ 持久化环境变量。对于随附 /usr/bin/binpython3 的基于 musl 的镜像,会自动应用 Python 热启动。

拉取请求

  • 保持 PR 聚焦:每个 PR 只做一项更改。
  • fmtclippy 和测试套件必须通过(CI 强制执行这三项)。
  • 如果您修改了模拟器的执行或内存路径,请说明您如何验证正确性(至少运行测试套件;对于细微更改,在客户机中执行一次启动加真实工作负载是很好的健全性检查)。

许可证

本项目采用 Apache License 2.0 许可证。 详情请参阅 LICENSE 文件。

分类