Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,11 @@ jobs:
sudo modprobe loop
sudo modprobe erofs
sudo modprobe br_netfilter
sudo modprobe kvm
sudo modprobe kvm_intel || sudo modprobe kvm_amd || true
test -c /dev/net/tun
test -c /dev/kvm
sudo chmod 0666 /dev/kvm
sudo sysctl -w net.bridge.bridge-nf-call-iptables=1

- name: Build all-in-one image
Expand Down Expand Up @@ -166,6 +170,25 @@ jobs:
timeout 120s python "sdk/python/examples/${example}"
done

- name: Run runsc and Firecracker checkpoint restore E2E
run: |
gateway_ip="$(docker inspect \
--format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
akernel-traefik)"
test -n "${gateway_ip}"
token="$(cat deploy/standalone/data/token)"
export AKERNEL_TOKEN="${token}"
export AKERNEL_SERVER_ADDRESS="${gateway_ip}"
export AKERNEL_RUN_INTEGRATION=1
export PYTHONPATH="${GITHUB_WORKSPACE}/sdk/python"

for runtime in runsc firecracker; do
echo "=== Checkpoint/restore runtime=${runtime} ==="
AKERNEL_TEST_RUNTIME="${runtime}" timeout 300s python \
sdk/python/tests/integration/test_sandbox.py \
SandboxCheckpointIntegrationTest -v
done

- name: Show standalone diagnostics
if: failure()
run: |
Expand Down
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,12 @@ quotas for runsc and Firecracker use this local-disk filestore. Without an
explicit quota, runsc retains its configured memory-backed overlay while
Firecracker creates its configured sparse ext4 default.

The bundled node enables YuanRong's sandbox snapshot data plane with the
DataSystem backend and uses `/home/yuanrong/checkpoints` as node-local staging.
The public SDK does not expose snapshot TTLs: reusable checkpoints remain
until explicitly deleted. Keep the DataSystem backend and checkpoint staging
configuration enabled together when changing node startup arguments.

Keep detailed SDK reference material with the SDK. The root README should
contain only the project-level entry points and representative examples:

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ See the complete [basic usage example](./sdk/python/examples/basic_usage.py), th
- [x] Optional native Linux runc runtime
- [x] Sandbox network ACL
- [ ] Fork-based sandbox launch based on gVisor
- [ ] Sandbox checkpoint and restore
- [x] Sandbox checkpoint and restore for runsc and Firecracker
- [ ] Support for GKE and AWS
- [x] Cgroup v2 node support

Expand Down
8 changes: 8 additions & 0 deletions builder/scripts/yr_node_bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ resolve_node_ip() {

YR_NODE_IP="$(resolve_node_ip)"
echo "Using ${YR_NODE_IP} as the YuanRong node address"
CHECKPOINT_DIR="/home/yuanrong/checkpoints"
mkdir -p "${CHECKPOINT_DIR}"

# Select the legacy etcd registry or the FunctionMaster HTTP provider.
if [ "${TRAEFIK_MODE:-etcd}" = "etcd" ]; then
Expand Down Expand Up @@ -107,6 +109,9 @@ if [ "x${AKS_LOCAL_MODE}" == "xtrue" ]; then
--iam_local_ip 127.0.0.1 \
--frontend_lease_bypass true \
--force_low_reliability_instance true \
--enable_sandbox_pause_resume true \
--snapshot_storage_backend datasystem \
--checkpoint_dir "${CHECKPOINT_DIR}" \
--enable_sandbox_router true \
--enable_direct_routing false
else
Expand Down Expand Up @@ -145,5 +150,8 @@ else
--function_proxy_merge_process_enable true \
--enable_direct_routing false \
--force_low_reliability_instance true \
--enable_sandbox_pause_resume true \
--snapshot_storage_backend datasystem \
--checkpoint_dir "${CHECKPOINT_DIR}" \
--block true
fi
9 changes: 0 additions & 9 deletions deploy/akernel/charts/core/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -379,15 +379,6 @@ node:
"noexec",
"nodev"
]
},
{
"destination": "/etc/resolv.conf",
"type": "bind",
"source": "/etc/resolv_akernel.conf",
"options": [
"bind",
"ro"
]
}
],
"linux": {
Expand Down
6 changes: 6 additions & 0 deletions deploy/standalone/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ rather than tmpfs. Without `storage_mb`, runsc retains its configured
memory-backed overlay while Firecracker uses its configured sparse ext4
default.

Sandbox checkpoints for runsc and Firecracker are published through the
embedded YuanRong DataSystem. `/home/yuanrong/checkpoints` is only the node's
local staging directory; SDK checkpoint records have no automatic TTL and are
retained until `Sandbox.delete_checkpoint()` is called. A restored sandbox is
a new sandbox and receives fresh network routes.

`start.sh` loads the host `tun` module and verifies `/dev/net/tun` before
starting the pooled-TAP runtimes. Runc retains its separate veth network path.

Expand Down
9 changes: 0 additions & 9 deletions deploy/standalone/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -232,15 +232,6 @@
"noexec",
"nodev"
]
},
{
"destination": "/etc/resolv.conf",
"type": "bind",
"source": "/etc/resolv.conf",
"options": [
"bind",
"ro"
]
}
],
"linux": {
Expand Down
58 changes: 58 additions & 0 deletions sdk/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ It supports two backends:
- [Filesystem](#filesystem)
- [Interactive PTYs](#interactive-ptys)
- [Port forwarding](#port-forwarding)
- [Checkpoint and restore](#checkpoint-and-restore)
- [Reverse tunnels](#reverse-tunnels)
- [Rootfs and mounts](#rootfs-and-mounts)
- [Launch from a Dockerfile](#launch-from-a-dockerfile)
Expand Down Expand Up @@ -352,6 +353,61 @@ with Sandbox(port_forwardings=[8080]) as sandbox:
deployment operator explicitly wants the direct Traefik address instead of the
public gateway.

## Checkpoint and restore

Create an immutable checkpoint of a running sandbox and restore it as a new,
independent sandbox:

```python
from akernel_sdk import Sandbox

checkpoint = None
try:
with Sandbox(runtime="runsc") as source:
source.commands.run("printf before > /tmp/state && sync")
checkpoint = source.checkpoint(timeout=180)
source.commands.run("printf after > /tmp/state")

with Sandbox.restore(checkpoint) as restored:
assert restored.id != source.id
assert restored.commands.run("cat /tmp/state").stdout == "before"
finally:
if checkpoint is not None:
Sandbox.delete_checkpoint(checkpoint)
```

`checkpoint()` keeps the source running by default. Set
`leave_running=False` to terminate it after the checkpoint commits. Checkpoints
do not expire and must be removed explicitly with `delete_checkpoint()`;
`list_checkpoints()` returns all checkpoint identities visible to the current
tenant.

Each restore gets a new sandbox ID, placement, network attachment, and routes.
The runtime, root filesystem, resources, mounts, environment, network policy,
and filesystem/process state come from the checkpoint. v1 does not support
in-place rollback or restore-time resource and configuration overrides.

The bundled backend supports checkpoints for runsc and Firecracker. A restore
must use compatible runtime binaries, architecture, kernel, and runtime
configuration. The cluster prefers the source node when it is available and
may fall back to another compatible node through the configured snapshot
storage.

For a checkpoint created from a sandbox with a reverse tunnel, pass an
explicit `reverse_tunnel` to `restore()` using the same `reverse_port` and
`listen_port`. The target and connection timeout may change. A checkpoint made
without a tunnel rejects adding one during restore. The source tunnel is
briefly disconnected during checkpoint creation and then reconnected.

Checkpoint/restore is available through the default `openyuanrong-sandbox`
backend. The legacy `openyuanrong-sdk` actor backend reports it as unsupported.
The current official backend package supports the default 180-second
checkpoint timeout. Custom checkpoint timeouts and checkpointing a sandbox
with an active reverse tunnel require a backend release containing the
corresponding YuanRong changes.
See [`examples/checkpoint_restore.py`](./examples/checkpoint_restore.py) for a
runnable example.

## Reverse tunnels

A reverse tunnel lets sandbox code call an HTTP or HTTPS service reachable
Expand Down Expand Up @@ -519,6 +575,7 @@ Maintained examples are under [`examples/`](./examples):

- `basic_usage.py`
- `command_stdin.py`
- `checkpoint_restore.py`
- `custom_image.py`
- `dockerfile_launch.py`
- `gpu_sandbox.py`
Expand Down Expand Up @@ -556,6 +613,7 @@ not part of the default test suite.
| `CommandInfo` | `pid`, `command`, `running` |
| `EntryInfo` | `name`, `path`, `type`, `size`, `permissions`, `modified_time` |
| `SandboxInfo` | `id`, `state`, `cpu`, `memory`, `image`, `xpu`, `storage_mb` |
| `CheckpointInfo` | `id` |
| `NodeInfo` | `id`, `status`, `capacity`, `allocatable`, `labels` |
| `S3Config` | `endpoint`, `bucket`, `object`, optional credentials |
| `Mount` | `target`, one source, and `type` |
Expand Down
2 changes: 2 additions & 0 deletions sdk/python/akernel_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
)
from ._backends.registry import selected_backend
from .types import (
CheckpointInfo,
CommandInfo,
CommandResult,
EntryInfo,
Expand All @@ -37,6 +38,7 @@

__all__ = [
"Sandbox",
"CheckpointInfo",
"S3Config",
"Mount",
"NetworkPolicy",
Expand Down
14 changes: 14 additions & 0 deletions sdk/python/akernel_sdk/_backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class Capability(Enum):
NODE_PLACEMENT = auto()
CUSTOM_REVERSE_TUNNEL_PORTS = auto()
REVERSE_WEBSOCKET = auto()
CHECKPOINT_RESTORE = auto()


@dataclass(frozen=True)
Expand Down Expand Up @@ -149,6 +150,8 @@ def is_running(self) -> bool: ...

def get_info(self) -> SandboxInfo: ...

def checkpoint(self, *, timeout: int) -> str: ...

def terminate(self) -> None: ...

def close(self) -> None: ...
Expand All @@ -163,6 +166,17 @@ class Backend(Protocol):

def create(self, spec: SandboxSpec) -> BackendSession: ...

def restore(
self,
checkpoint_id: str,
*,
reverse_tunnel: HttpReverseTunnel | None,
) -> BackendSession: ...

def list_checkpoints(self) -> list[str]: ...

def delete_checkpoint(self, checkpoint_id: str) -> None: ...

def delete_named(self, name: str) -> None: ...

def close(self) -> None: ...
Loading
Loading