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
10 changes: 5 additions & 5 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Git metadata is not part of the image build. Component versions come from
# their source manifests, and exact revisions remain traceable through the
# parent repository's submodule gitlinks.
# source manifests or pinned releases; revisions remain traceable through
# submodule gitlinks and packaged release provenance.
.git
**/.git

Expand All @@ -20,9 +20,9 @@ deploy/standalone/data/
deploy/standalone/logs/
deploy/standalone/output/

# Local component build outputs. The image build compiles these components in
# dedicated Docker stages.
src/distill-fs/target/
# Local source build outputs.
# distill-fs is consumed as a release artifact, not from this checkout.
src/distill-fs/
src/sandboxd/output/
src/yuanrong/

Expand Down
14 changes: 13 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ jobs:

- name: Initialize build submodules
run: |
git submodule update --init src/sandboxd src/distill-fs
git submodule update --init src/sandboxd

- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
Expand Down Expand Up @@ -210,6 +210,18 @@ jobs:
echo "=== akernel-node: service journal ==="
docker exec akernel-node journalctl --no-pager -n 500 \
-u sandboxd.service -u yuanrong.service || true
docker exec akernel-node bash -c '
shopt -s nullglob
for logfile in \
/home/akernel/logs/sandboxd/sandboxd*.log \
/home/yuanrong/logs/*function_master*.log \
/home/yuanrong/logs/*function_proxy*.log \
/home/yuanrong/logs/*function_agent*.log \
/home/akernel/sandboxd/image_manager/daemons/*/daemon.log; do
echo "=== ${logfile} ==="
tail -n 300 "${logfile}"
done
' || true
fi

- name: Stop standalone AKernel
Expand Down
40 changes: 27 additions & 13 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,11 @@ tunnels. The project overview and deployment quick start are in
The open-source AKernel repository contains the SDK, deployment configuration,
build tooling, and examples. Node runtime components such as `sandboxd` and
`distill-fs` are maintained in their own upstream repositories and pinned as
Git submodules. The all-in-one build compiles those revisions and packages the
runtime payloads described in the Build section below.
Git submodules. The all-in-one build compiles sandboxd and downloads the
checksum-pinned static distill-fs release recorded in
`builder/distill-fs-versions.env`. The distill-fs submodule is an optional
source reference, not a build input. See the Build section below for runtime
payloads.

## Common Commands

Expand Down Expand Up @@ -129,9 +132,9 @@ actor backend is deprecated and retained only for compatibility with existing
applications. Keep it on its explicitly pinned legacy version; do not advance
it with the default `openyuanrong-sandbox` backend or use it for new features.

Initialize submodules with `git submodule update --init --recursive` before
Initialize sandboxd with `git submodule update --init src/sandboxd` before
building. The all-in-one image builds the sandboxd binaries, including
`firecracker-agent`, and `distill_fs`; installs checksum-pinned gVisor and Kata
`firecracker-agent`; installs checksum-pinned static distill-fs, gVisor, and Kata
artifacts; installs the Firecracker VMM and guest kernel; and constructs the
matching guest-agent initrd. Runc remains build-time optional, and
`AKERNEL_ENABLE_FIRECRACKER=false` excludes the Firecracker payload.
Expand All @@ -149,14 +152,24 @@ pins it rather than overriding manifest fields from the AKernel build. Keep
sandboxd's pooled-TAP contract and the matching gVisor compatibility patches
validated together when upgrading.

The submodule gitlinks are the single source of truth for the sandboxd and
distill-fs revisions included in a clean release. `make build` always compiles
the local submodule worktrees, so developers may check out a different commit
or edit either directory and rebuild without pushing first. Each component
maintains and embeds its own semantic version: sandboxd uses
`version/VERSION`, while distill-fs uses the package version in `Cargo.toml`.
AKernel does not inject parent-repository version metadata into component
compilation.
The sandboxd gitlink fixes the source revision compiled by `make build`.
AKernel's `builder/distill-fs-versions.env` fixes the distill-fs release URL
and SHA-256. `make build` compiles the local sandboxd worktree and consumes the
static distill-fs release through `builder/scripts/install-distill-fs.sh`; editing
`src/distill-fs` no longer affects the image. The installer verifies the archive,
provenance, version, binary hash, and static ELF contract, and packages its
licenses and manifest under `/usr/local/share/distill-fs`.

Publish and verify a distill-fs release before updating the AKernel manifest
pin. This dependency does not require a sandboxd source or gitlink change.
Never use a guessed checksum or silently fall back to a source build.
Missing or invalid release pins prevent builds. `make versions` reports the
release tag and archive digest without requiring the distill-fs submodule.

Each component embeds its own semantic version: sandboxd uses
`version/VERSION`, while distill-fs uses its release package version in
`Cargo.toml`. AKernel does not inject parent-repository version metadata into
component compilation.

To test an unreleased openYuanRong core wheel without rebuilding YuanRong,
provide both `OPEN_YR_CORE_WHEEL_URL` and `OPEN_YR_CORE_WHEEL_SHA256` to
Expand All @@ -175,7 +188,8 @@ make versions

The final image uses standard OCI labels for the AKernel version and revision.
Component semantic versions are reported by their binaries, and their exact
source revisions are traceable through the AKernel commit's submodule gitlinks.
source revisions are traceable through the sandboxd gitlink and the pinned
distill-fs release's packaged manifest.

## Deploy

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ See the complete [basic usage example](./sdk/python/examples/basic_usage.py), th
- **Sandbox runtimes**: gVisor by default; Kata Containers and Firecracker on
KVM-capable nodes; and an explicitly enabled native Linux runc backend
- **sandboxd**: Sandbox lifecycle daemon with pluggable sandbox runtime integration
- **distill-fs**: Rust-based FUSE filesystem for lazy rootfs access, chunk caching, and deduplication
- **distill-fs**: Rust-based FUSE filesystem for lazy rootfs access, chunk caching, and deduplication; packaged from a static GitHub Release with its version and checksum pinned in AKernel

**Cluster-Wide Services**
- **Distributed Scheduler**: Workload-aware placement and scaling
Expand Down
8 changes: 8 additions & 0 deletions builder/distill-fs-versions.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) 2026 Ant Group Corporation.
# SPDX-License-Identifier: Apache-2.0

# AKernel's distill-fs release dependency. Verify the published release archive
# before updating its version, URL, and SHA-256 together.
DISTILL_FS_RELEASE=v0.1.1
DISTILL_FS_AMD64_URL=https://github.com/inclusionAI/distill-fs/releases/download/v0.1.1/distill-fs-v0.1.1-linux-amd64.tar.gz
DISTILL_FS_AMD64_SHA256=b16e225e5b777f673bd98dd0968a15c190e333690356552b69a2b2624c3d5dcb
28 changes: 11 additions & 17 deletions builder/node.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ ARG AKERNEL_ENABLE_KATA=true
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.2rc2
ARG OPEN_YR_CORE_WHEEL_URL=
ARG OPEN_YR_CORE_WHEEL_SHA256=
Expand Down Expand Up @@ -214,23 +213,17 @@ RUN mkdir -p /runc/usr/local/bin

FROM runc-runtime-${AKERNEL_ENABLE_RUNC} AS runc-runtime

FROM ${DISTILL_FS_BUILD_IMAGE} AS distill-fs-builder
ENV DEBIAN_FRONTEND=noninteractive \
CARGO_NET_GIT_FETCH_WITH_CLI=true
FROM ubuntu:24.04 AS distill-fs-runtime
ARG TARGETARCH
ARG DISTILL_FS_RELEASE
ARG DISTILL_FS_AMD64_URL
ARG DISTILL_FS_AMD64_SHA256
RUN apt-get update && \
apt-get install -y --no-install-recommends \
ca-certificates \
cmake \
g++ \
gcc \
git \
make \
perl \
pkg-config && \
apt-get install -y --no-install-recommends ca-certificates curl jq binutils && \
rm -rf /var/lib/apt/lists/*
WORKDIR /src/distill-fs
COPY ./src/distill-fs/ ./
RUN cargo build --locked --release --bin distill_fs
COPY ./builder/scripts/install-distill-fs.sh /install-distill-fs.sh
RUN sh /install-distill-fs.sh "$DISTILL_FS_RELEASE" \
"$DISTILL_FS_AMD64_URL" "$DISTILL_FS_AMD64_SHA256" /distill-fs

FROM ${AKERNEL_NODE_BASE_IMAGE}
# Let PID 1 systemd avoid remounting shared host filesystems during shutdown.
Expand Down Expand Up @@ -371,7 +364,8 @@ COPY --from=gvisor-runtime /gvisor/runsc /usr/local/bin/runsc
COPY --from=sandboxd-builder /src/sandboxd/output/sandboxd /usr/local/bin/sandboxd
COPY --from=sandboxd-builder /src/sandboxd/output/sbox /usr/local/bin/sbox
COPY --from=sandboxd-builder /src/sandboxd/output/sandbox-logger /usr/local/bin/sandbox-logger
COPY --from=distill-fs-builder /src/distill-fs/target/release/distill_fs /usr/local/bin/distill_fs
COPY --from=distill-fs-runtime /distill-fs/bin/distill_fs /usr/local/bin/distill_fs
COPY --from=distill-fs-runtime /distill-fs/share/distill-fs/ /usr/local/share/distill-fs/
COPY --from=kata-runtime /kata/opt/kata /opt/kata
COPY --from=runc-runtime /runc/usr/local/bin/ /usr/local/bin/
COPY --from=firecracker-runtime /firecracker/ /
Expand Down
56 changes: 56 additions & 0 deletions builder/scripts/install-distill-fs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/sh
# Copyright (c) 2026 Ant Group Corporation.
# SPDX-License-Identifier: Apache-2.0

# Install the distill-fs release for AKernel. Callers pass pins from
# builder/distill-fs-versions.env, never checksums downloaded with the archive.
set -eu

DISTILL_FS_RELEASE=$1
DISTILL_FS_AMD64_URL=$2
DISTILL_FS_AMD64_SHA256=$3
destination=$4

case "${TARGETARCH:-$(uname -m)}" in
amd64|x86_64) ;;
*) echo "distill-fs release supports linux/amd64 only" >&2; exit 1 ;;
esac
: "${DISTILL_FS_RELEASE:?distill-fs release is not pinned}"
: "${DISTILL_FS_AMD64_URL:?distill-fs release URL is not pinned}"
: "${DISTILL_FS_AMD64_SHA256:?publish and pin the distill-fs release before building}"
printf '%s\n' "$DISTILL_FS_AMD64_SHA256" | grep -Eq '^[0-9a-f]{64}$' || {
echo "invalid distill-fs SHA-256 pin" >&2
exit 1
}

# Keep the download separate from installed files. Image consumers copy only
# bin/ and share/ from this staging directory.
mkdir -p "$destination/download" "$destination/bin" "$destination/share/distill-fs"
archive="$destination/download/distill-fs.tar.gz"
curl -fSL --retry 5 --retry-delay 2 --retry-all-errors \
"$DISTILL_FS_AMD64_URL" -o "$archive"
printf '%s %s\n' "$DISTILL_FS_AMD64_SHA256" "$archive" | sha256sum -c -
tar -xzf "$archive" -C "$destination/download" \
distill_fs manifest.json LICENSE NOTICE Cargo.lock
bundle="$destination/download"
jq -e --arg release "$DISTILL_FS_RELEASE" \
'.component == "distill-fs" and .release_tag == $release and
.version == ($release | ltrimstr("v")) and
.repository == "inclusionAI/distill-fs" and
.target == "x86_64-unknown-linux-musl" and
(.source_revision | test("^[0-9a-f]{40}$")) and
(.binary_sha256 | test("^[0-9a-f]{64}$"))' \
"$bundle/manifest.json" >/dev/null
printf '%s %s\n' "$(jq -r .binary_sha256 "$bundle/manifest.json")" \
"$bundle/distill_fs" | sha256sum -c -
readelf -h "$bundle/distill_fs" >/dev/null
if readelf -l "$bundle/distill_fs" | grep -q INTERP ||
readelf -d "$bundle/distill_fs" | grep -q NEEDED; then
echo "distill-fs release must be a static executable" >&2
exit 1
fi
chmod 0755 "$bundle/distill_fs"
test "$("$bundle/distill_fs" --version)" = "distill_fs ${DISTILL_FS_RELEASE#v}"
install -m 0755 "$bundle/distill_fs" "$destination/bin/distill_fs"
install -m 0644 "$bundle/manifest.json" "$bundle/LICENSE" \
"$bundle/NOTICE" "$bundle/Cargo.lock" "$destination/share/distill-fs/"
76 changes: 76 additions & 0 deletions builder/scripts/test-install-distill-fs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""Exercise the AKernel installer with a real candidate/release archive.

Usage: python3 builder/scripts/test-install-distill-fs.py /path/to/release.tar.gz
Requires curl, jq, binutils, and a Linux/amd64 host. No network is needed.
"""

import hashlib
import io
import json
import os
from pathlib import Path
import subprocess
import sys
import tarfile
import tempfile


archive = Path(sys.argv[1]).resolve()
installer = Path(__file__).with_name("install-distill-fs.sh")
with tarfile.open(archive) as bundle:
files = {name: bundle.extractfile(name).read() for name in (
"distill_fs", "manifest.json", "LICENSE", "NOTICE", "Cargo.lock"
)}
manifest = json.loads(files["manifest.json"])
release = manifest["release_tag"]
digest = hashlib.sha256(archive.read_bytes()).hexdigest()

with tempfile.TemporaryDirectory(prefix="distill-fs-installer-") as work:
work = Path(work)

def check(name, asset=archive, checksum=digest, tag=release, arch="amd64", error=None):
destination = work / name
result = subprocess.run(
["sh", str(installer), tag, asset.as_uri(), checksum, str(destination)],
env={**os.environ, "TARGETARCH": arch},
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
)
binary = destination / "bin/distill_fs"
if error is None:
assert result.returncode == 0, result.stdout
assert binary.read_bytes() == files["distill_fs"]
for filename in ("manifest.json", "LICENSE", "NOTICE", "Cargo.lock"):
assert (destination / "share/distill-fs" / filename).read_bytes() == files[filename]
else:
assert result.returncode != 0, name
assert error in result.stdout, result.stdout
assert not binary.exists(), "a rejected artifact must not be installed"
print(f"PASS {name}")

def altered_archive(name, binary, binary_hash):
data = {**files, "distill_fs": binary}
metadata = {**manifest, "binary_sha256": binary_hash}
data["manifest.json"] = json.dumps(metadata).encode()
path = work / f"{name}.tar.gz"
with tarfile.open(path, "w:gz") as tar:
for filename, content in data.items():
entry = tarfile.TarInfo(filename)
entry.size = len(content)
tar.addfile(entry, io.BytesIO(content))
return path, hashlib.sha256(path.read_bytes()).hexdigest()

check("valid")
check("missing-pin", checksum="", error="publish and pin")
check("bad-pin", checksum="not-a-digest", error="invalid distill-fs SHA-256")
check("corrupt-archive", checksum="0" * 64, error="FAILED")
check("wrong-version", tag="v999.0.0", error="")
check("unsupported-arch", arch="arm64", error="linux/amd64 only")
bad, sha = altered_archive("bad-binary-hash", files["distill_fs"], "0" * 64)
check("bad-binary-hash", asset=bad, checksum=sha, error="FAILED")
# A correctly checksummed bundle must still reject a dynamic executable.
dynamic = Path("/bin/true").read_bytes()
headers = subprocess.check_output(["readelf", "-l", "/bin/true"], text=True)
assert "INTERP" in headers, "this negative fixture needs a dynamic /bin/true"
bad, sha = altered_archive("dynamic-binary", dynamic, hashlib.sha256(dynamic).hexdigest())
check("dynamic-binary", asset=bad, checksum=sha, error="must be a static executable")
8 changes: 8 additions & 0 deletions deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,3 +367,11 @@ deploy/
├── terraform/ # multi-cloud provisioning (aliyun, huaweicloud, shared)
└── scripts/ # deployment and image helper scripts
```

### distill-fs release dependency

The all-in-one image downloads the static Linux/amd64 distill-fs release pinned in `builder/distill-fs-versions.env`. It does not compile `src/distill-fs`; that checkout is optional source reference. `make versions` reports the release tag and archive SHA-256. The AKernel installer at `builder/scripts/install-distill-fs.sh` checks the archive, package provenance, CLI version, and static ELF linkage, and retains licenses and provenance in `/usr/local/share/distill-fs`.

Publish and verify the distill-fs release before updating the AKernel version, URL, and checksum pin together. Missing or invalid pins stop `make build` before either image is built. There is no source-build fallback.

Validate installation against a downloaded candidate or release with `python3 builder/scripts/test-install-distill-fs.py /path/to/distill-fs-vX.Y.Z-linux-amd64.tar.gz` on Linux/amd64 with curl, jq, and binutils. This checks normal installation and rejects missing/invalid pins, corrupted archives, version/architecture mismatch, binary hash mismatch, and dynamically linked executables. The sandboxd pipeline and gitlink are independent of this dependency.
Loading