
vpod v0.8.1
Лёгкие и безопасные Linux-песочницы для недоверенных процессов. Работают в браузере и на сервере.
Vpod
Что такое vpod?
vpod — это лёгкая, переносимая песочница, которая предоставляет непроверенному процессу мгновенное Linux-окружение. Она использует архитектуру RISC‑V и полностью работает внутри WebAssembly.
- Быстрый запуск: загрузка менее чем за секунду.
- Переносимость: работает где угодно без какой-либо настройки.
- Изоляция: всё состояние выполнения остаётся внутри WASM-песочниц.
Как это работает
vpod запускает полноценную RISC‑V систему (RV64GC, одно виртуальное ядро), скомпилированную в WebAssembly. Внутри загружается реальное ядро Linux с реальным пользовательским пространством, поэтому оболочки, инструменты и демоны ведут себя так же, как на реальном оборудовании.
Снимки. Вместо загрузки Linux с нуля vpod восстанавливает снимок: сохранённое состояние машины (регистры CPU, RAM, файловая система), захваченное сразу после загрузки. Восстановление занимает значительно меньше секунды. Приостановка работает аналогичным образом в обратном направлении: на диск записываются только изменённые страницы памяти, поэтому вы можете поставить песочницу на паузу и возобновить её позже, даже из другого процесса.
Упреждающая трансляция. Чистая эмуляция «инструкция за инструкцией» медленна, а WebAssembly исключает JIT во время выполнения. Поэтому на этапе сборки снимка самые горячие пути гостевого кода транслируются из RISC‑V в нативный код, который компилируется непосредственно в сам WASM-модуль. Во время выполнения эмулятор переключается на эти транслированные блоки, когда гостевой код совпадает, и возвращается к интерпретатору, когда нет. Это даёт примерно 5-кратное ускорение на задачах, интенсивно использующих CPU, без какого-либо влияния на изоляцию: транслированный код проходит через те же проверки MMU и памяти, что и интерпретируемый.
Граница WASI. WASM-компонент взаимодействует с хостом исключительно через WASI 0.2. Гость никогда не видит файловые дескрипторы, сокеты или память хоста: доступ к файловой системе осуществляется через явно смонтированные каталоги, а сеть — через пользовательский сетевой стек внутри компонента, который запрашивает у хоста только обычные исходящие сокеты. Всё остальное (гостевое ядро, процессы, память) находится внутри линейной памяти WASM и исчезает вместе с ней.
Спецификация RV64GC
G (расширения общего назначения)
- I: Базовый набор 64-битных целочисленных инструкций.
- M: Аппаратное умножение и деление, полезно для хеширования и криптографии.
- A: Атомарные операции для потокобезопасных программ.
- F/D: Числа с плавающей запятой одинарной и двойной точности, подходят для научных вычислений и ML-инференса.
C (сжатые инструкции) Уменьшает размер кода на 30%, улучшая скорость выборки инструкций и эффективность использования памяти. Это важно при запуске полного пользовательского пространства Linux в нашей ограниченной по памяти WASM-среде.
[!NOTE] Векторное расширение V не реализовано. Инструкции RVV выполнялись бы как эмулируемый RISC-V; сквозной передачи SIMD на CPU хоста нет. Добавление 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 нет аппаратной виртуализации, поэтому весь гостевой код эмулируется. Накладные расходы полностью зависят от рабочей нагрузки: задачи, связанные с вводом-выводом и сетью, работают почти с нативной скоростью, тогда как тяжёлые задачи, интенсивно использующие CPU, работают заметно медленнее даже с AOT-трансляцией. Если ваша нагрузка в основном «запустить инструмент, прочитать файл, вызвать API», вы ничего не заметите.
- Нет доступа к GPU: CUDA, Metal и аппаратные ML-ускорители недоступны. Поддержка может быть добавлена в будущем с помощью 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 управляет ею в headless-режиме.
Использование локально собранного снимка
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 в имени файла, поскольку эмулятор считывает его оттуда.
Сборка снимков
Проект использует предварительно собранные снимки Alpine из registry.vpod.sh, поэтому
обычно это не требуется. Для локальной сборки:
./scripts/build-default-snapshot.sh # dist/alpine-3.23.0-256mb.snap
./scripts/build-data-snapshot.sh # вариант 512 МБ с numpy/pandas/scipy
[!TIP] Чтобы использовать локально собранный снимок в CLI, раскомментируйте строки в
resolve_snapshot()вcrates/vpod/src/main.rs.
Сборка снимков также может выполнять AOT-проход (scripts/aot-snapshot.sh <snapshot>), который трассирует репрезентативную рабочую нагрузку, транслирует горячие блоки и пересобирает эмулятор с ними. Это занимает время; заглушка из aot-stub.sh подходит для повседневной разработки — всё работает одинаково, просто медленнее.
Из Dockerfile (macOS и Linux)
Пользовательские снимки также можно собирать из Dockerfile. Сборщик использует
CLI container от Apple на macOS и
Docker Buildx на Linux. Свежая установка macOS требует однократной настройки среды выполнения,
иначе сборка будет ждать сборщик, который никогда не запустится:
container system kernel set --recommended
container builder start
На Linux установите Docker с плагином Buildx и один раз зарегистрируйте эмуляцию 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.
При экспорте сохраняется только файловая система. ENV, CMD и ENTRYPOINT из
конфигурации образа отбрасываются, поэтому сохраняйте переменные окружения через /etc/profile.d/ в
шаге RUN. Автоматический тёплый запуск Python применяется для образов на основе musl,
в которых python3 находится в /usr/bin или /bin.
Pull requests
- Держите PR сфокусированными: одно изменение на PR.
fmt,clippyи набор тестов должны проходить (CI проверяет все три).- Если вы затрагиваете пути выполнения или памяти эмулятора, опишите, как вы проверили корректность (минимум — набор тестов; для тонких изменений хорошей проверкой будет загрузка и реальная рабочая нагрузка в госте).
Лицензия
Этот проект лицензирован под Apache License 2.0. Подробности см. в файле LICENSE.