
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초 미만이 소요됩니다. 일시 중단도 동일한 방식으로 역방향으로 작동하며, 더티 메모리 페이지만 디스크에 기록되므로 샌드박스를 일시 중지했다가 나중에 다른 프로세스에서도 재개할 수 있습니다.
AOT(사전 컴파일) 변환. 순수한 명령어 단위 에뮬레이션은 느리고, 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();
동일한 패키지가 브라우저 탭에서도 실행되며, 이 경우 스냅샷은 디스크가 아닌 origin-private 스토리지에 캐시됩니다.
[!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 3.10+ (Python SDK용)
- Node 20+ (TypeScript SDK용)
- Zig (0.16) 및 bsdtar, 스냅샷을 직접 빌드하는 경우에만 필요합니다.
개발 환경 설정
# 1회성: 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 크기를 유지하세요. 에뮬레이터가 파일 이름에서 이를 읽기 때문입니다.
스냅샷 빌드
프로젝트는 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/를 통해 환경을 유지하세요. Python 웜 스타트는 /usr/bin 또는 /bin에 python3이 포함된 musl 기반 이미지에 자동으로 적용됩니다.
풀 리퀘스트
- PR은 집중적으로 유지하세요: PR당 하나의 변경 사항.
fmt,clippy및 테스트 스위트가 통과해야 합니다 (CI가 세 가지 모두 강제).- 에뮬레이터의 실행 또는 메모리 경로를 수정하는 경우 정확성을 어떻게 검증했는지 설명하세요 (최소한 테스트 스위트; 미묘한 변경의 경우 게스트에서 부팅 및 실제 워크로드 실행이 좋은 sanity check입니다).
라이선스
이 프로젝트는 Apache License 2.0에 따라 라이선스가 부여됩니다. 자세한 내용은 LICENSE 파일을 참조하세요.