-
Notifications
You must be signed in to change notification settings - Fork 46
Add Hyperlight containment backend (Hyperlight + Unikraft micro-VM) #276
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
0114d70
Add Hyperlight containment backend variant
danbugs e3d162e
Add HyperlightScriptRunner with GHCR setup and hostfs mounts
danbugs 9dc88c3
wxc-exec: dispatch Hyperlight containment + --setup-hyperlight
danbugs d64840b
lxc-exec: dispatch Hyperlight containment + --setup-hyperlight
danbugs e461233
Add Hyperlight test configs (hello, pandas, fs)
danbugs 94dbb52
Add --with-hyperlight flag to build scripts
danbugs 3f02690
Add Hyperlight e2e tests and CI workflow
danbugs 6d666de
docs: add Hyperlight integration plan
danbugs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| name: Hyperlight E2E Tests | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main] | ||
| pull_request: | ||
| branches: [main] | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| hyperlight-e2e: | ||
| name: WXC-Exec Hyperlight | ||
| runs-on: windows-latest | ||
| timeout-minutes: 30 | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v6 | ||
|
|
||
| - name: Setup Rust toolchain | ||
| run: | | ||
| rustup update stable | ||
| rustup target add x86_64-pc-windows-msvc | ||
|
|
||
| - name: Cache Rust build artifacts | ||
| uses: Swatinem/rust-cache@v2 | ||
| with: | ||
| workspaces: src | ||
|
|
||
| - name: Ensure surrogate build consistency | ||
| shell: pwsh | ||
| run: | | ||
| $hlsExe = "src\target\x86_64-pc-windows-msvc\debug\hls\x86_64-pc-windows-msvc\debug\hyperlight_surrogate.exe" | ||
| if (-not (Test-Path $hlsExe)) { | ||
| Write-Host "Surrogate missing — clearing hyperlight-host fingerprints to force rebuild" | ||
| Get-ChildItem "src\target\x86_64-pc-windows-msvc\debug\.fingerprint" -Filter "hyperlight-host-*" -Directory -ErrorAction SilentlyContinue | | ||
| Remove-Item -Recurse -Force | ||
| } | ||
|
|
||
| - name: Build with Hyperlight support | ||
| working-directory: src | ||
| run: cargo build --features hyperlight --target x86_64-pc-windows-msvc | ||
|
|
||
| - name: Diagnose hypervisor environment | ||
| shell: pwsh | ||
| run: ./scripts/ci/diagnose-whp.ps1 | ||
|
|
||
| - name: Check Windows Hypervisor Platform | ||
| id: whp-check | ||
| shell: pwsh | ||
| run: ./scripts/ci/check-whp.ps1 | ||
|
|
||
| - name: Install crane (OCI tool) | ||
| if: steps.whp-check.outputs.whp_available == 'true' | ||
| shell: pwsh | ||
| run: | | ||
| $url = "https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Windows_x86_64.tar.gz" | ||
| Invoke-WebRequest -Uri $url -OutFile crane.tar.gz -UseBasicParsing | ||
| tar -xzf crane.tar.gz crane.exe | ||
|
|
||
| - name: Download Hyperlight kernel and initrd | ||
| if: steps.whp-check.outputs.whp_available == 'true' | ||
| shell: pwsh | ||
| run: | | ||
| $pyhlHome = Join-Path $env:LOCALAPPDATA "pyhl" | ||
| New-Item -ItemType Directory -Force -Path $pyhlHome | Out-Null | ||
|
|
||
| .\crane.exe export ghcr.io/danbugs/hyperlight-unikraft/python-agent-driver-kernel:latest kernel.tar | ||
| tar -xf kernel.tar -C $pyhlHome kernel | ||
|
|
||
| .\crane.exe export ghcr.io/danbugs/hyperlight-unikraft/python-agent-driver-initrd:latest initrd.tar | ||
| tar -xf initrd.tar -C $pyhlHome initrd.cpio | ||
|
|
||
| Write-Host "Downloaded to ${pyhlHome}:" | ||
| Get-ChildItem $pyhlHome | ||
|
|
||
| - name: Warm Hyperlight snapshot | ||
| if: steps.whp-check.outputs.whp_available == 'true' | ||
| shell: pwsh | ||
| run: | | ||
| $binDir = Join-Path $env:GITHUB_WORKSPACE "src\target\x86_64-pc-windows-msvc\debug" | ||
| $json = '{"process":{"commandLine":"print(\"snapshot warm\")"},"containment":"hyperlight"}' | ||
| $b64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($json)) | ||
| & "$binDir\wxc-exec.exe" --experimental --config-base64 $b64 2>&1 | ||
| if ($LASTEXITCODE -ne 0) { | ||
| Write-Host "::error::Hyperlight snapshot warmup failed" | ||
| exit 1 | ||
| } | ||
| Write-Host "Snapshot created:" | ||
| Get-ChildItem (Join-Path $env:LOCALAPPDATA "pyhl") | ||
|
|
||
| - name: Run Hyperlight E2E Tests | ||
| if: steps.whp-check.outputs.whp_available == 'true' | ||
| shell: pwsh | ||
| working-directory: src | ||
| run: | | ||
| cargo test -p wxc_e2e_tests --target x86_64-pc-windows-msvc test_hyperlight_suite -- --nocapture | ||
|
|
||
| - name: Upload logs on failure | ||
| if: failure() || cancelled() | ||
| uses: actions/upload-artifact@v6 | ||
| with: | ||
| name: hyperlight-e2e-logs-${{ github.event.pull_request.number || github.run_number }} | ||
| retention-days: 7 | ||
| path: | | ||
| logs/ | ||
| **/*.log |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| # MXC Hyperlight Integration — Design Document | ||
|
|
||
| ## Problem | ||
|
|
||
| MXC needs a **cross-platform micro-VM execution backend** with a good story | ||
| for agentic Python workloads that care about cold-start time. The backend | ||
| should work identically on Linux and Windows, boot in milliseconds, and | ||
| provide hardware-level isolation. | ||
|
|
||
| ## Proposed Solution | ||
|
|
||
| Add a **Hyperlight backend** — embedded [Hyperlight](https://github.com/hyperlight-dev/hyperlight) | ||
| + [Unikraft](https://unikraft.org/) driving a warmed-up CPython snapshot | ||
| via the [`hyperlight-unikraft-host`](https://github.com/hyperlight-dev/hyperlight-unikraft) library. | ||
|
|
||
| When the JSON config specifies `"containment": "hyperlight"`, `wxc-exec` | ||
| routes to `HyperlightScriptRunner`, which instantiates a Hyperlight micro-VM | ||
| directly in-process. Every `run_code(&script)` rewinds to the snapshot and runs | ||
| hermetic. | ||
|
|
||
| **Cross-platform:** KVM on Linux, WHP on Windows — same code path, same | ||
| library. | ||
|
|
||
| ## Performance | ||
|
|
||
| > Benchmarks: bare-metal Windows (Hyper-V / WHP). | ||
| > pyhl 0.1.0 (the CLI from `hyperlight-unikraft-host` for running python-agent unikernels), CPython 3.12.0, x86_64. 15 runs. | ||
|
|
||
| | Metric | Median | Avg | Min | Max | | ||
| |--------|--------|-----|-----|-----| | ||
| | Hello world (`print(42)`, end-to-end) | 139 ms | 141 ms | 133 ms | 157 ms | | ||
|
|
||
| ## Density | ||
|
|
||
| | Metric | Value | | ||
| |--------|-------| | ||
| | Per-VM memory | 17 MB | | ||
| | Shared snapshot (one-time, mapped read-only CoW) | ~650 MiB on disk (2 GiB apparent) | | ||
|
|
||
| The snapshot file is 2 GiB in apparent size but only ~650 MiB on disk | ||
| thanks to sparse-file hole-punching (`fallocate(PUNCH_HOLE)` on Linux, | ||
| `FSCTL_SET_SPARSE` on Windows). It is mmap'd read-only and shared across | ||
| all VMs — each new VM only pays for pages it actually writes. | ||
|
|
||
| ## Ecosystem | ||
|
|
||
| **Hyperlight-Unikraft** builds on two open-source foundations: | ||
|
|
||
| - **[Unikraft](https://unikraft.org/)** — Linux Foundation project with | ||
| an active community, regular releases, and commercial backing. Hyperlight | ||
| platform support has been upstreamed | ||
| ([unikraft/unikraft#1821](https://github.com/unikraft/unikraft/pull/1821), | ||
| [unikraft/app-elfloader#102](https://github.com/unikraft/app-elfloader/pull/102), | ||
| [unikraft/kraftkit#2797](https://github.com/unikraft/kraftkit/pull/2797)). | ||
| - **[Hyperlight](https://github.com/hyperlight-dev/hyperlight)** — CNCF | ||
| sandbox project. Already adopted across multiple Microsoft organizations | ||
| including Edge Actions, HorizonDB, and the Agentic Framework. | ||
|
|
||
| Beyond Python, hyperlight-unikraft supports .NET, Node.js, Go, Rust, | ||
| C/C++, PowerShell, and Bash/Shell runtimes. | ||
|
|
||
| ## Why a separate `Hyperlight` variant | ||
|
|
||
| - **Non-breaking.** Existing containment backends are unaffected. | ||
| - **Distinct semantics.** Hyperlight has: | ||
| - A pre-installed warm snapshot as a prerequisite (not just binaries). | ||
| - An in-process execution model. | ||
| - A rich stdlib (full CPython + ~20 pre-imported packages including C | ||
| extensions: numpy, pandas, Pillow, pydantic, cryptography, lxml). | ||
| - Live VFS forwarding for host filesystem access — guest POSIX calls | ||
| are forwarded to the host in real-time, limited only by host disk. | ||
| - **Different artifact provenance.** Hyperlight images come from | ||
| `hyperlight-dev/hyperlight-unikraft`'s `python-agent-driver` pipeline. | ||
| Adding new packages is a Dockerfile change + rebuild. | ||
|
|
||
| ## Design Decisions | ||
|
|
||
| 1. **In-process, not subprocess.** Hyperlight is a Rust library; wxc-exec | ||
| is a Rust binary. Linking directly avoids pipe plumbing, watchdog | ||
| threads, and process lifecycle management. | ||
|
|
||
| 2. **`script_code` is raw Python source.** No shell quoting, no cmdline | ||
| limit (`run_code` takes `&str` unbounded). | ||
|
|
||
| 3. **`--experimental` gate.** Keeps this backend off the happy path until | ||
| artifact distribution and docs catch up. | ||
|
|
||
| 4. **Unsupported policies are rejected.** A config specifying `network` | ||
| or `workingDirectory` with `containment: "hyperlight"` produces a preflight | ||
| error. | ||
|
|
||
| 5. **Image artifacts are user-provided, not bundled.** The `--setup-hyperlight` | ||
| flag populates the image home. The runner auto-discovers | ||
| `$PYHL_HOME` → `<exe>/pyhl/` → `<cwd>/.pyhl/`; the first location | ||
| with all three files wins. | ||
|
|
||
| 6. **Exit codes.** 0 on clean completion of `run_code`; -1 on any error | ||
| (preflight, runtime, guest crash). Distinct per-error variants go | ||
| through `error_message`. | ||
|
|
||
| 7. **stdout/stderr are inherited.** Guest `print(...)` reaches the user's | ||
| terminal directly via Hyperlight's `host_print`. | ||
| `ScriptResponse.standard_{out,err}` stay empty — consumers who need | ||
| capture redirect wxc-exec at the process level. | ||
|
|
||
| ## Workspace Changes | ||
|
|
||
| ``` | ||
| mxc/src/wxc_common/ | ||
| ├── Cargo.toml # + hyperlight-unikraft-host dependency | ||
| └── src/ | ||
| ├── lib.rs # + pub mod hyperlight_runner; | ||
| ├── models.rs # + ContainmentBackend::Hyperlight (serde "hyperlight") | ||
| ├── config_parser.rs # + Some("hyperlight") => Hyperlight match arm | ||
| └── hyperlight_runner.rs # NEW | ||
|
|
||
| mxc/src/wxc/ | ||
| └── src/main.rs # + ContainmentBackend::Hyperlight dispatch arm | ||
|
|
||
| mxc/test_configs/ | ||
| ├── hyperlight_hello.json # NEW — hello from Python | ||
| └── hyperlight_pandas.json # NEW — exercises pre-imported numpy/pandas | ||
|
|
||
| mxc/docs/ | ||
| └── hyperlight-integration-plan.md # NEW — this document | ||
| ``` | ||
|
|
||
| ## Configuration | ||
|
|
||
| ### JSON | ||
|
|
||
| ```json | ||
| { | ||
| "process": { | ||
| "commandLine": "import sys\nprint(f'Python {sys.version.split()[0]} on {sys.platform}')", | ||
| "timeout": 30000 | ||
| }, | ||
| "containment": "hyperlight" | ||
| } | ||
| ``` | ||
|
|
||
| ### Field semantics | ||
|
|
||
| | JSON Field | Hyperlight Behavior | | ||
| |------------|---------------| | ||
| | `process.commandLine` | ✅ Used — raw Python source | | ||
| | `process.timeout` | ✅ Used — script execution timeout (ms) | | ||
| | `containment` | ✅ Must be `"hyperlight"` | | ||
| | `filesystem.*` | ✅ `readwritePaths`/`readonlyPaths` mapped to host mounts | | ||
| | `network.*` | ❌ Rejected | | ||
| | `workingDirectory` | ❌ Rejected (guest has its own FS namespace) | | ||
| | `appContainer.*`, `sandbox.*` | ❌ N/A | | ||
|
|
||
| ## Security Model | ||
|
|
||
| | Property | Hyperlight | | ||
| |----------|------| | ||
| | Isolation level | Micro VM (KVM/WHP) | | ||
| | Host FS access | Explicit mounts via `Preopen` | | ||
| | Network | None | | ||
| | Guest OS | Unikraft unikernel | | ||
| | Cold start | ~30ms KVM / ~140 ms WHP | | ||
| | Host platforms | Linux + Windows | | ||
|
|
||
| ## Supported Workloads | ||
|
|
||
| ### Supported out of the box (preloaded in snapshot) | ||
|
|
||
| | Category | Examples | | ||
| |----------|----------| | ||
| | Stdlib | `os`, `sys`, `json`, `re`, `pathlib`, `datetime`, `hashlib`, `itertools`, `functools`, `math`, `decimal`, `fractions`, `collections`, `statistics` | | ||
| | Pre-imported 3rd-party | `numpy`, `pandas`, `pydantic`, `yaml`, `jinja2`, `bs4`, `tabulate`, `click`, `tenacity`, `tqdm`, `openpyxl`, `pypdf`, `markdown_it`, `PIL`, `lxml`, `cryptography`, `dateutil`, `dotenv` | | ||
|
|
||
| ### Not supported | ||
|
|
||
| | Why not | Example failure | | ||
| |---------|-----------------| | ||
| | No network stack in guest | `urllib`, `socket`, `http` — `OSError: Function not implemented` | | ||
| | Read-only sysroot by default | File writes under `/` — `OSError: Read-only file system` | | ||
| | No subprocess / fork | `subprocess.run` — `OSError: Function not implemented` | | ||
|
|
||
| ## Testing Strategy | ||
|
|
||
| ### Unit tests (`cargo test -p wxc_common`) | ||
|
|
||
| - `is_installed_false_on_empty_dir` — negative case for the install probe | ||
| - `resolve_home_errors_when_nothing_configured` — actionable error when no image | ||
| - `policy_rejects_filesystem_paths` — blocks readwritePaths/readonlyPaths/deniedPaths | ||
| - `policy_rejects_network_rules` — blocks allowed/blockedHosts | ||
| - `policy_rejects_block_default_network` — blocks `defaultNetworkPolicy: block` | ||
| - `policy_rejects_working_directory` — blocks non-empty `workingDirectory` | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.