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
87 changes: 78 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
# Supersede PR checks, but keep a build for every push to main.
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
sdk-unit-tests:
Expand Down Expand Up @@ -112,8 +113,19 @@ jobs:

standalone-e2e:
name: Standalone E2E
needs:
- sdk-unit-tests
- sdk-quality
- sdk-distributions
- deployment-script-syntax
runs-on: ubuntu-22.04
timeout-minutes: 45
timeout-minutes: 60
# Keep testing and publishing the same local image. Serialize main jobs
# through the latest push, including reruns, without replacing queued jobs.
concurrency:
group: ci-standalone-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: false
queue: max

steps:
- name: Check out repository
Expand Down Expand Up @@ -146,19 +158,37 @@ jobs:
sudo sysctl -w net.bridge.bridge-nf-call-iptables=1

- name: Build all-in-one image
env:
AKERNEL_ENABLE_KATA: "false"
AKERNEL_ENABLE_FIRECRACKER: "false"
AKERNEL_ENABLE_RUNC: "false"
run: |
AKERNEL_ENABLE_KATA=false \
make build \
IMAGE_REPOSITORY=akernel-ci/all-in-one \
IMAGE_TAG="${GITHUB_SHA}" \
RUNTIME_PROFILE=rrt
make build \
IMAGE_REPOSITORY=akernel-ci/all-in-one \
IMAGE_TAG="${GITHUB_SHA}" \
RUNTIME_PROFILE=rrt

- name: Verify systemd container identity in the image
run: |
docker image inspect "akernel-ci/all-in-one:${GITHUB_SHA}" \
--format '{{json .Config.Env}}' | \
python -c 'import json, sys; assert "container=oci" in json.load(sys.stdin)'

- name: Verify gVisor-only runtime payload
run: |
docker run --rm --entrypoint /bin/bash \
"akernel-ci/all-in-one:${GITHUB_SHA}" -euc '
test -x /usr/local/bin/runsc
test ! -e /usr/local/bin/containerd-shim-kata-v2
test ! -e /opt/kata/runtime-rs/bin/containerd-shim-kata-v2
test ! -e /usr/local/bin/firecracker
test ! -e /usr/local/bin/virtiofsd
test ! -e /opt/firecracker/vmlinux
test ! -e /opt/firecracker/initrd.img
test ! -e /usr/local/bin/runc
test ! -e /usr/local/bin/runc-shim
'

- name: Start standalone AKernel
run: |
IMAGE="akernel-ci/all-in-one:${GITHUB_SHA}" \
Expand Down Expand Up @@ -198,7 +228,13 @@ jobs:
)
for example in "${examples[@]}"; do
echo "=== Running ${example} ==="
timeout 120s python "sdk/python/examples/${example}"
example_timeout=120
if [[ "${example}" == "dockerfile_launch.py" ]]; then
# This example creates fresh sandboxes across nine sections.
example_timeout=600
fi
timeout --kill-after=30s "${example_timeout}s" \
python -u "sdk/python/examples/${example}"
done

- name: Run runsc checkpoint reload E2E
Expand Down Expand Up @@ -253,3 +289,36 @@ jobs:
else
docker rm -f akernel-traefik akernel-node >/dev/null 2>&1 || true
fi

- name: Log in to Docker Hub
if: >-
github.repository == 'inclusionAI/AKernel' &&
github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Publish tested all-in-one image
if: >-
github.repository == 'inclusionAI/AKernel' &&
github.event_name == 'push' && github.ref == 'refs/heads/main'
env:
GH_TOKEN: ${{ github.token }}
IMAGE_REPOSITORY: akerneldev/all-in-one
run: |
# Jobs can queue out of commit order, and old runs can be rerun.
# Check main before any push while holding the job lock. Publish
# only latest; superseded commits must not create registry tags.
main_sha="$(gh api "repos/${GITHUB_REPOSITORY}/commits/main" --jq .sha)"
if [[ "${main_sha}" != "${GITHUB_SHA}" ]]; then
echo "Skipped latest: main has advanced to ${main_sha}." \
>> "${GITHUB_STEP_SUMMARY}"
exit 0
fi

source_image="akernel-ci/all-in-one:${GITHUB_SHA}"
docker tag "${source_image}" "${IMAGE_REPOSITORY}:latest"
docker push "${IMAGE_REPOSITORY}:latest"
echo "Published ${IMAGE_REPOSITORY}:latest from ${GITHUB_SHA}" \
>> "${GITHUB_STEP_SUMMARY}"
29 changes: 29 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,35 @@ are not part of the default test suite.

## Release

The CI workflow builds the public Linux/amd64 all-in-one image with only
gVisor runsc and the `rrt` runtime profile. It explicitly sets
`AKERNEL_ENABLE_KATA=false`, `AKERNEL_ENABLE_FIRECRACKER=false`, and
`AKERNEL_ENABLE_RUNC=false`, excluding VM payloads and virtiofsd. Source-build
defaults still include Kata and Firecracker for operators who need them.
After SDK checks, distribution validation, deployment syntax checks, and
standalone runsc E2E pass, pushes to `main` in `inclusionAI/AKernel` publish
that tested image only as `akerneldev/all-in-one:latest`. PRs and forks never
publish. Check that the commit is still the current `main` head before any
push; superseded commits and reruns of older commits skip publication
entirely. Do not publish per-commit SHA tags or other historical image tags.
The job checks the image contains runsc and excludes Kata, Firecracker,
virtiofsd, and runc before starting standalone E2E.

CI runs examples with unbuffered Python output. Ordinary examples have a
120-second limit; `dockerfile_launch.py` gets 600 seconds for its nine
sections. Its core startup script uses the Ubuntu base image's shell and
does not install packages. Keep its RUN, context-transfer, and startup checks
independent of external package mirrors.

Configure the repository Actions variable `DOCKERHUB_USERNAME` and secret
`DOCKERHUB_TOKEN` with Docker Hub credentials that can push to
`akerneldev/all-in-one`. Keep credentials out of source and logs. Main CI runs
are not canceled by subsequent pushes. The standalone job serializes builds,
tests, and publication using `queue: max` (up to 100 pending jobs), so a slow
or rerun job cannot overwrite a newer published `latest`. Do not remove the
main-head check or the publication lock. A failed check or push leaves the
run failed and can be retried using GitHub Actions' rerun controls.

Python SDK releases use stable `vX.Y.Z` tags or release-candidate
`vX.Y.ZrcN` tags. The tag version must match the version in
`sdk/python/pyproject.toml`, and the tagged commit must be part of `main`.
Expand Down
2 changes: 2 additions & 0 deletions sdk/python/docs/launch-from-dockerfile.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ with Sandbox(dockerfile=DockerfileLaunch(context, run_timeout=300)) as sandbox:

See the maintained end-to-end
[`examples/dockerfile_launch.py`](../examples/dockerfile_launch.py).
It exercises context transfer and startup with the base image's shell, so
running the example does not require downloading additional packages.

## Precheck and DockerfileLaunch configuration

Expand Down
29 changes: 19 additions & 10 deletions sdk/python/examples/dockerfile_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
sets runtime state. RUN/COPY/ADD execute for every launch, without a snapshot
or build cache.

The core startup script uses the base image's shell, so exercising Dockerfile
semantics does not require installing packages from an external apt mirror.

Sections:
1. Core path, ignore filtering, wildcard COPY, modes, and empty directories
2. Root cwd plus ENTRYPOINT + CMD exec-form combination
Expand All @@ -35,7 +38,12 @@
import tempfile
from pathlib import Path

from akernel_sdk import DockerfileLaunch, LocalDockerContext, Sandbox, check_direct_launch
from akernel_sdk import (
DockerfileLaunch,
LocalDockerContext,
Sandbox,
check_direct_launch,
)


def _precheck(context: LocalDockerContext) -> None:
Expand All @@ -60,11 +68,9 @@ def section_core_path() -> None:
docs.mkdir()
(docs / "README.md").write_text("visible\n", encoding="utf-8")
(docs / "private.txt").write_text("hidden\n", encoding="utf-8")
(context_dir / "app.py").write_text(
"import os\n"
"open('/tmp/app.started', 'w').write(\n"
" f\"whoami={os.environ['WHOAMI']} cwd={os.getcwd()}\"\n"
")\n",
(context_dir / "app.sh").write_text(
"#!/bin/sh\n"
"printf 'whoami=%s cwd=%s' \"$WHOAMI\" \"$(pwd)\" > /tmp/app.started\n",
encoding="utf-8",
)
executable = context_dir / "entrypoint.sh"
Expand All @@ -82,7 +88,6 @@ def section_core_path() -> None:
dockerfile = build / "custom.Dockerfile"
dockerfile.write_text(
"""FROM ubuntu:22.04
RUN apt-get update && apt-get install -y --no-install-recommends python3
RUN useradd -m app
ENV WHOAMI=app
WORKDIR /srv
Expand All @@ -96,7 +101,7 @@ def section_core_path() -> None:
COPY wild/* /srv/wild/
COPY docs /srv/reincluded-literal/
COPY doc* /srv/reincluded-wildcard/
CMD ["python3", "/srv/core/app.py"]
CMD ["/bin/sh", "/srv/core/app.sh"]
""",
encoding="utf-8",
)
Expand Down Expand Up @@ -131,7 +136,9 @@ def section_core_path() -> None:
"/srv/core/tree/nested-empty /srv/core/literal-empty"
)
assert modes.exit_code == 0, modes.stderr
assert modes.stdout.splitlines() == ["755", "711", "750", "755"], modes.stdout
assert modes.stdout.splitlines() == ["755", "711", "750", "755"], (
modes.stdout
)
wildcard = sandbox.commands.run(
"test -f /srv/wild/dir2/foo && test ! -e /srv/wild/dir1"
)
Expand Down Expand Up @@ -264,7 +271,9 @@ def section_auto_start_disabled() -> None:
)
_precheck(context)

with Sandbox(dockerfile=DockerfileLaunch(context, auto_start_cmd=False)) as sandbox:
with Sandbox(
dockerfile=DockerfileLaunch(context, auto_start_cmd=False)
) as sandbox:
assert sandbox.startup_command is None
absent = sandbox.commands.run("test ! -e /tmp/disabled-start.out")
assert absent.exit_code == 0, absent.stderr
Expand Down