Skip to content
Merged
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
9 changes: 5 additions & 4 deletions builder/node.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ ARG AKERNEL_ENABLE_RUNC=false
ARG AKERNEL_ENABLE_FIRECRACKER=true
ARG SANDBOXD_BUILD_IMAGE=golang:1.25.5-bookworm
ARG DISTILL_FS_BUILD_IMAGE=rust:1.85.0-bookworm
ARG OPEN_YR_VERSION=0.10.1rc4
ARG OPEN_YR_VERSION=0.10.2rc2
ARG OPEN_YR_CORE_WHEEL_URL=
ARG OPEN_YR_CORE_WHEEL_SHA256=
ARG OPEN_YR_RELEASE_BASE_URL=https://openyuanrong.obs.cn-southwest-2.myhuaweicloud.com/release
ARG OPEN_YR_CORE_AMD64_SHA256=65c1f27e7e700a253a2e907dea0273e85f1c76610e48c93544caa6bcc07ac3af
ARG OPEN_YR_CORE_ARM64_SHA256=29d25c3388c2913346035ee8df9b8159e702de7a8e6f783e3e218ab773896333
ARG OPEN_YR_CORE_AMD64_SHA256=8cdefba9a415a7a35b6f39bf847e7fb933ad6ca55b27b7dec2f377ece47198c4
ARG OPEN_YR_CORE_ARM64_SHA256=2e9d2d18922b87721fcc3e92fa959cdd2ce026a51b18512c7c71ab24ac6a6eaa
ARG GVISOR_DOWNLOAD_IMAGE=ubuntu:24.04
ARG GVISOR_RELEASE
ARG GVISOR_AMD64_URL
Expand Down Expand Up @@ -420,5 +420,6 @@ LABEL org.opencontainers.image.version="${AKERNEL_VERSION}" \
org.akernel.firecracker.release="${FIRECRACKER_RELEASE}" \
org.akernel.firecracker.enabled="${AKERNEL_ENABLE_FIRECRACKER}"

ENV YR_LOG_PATH=${YR_INSTALLATION_DIR}/logs
ENV YR_LOG_PATH=${YR_INSTALLATION_DIR}/logs \
YR_IMAGE_PROCESS_CONFIG=/run/akernel/yr-image-process.json
STOPSIGNAL SIGRTMIN+3
4 changes: 2 additions & 2 deletions builder/runtime.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ ARG PYTHON_311_VERSION=3.11.13
ARG PYTHON_312_VERSION=3.12.11
ARG PYTHON_313_VERSION=3.13.5
ARG PYTHON_314_VERSION=3.14.6
ARG OPEN_YR_VERSION=0.10.1rc4
ARG OPEN_YR_VERSION=0.10.2rc2
ARG OPEN_YR_LEGACY_SDK_VERSION=0.9.9

FROM ${AKERNEL_RUNTIME_BASE_IMAGE} AS rrt-download

ARG OPEN_YR_VERSION
ARG RRT_RUNTIME_URL=https://openyuanrong.obs.cn-southwest-2.myhuaweicloud.com/release/${OPEN_YR_VERSION}/linux/amd64/rrt-runtime-amd64
ARG RRT_RUNTIME_SHA256=7c2064531e91fba8b9bbe96ac9e706cd496a198551657ee5461a078a2f7ea9ea
ARG RRT_RUNTIME_SHA256=d67efcaaf07a5ca57079706ad1d5006d57e796baedf727db9392a877b34e365d

RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates curl && \
Expand Down
2 changes: 2 additions & 0 deletions builder/scripts/master_entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ BASE_DIR=$(
export DEPLOY_PATH="/home/yuanrong/master/"
mkdir -p "$DEPLOY_PATH"
export YR_LOG_PATH="$DEPLOY_PATH/log"
export YR_IMAGE_PROCESS_CONFIG="${YR_IMAGE_PROCESS_CONFIG:-/run/akernel/yr-image-process.json}"

# If ConfigMap-mounted config exists, symlink it to override the baked-in default
[ -f /etc/otel-collector/otel_config.yaml ] && ln -sf /etc/otel-collector/otel_config.yaml /home/yuanrong/otel_config.yaml
Expand Down Expand Up @@ -96,6 +97,7 @@ exec "${YR_BIN}" start --master --block true \
--trace_config "$(cat /home/yuanrong/trace/trace_config.json)" \
--ds_rpc_thread_num 128 \
--function_proxy_merge_process_enable true \
--force_low_reliability_instance true \
--enable_traefik_provider=${ENABLE_TRAEFIK_PROVIDER} \
--traefik_http_entry_point=${TRAEFIK_HTTP_ENTRYPOINT:-websecure} \
--traefik_enable_tls=${TRAEFIK_ENABLE_TLS:-false} \
Expand Down
1 change: 1 addition & 0 deletions builder/scripts/yr_node_bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# SPDX-License-Identifier: Apache-2.0
ulimit -n 32768
export YR_RUNTIME_BACKEND=sandboxd
export YR_IMAGE_PROCESS_CONFIG="${YR_IMAGE_PROCESS_CONFIG:-/run/akernel/yr-image-process.json}"

resolve_node_ip() {
local default_device
Expand Down
2 changes: 1 addition & 1 deletion builder/systemd_services/yuanrong.service
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Description=yuanrong.service
[Service]
#Type=simple
PIDFile=/run/yuanrong.pid
PassEnvironment=ETCD_PORT ETCD_PEER_PORT ETCD_ADDRESS HOSTNAME AKS_LOCAL_MODE AKERNEL_NODE_IP INSTANCE_IP LITEBUS_DATA_KEY YR_LOG_PATH YR_INSTALLATION_DIR YR_RRT_CONTROL_SOCKET_PATH ENABLE_METRICS ENABLE_TRACE TRAEFIK_MODE TRAEFIK_ENABLE_TLS TRAEFIK_HTTP_ENTRYPOINT
PassEnvironment=ETCD_PORT ETCD_PEER_PORT ETCD_ADDRESS HOSTNAME AKS_LOCAL_MODE AKERNEL_NODE_IP INSTANCE_IP LITEBUS_DATA_KEY YR_LOG_PATH YR_INSTALLATION_DIR YR_RRT_CONTROL_SOCKET_PATH YR_IMAGE_PROCESS_CONFIG ENABLE_METRICS ENABLE_TRACE TRAEFIK_MODE TRAEFIK_ENABLE_TLS TRAEFIK_HTTP_ENTRYPOINT
Environment="CONTAINER_EP=unix:///run/sandboxd/sandboxd.sock"
Environment="RUNTIME_HOME_DIR=/home/yuanrong/runtime"
Environment="YR_NOSET_CUDA_VISIBLE_DEVICES=1"
Expand Down
2 changes: 2 additions & 0 deletions deploy/akernel/charts/core/templates/node/daemonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ spec:
value: "1"
- name: YR_RRT_CONTROL_SOCKET_PATH
value: "/run/akernel"
- name: YR_IMAGE_PROCESS_CONFIG
value: {{ .Values.yuanrong.imageProcessConfig | default "/run/akernel/yr-image-process.json" | quote }}
- name: ETCD_ADDRESS
value: {{ get $nodeEtcd "host" | default (printf "akernel-etcd.%s.svc.cluster.local" .Release.Namespace) | quote }}
- name: ETCD_PORT
Expand Down
3 changes: 3 additions & 0 deletions deploy/akernel/charts/core/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ image:
tag: "latest"
pullPolicy: IfNotPresent

yuanrong:
imageProcessConfig: "/run/akernel/yr-image-process.json"

auth:
# Existing Secret that contains the JWT signing seed. For
# `helm template | kubectl apply`, prefer pre-creating this Secret with
Expand Down
2 changes: 2 additions & 0 deletions deploy/standalone/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ TOKEN_FILE="${DATA_DIR}/token"
SANDBOXD_CONFIG_FILE="${DATA_DIR}/sandboxd/config.toml"
AKERNEL_NAT_BACKEND="${AKERNEL_NAT_BACKEND:-iptables}"
AKERNEL_ENABLE_RUNC="${AKERNEL_ENABLE_RUNC:-false}"
YR_IMAGE_PROCESS_CONFIG="${YR_IMAGE_PROCESS_CONFIG:-/run/akernel/yr-image-process.json}"
LITEBUS_DATA_KEY=""

# Container runtime command (docker or pouch)
Expand Down Expand Up @@ -362,6 +363,7 @@ start_node_container() {
--restart always \
-e AKS_LOCAL_MODE="true" \
-e YR_RRT_CONTROL_SOCKET_PATH="/run/akernel" \
-e YR_IMAGE_PROCESS_CONFIG="${YR_IMAGE_PROCESS_CONFIG}" \
-e TRAEFIK_MODE="http" \
-e TRAEFIK_HTTP_ENTRYPOINT="web" \
-e TRAEFIK_ENABLE_TLS="false" \
Expand Down
50 changes: 48 additions & 2 deletions sdk/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,36 @@ with Sandbox(image="ubuntu:24.04") as sandbox:
print(sandbox.commands.run("cat /etc/os-release").stdout)
```

To start the effective OCI image `ENTRYPOINT` and `CMD` as the managed sandbox
workload, enable entrypoint inheritance and wait for its exit status:

```python
with Sandbox(image="example/worker:latest", inherit_entrypoint=True) as sandbox:
exit_code = sandbox.wait_entrypoint()
print(exit_code, sandbox.entrypoint_exit_info)
```

For this image-inheritance path, `sandbox.startup_command` is `None`.
`wait_entrypoint()` returns an integer exit code; `entrypoint_exit_info`
contains the structured exit details collected by that call. Waiting observes
process exit, not application readiness. An inherited process exiting after
successful sandbox creation does not by itself terminate the sandbox.
For a long-running service, check its application health endpoint separately.

`Sandbox(cwd=...)` sets the default working directory for subsequent
`sandbox.commands.run()` calls that omit `cwd`. With `inherit_entrypoint=True`,
the image process starts in the image's OCI `WORKDIR`; the constructor's
`cwd` does not override it. For example, if the image declares `WORKDIR /app`:

```python
with Sandbox(
image="example/worker:latest", inherit_entrypoint=True, cwd="/tmp"
) as sandbox:
# The inherited image entrypoint starts in /app.
print(sandbox.commands.run("pwd").stdout) # /tmp
print(sandbox.commands.run("pwd", cwd="/").stdout) # /
```

Or use an object in S3-compatible storage as the rootfs:

```python
Expand Down Expand Up @@ -570,8 +600,24 @@ from akernel_sdk import DockerfileLaunch, LocalDockerContext, Sandbox, check_dir
context = LocalDockerContext("Dockerfile", context_dir=".")
if check_direct_launch(context).direct_launchable:
with Sandbox(dockerfile=DockerfileLaunch(context, run_timeout=300)) as sandbox:
pass
```
startup = sandbox.startup_command
if startup is not None:
# For a finite CMD/ENTRYPOINT, collect its exit code and output.
result = startup.wait(timeout=60)
print(result.exit_code, result.stdout, result.stderr)
```

`startup_command` is a `CommandHandle` for the Dockerfile's background
`CMD`/`ENTRYPOINT`. It is `None` when `auto_start_cmd=False` or no startup
command is declared. The handle supports `wait(timeout=...)` and `kill()`;
construction confirms dispatch, while application readiness requires a
separate health check. For long-running services, perform that check instead
of waiting for exit during startup.

`wait_entrypoint()` is exclusive to image launches with
`inherit_entrypoint=True`; calling it on a Dockerfile launch raises
`RuntimeError`, and `entrypoint_exit_info` is `None`. See the
[image launch examples](#rootfs-and-mounts) for that path.

`RUN`, `COPY`, and `ADD` run on every launch without a snapshot or cache;
unsupported Dockerfiles must be built externally. Read the complete contract,
Expand Down
7 changes: 7 additions & 0 deletions sdk/python/akernel_sdk/_backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class SandboxSpec:
reverse_tunnel: HttpReverseTunnel | None
detached: bool
failover: bool
inherit_entrypoint: bool
node_id: str | None
xpu: str | None
storage_mb: int | None
Expand Down Expand Up @@ -151,6 +152,12 @@ def is_running(self) -> bool: ...
def get_info(self) -> SandboxInfo: ...

def reload(self) -> bool: ...

def wait_entrypoint(self) -> int: ...

@property
def entrypoint_exit_info(self) -> Mapping[str, object] | None: ...

def update_network_policy(self, policy: NetworkPolicy | None) -> None: ...

def terminate(self) -> None: ...
Expand Down
41 changes: 41 additions & 0 deletions sdk/python/akernel_sdk/_backends/openyuanrong_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,36 @@ def reload(self) -> bool:
except Exception:
return False

def wait_entrypoint(self) -> int:
if not self._spec.inherit_entrypoint:
raise RuntimeError("inherit_entrypoint was not enabled for this sandbox")
wait = getattr(self._sandbox, "wait_entrypoint", None)
if not callable(wait):
raise UnsupportedBackendFeatureError(
"The installed openyuanrong-sandbox backend does not support "
"waiting for an inherited image entrypoint. Upgrade it to "
"0.10.2rc1 or newer."
)
try:
return int(wait())
except Exception as error:
raise _convert_error(
"wait for inherited image entrypoint", error
) from error

@property
def entrypoint_exit_info(self) -> Mapping[str, object] | None:
if not self._spec.inherit_entrypoint:
return None
value = getattr(self._sandbox, "entrypoint_exit_info", None)
if value is None:
return None
if not isinstance(value, Mapping):
raise BackendOperationError(
"inherited image entrypoint returned invalid exit information"
)
return dict(value)

def update_network_policy(self, policy: NetworkPolicy | None) -> None:
if self._terminated or self._closed:
raise BackendOperationError(
Expand Down Expand Up @@ -414,12 +444,21 @@ def _validate(self, spec: SandboxSpec) -> None:
def create(self, spec: SandboxSpec) -> BackendSession:
self._validate(spec)
supports_failover = _supports_keyword(yr_sandbox.Sandbox, "failover")
supports_inherit_entrypoint = _supports_keyword(
yr_sandbox.Sandbox, "inherit_entrypoint"
)
if spec.failover and not supports_failover:
raise UnsupportedBackendFeatureError(
"The installed openyuanrong-sandbox backend does not support "
"automatic sandbox failover. Upgrade it to a version with "
"failover support."
)
if spec.inherit_entrypoint and not supports_inherit_entrypoint:
raise UnsupportedBackendFeatureError(
"The installed openyuanrong-sandbox backend does not support "
"inheriting image ENTRYPOINT and CMD. Upgrade it to 0.10.2rc1 "
"or newer."
)
rootfs = None
if spec.rootfs is not None:
rootfs = yr_sandbox.S3Config(
Expand Down Expand Up @@ -492,6 +531,8 @@ def create(self, spec: SandboxSpec) -> BackendSession:
)
if supports_failover:
create_args["failover"] = spec.failover
if supports_inherit_entrypoint:
create_args["inherit_entrypoint"] = spec.inherit_entrypoint
try:
sandbox = yr_sandbox.Sandbox(**create_args)
except Exception as error:
Expand Down
15 changes: 15 additions & 0 deletions sdk/python/akernel_sdk/_backends/openyuanrong_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,16 @@ def reload(self) -> bool:
"Use the default 'openyuanrong-sandbox' backend."
)

def wait_entrypoint(self) -> int:
raise UnsupportedBackendFeatureError(
"Backend 'openyuanrong-sdk' does not support inheriting image "
"ENTRYPOINT and CMD. Use the default 'openyuanrong-sandbox' backend."
)

@property
def entrypoint_exit_info(self) -> Mapping[str, object] | None:
return None

def update_network_policy(self, policy: NetworkPolicy | None) -> None:
raise UnsupportedBackendFeatureError(
"Backend 'openyuanrong-sdk' does not support dynamic network policy "
Expand Down Expand Up @@ -289,6 +299,11 @@ def __init__(self, _config: BackendConfig) -> None:
_impl.ensure_initialized()

def create(self, spec: SandboxSpec) -> BackendSession:
if spec.inherit_entrypoint:
raise UnsupportedBackendFeatureError(
"Backend 'openyuanrong-sdk' does not support inheriting image "
"ENTRYPOINT and CMD. Use the default 'openyuanrong-sandbox' backend."
)
if spec.failover:
raise UnsupportedBackendFeatureError(
"Backend 'openyuanrong-sdk' does not support automatic sandbox "
Expand Down
44 changes: 43 additions & 1 deletion sdk/python/akernel_sdk/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ def __init__(
node_id: str | None = None,
*,
failover: bool = False,
inherit_entrypoint: bool = False,
xpu: str | None = None,
storage_mb: int | None = None,
network_policy: NetworkPolicy | None = None,
Expand All @@ -214,7 +215,10 @@ def __init__(
schedule_timeout: Positive scheduling timeout in seconds.
env: Environment variables applied to the sandbox process.
name: Optional stable name for a detached sandbox.
cwd: Initial working directory inside the sandbox.
cwd: Default working directory for subsequent ``commands.run()``
calls that omit ``cwd``. This does not override the inherited
image entrypoint's working directory, which uses the image's
OCI WORKDIR when ``inherit_entrypoint=True``.
port_forwardings: Sandbox TCP ports exposed through the gateway.
mounts: Additional read-only OCI or S3-backed mounts.
reverse_tunnel: SDK-side HTTP service exposed inside the sandbox.
Expand All @@ -223,6 +227,8 @@ def __init__(
failover: Restore the same logical sandbox on its original node
from the latest local anonymous checkpoint after failure.
Use :meth:`reload` to request the same rollback explicitly.
inherit_entrypoint: Start the OCI image's effective ENTRYPOINT and
CMD as the sandbox workload. Valid only with ``image``.
Comment thread
tianyuzhou95 marked this conversation as resolved.
xpu: Experimental whole-device accelerator request in
``type:model:count`` format. Currently only exact-model NVIDIA
GPU requests are supported. The backend validates runtime
Expand Down Expand Up @@ -301,6 +307,10 @@ def __init__(
raise TypeError("detached must be a boolean")
if not isinstance(failover, bool):
raise TypeError("failover must be a boolean")
if not isinstance(inherit_entrypoint, bool):
raise TypeError("inherit_entrypoint must be a boolean")
if inherit_entrypoint and image is None:
raise ValueError("inherit_entrypoint requires an image")
if node_id is not None:
if not isinstance(node_id, str):
raise TypeError("node_id must be a string")
Expand Down Expand Up @@ -345,6 +355,7 @@ def __init__(
self._memory = memory
self._xpu = normalized_xpu
self._storage_mb = storage_mb
self._inherit_entrypoint = inherit_entrypoint
self._id = ""

spec = SandboxSpec(
Expand All @@ -365,6 +376,7 @@ def __init__(
reverse_tunnel=reverse_tunnel,
detached=detached,
failover=failover,
inherit_entrypoint=inherit_entrypoint,
node_id=node_id,
xpu=normalized_xpu,
storage_mb=storage_mb,
Expand Down Expand Up @@ -431,6 +443,9 @@ def startup_command(self) -> CommandHandle | None:
normal image/rootfs launches, disabled startup dispatch, or Dockerfiles
without CMD or ENTRYPOINT. Sandbox construction does not guarantee that
the process remains running or healthy after dispatch.

For ``image=..., inherit_entrypoint=True``, this property remains None;
use :meth:`wait_entrypoint` and :attr:`entrypoint_exit_info` instead.
"""

return self._startup_command
Expand Down Expand Up @@ -467,6 +482,33 @@ def reload(self) -> bool:
return False
return self._session.reload()

def wait_entrypoint(self) -> int:
"""Wait for the inherited OCI image process and return its exit code.

Requires ``image=..., inherit_entrypoint=True``. For Dockerfile direct
launches, use :attr:`startup_command` and its ``wait()`` method instead.
This waits for process exit, not application readiness. An exit after
successful sandbox creation does not by itself terminate the sandbox.
"""

if not self._inherit_entrypoint:
raise RuntimeError("inherit_entrypoint was not enabled for this sandbox")
if self._closed or self._session is None:
raise RuntimeError("sandbox is closed")
return self._session.wait_entrypoint()

@property
def entrypoint_exit_info(self) -> Mapping[str, object] | None:
"""Structured exit details cached after :meth:`wait_entrypoint`.

None before exit details have been collected or when entrypoint
inheritance is disabled, including Dockerfile direct launches.
"""

if not self._inherit_entrypoint or self._session is None:
return None
return self._session.entrypoint_exit_info

def update_network_policy(self, policy: NetworkPolicy | None) -> None:
"""Atomically replace the complete network policy of this sandbox.

Expand Down
Loading