Lab Lifecycle¶
A lab moves through four stages, each an optional block in config.yaml. Every command only runs the stages relevant to it — astrona run never touches testing or validation, astrona submit never touches bootstrap.
astrona run astrona submit astrona destroy
─────────────► ┌──────────────┐ ────────► ┌─────────────┐
bootstrap │ (your work) │ Proctor teardown
└──────────────┘ grading
astrona test ──────────────────────────────────────────────►
bootstrap → testing → submit (Proctor grading) → teardown (always)
bootstrap¶
Runs on astrona run and at the start of astrona test. Three parts, all optional, run in this order:
bootstrap:
init:
- name: "setup"
type: "file" # "file" | "folder" | "url"
source: "setup.sh"
manifests:
- name: "base"
type: "folder" # "file" | "folder" | "url"
source: "manifests/"
waitFor:
- resource: deploy/web # rollout complete
- resource: pod
selector: app=db # every matching pod Ready
timeout: 5m
init— scripts run in order (on kind, after the cluster's DNS is up), through whichever executor the runtime provides (host bash forkind, SSH into the VM forqemu). Afoldersource runs every file inside in filename order — number them (01-x.sh,02-y.sh) to control ordering.manifests— applied withkubectl applyagainst the cluster's context. Requires akindruntime (or a runtime with a kubectl-reachable cluster) —astrona runerrors out immediately ifbootstrap.manifestsis set on a runtime with none.waitFor— readiness gates, checked in order aftermanifests.kubectl applyreturns as soon as the API server accepts the objects, while pods may still be pulling images; without a gate, "ready" only means "applied". Each gate waits (default 2 minutes) for a rollout to finish or a condition to be met, and retries while its target doesn't exist yet (an operator creating it, a CRD still registering). If a gate times out,astrona runfails and prints the target's current state plus the namespace's recent events. kind only. SeeWaitFor.
testing takes the same waitFor list: under astrona test it gates grading on the reference solution actually being up, so the Proctor never races pods that are still starting.
testing¶
Same shape as bootstrap (init + manifests), but only ever runs as part of astrona test, never astrona run. This is where a lab author puts the reference solution — the manifests/scripts that drive the cluster into the "lab completed" state, so astrona test has something real to grade. A student taking the lab never sees this stage; it exists so a lab author's CI can prove their own lab is solvable and passes the Proctor's checks before publishing it.
validation¶
Not a "run" stage — it's what the Proctor executes when you run astrona submit (or as the grading step inside astrona test). Declarative checks plus one or more custom pass/fail scripts.
teardown¶
Runs on astrona destroy, and always (even on failure) at the end of astrona test.
initscripts run first — samefile/folder/urlshape asbootstrap.init, best-effort (a failing teardown script only warns, it never blocks the cluster from being deleted).- If a kind lab's environment is already gone, the scripts still run on the host for host-side cleanup — with
KUBECONFIG=/dev/null, so a script'skubectlcan never reach your own current context instead of the lab. A qemu lab's scripts only ever run inside its VMs: if the VMs are gone or unreachable, they're skipped with a warning. - A remote lab (
--git, URL, catalog) must be approved beforeastrona destroyruns its teardown scripts, just likerun. If it isn't (you decline, or there's no terminal and no--trust), the scripts are skipped with a warning and the lab is still destroyed. - Linked clusters (
runtime.kind.clusters) can have their ownteardown.init; they run after the lab's, in reverse start order, each withKUBECONFIGpointing at its cluster (skipped if that cluster is gone). - The lab's linked clusters are destroyed even if destroying the lab itself fails.
- If
astrona runpointed your kubectl at the lab, destroy switches it back to the context you had before (only while the lab's context is still current — see kubeconfig isolation), then forgets what was remembered about the lab (~/.astrona/labs/<lab>.json: its config source and lab session). keepCluster: trueskips deleting the environment afterwards — useful while iterating on a lab locally, sinceastrona destroyre-run without it will still clean up.
Starting over: astrona reset¶
astrona reset -c ./labs/my-lab # asks before throwing the lab away
astrona reset -c ./labs/my-lab --yes # no prompt (required in scripts/CI)
reset runs the lab's teardown scripts, destroys it, and then does everything astrona run does — the lab ends up exactly as a fresh run leaves it (any changes made while working on it are gone). If the lab isn't running, it's simply created.
- The config is validated before anything is destroyed — a broken config aborts with "nothing was reset" and leaves the existing lab as it was.
teardown.keepClusteris ignored: reset always recreates.--softkeeps the cluster(s) and only puts the lab back: it deletes every namespace created after the platform (kube-system, addons, …) was set up, clears what isn't astrona's fromdefault, re-runs the bootstrap and restarts port forwards — about 5 seconds instead of 40. Cluster-wide objects a student created (CRDs, ClusterRoles, …) aren't reverted; use a full reset for those. A lab started by astrona before v0.3 has no baseline yet and needs one full reset first.--cluster <name>rebuilds just one linked cluster of a running lab, leaving the lab and its other clusters as they are.- In a terminal it asks for confirmation; without a terminal it refuses unless
--yesis passed. - A full reset of a catalog lab asks for confirmation when running without a login, like
run(--without-loginexplicitly accepts running without account progress tracking;--headlessskips the reminder and new account sessions). When signed in, it starts a fresh lab session and opens the new lab page once the lab is rebuilt.--softand--clusterkeep the current session and need no sign-in.
astrona run on a lab that's already running asks the same question: destroy it and start over? — yes does what reset does, no keeps the lab as it is. astrona run --yes starts over without asking; without a terminal, run on a running lab stops and names --yes. If other labs are running, run also offers to destroy them first (handy when moving from one lab of a training to the next) — a multi-VM qemu lab counts as one lab, all its VMs destroyed and the lab forgotten together, as astrona destroy <lab> does — only when asked in a terminal; --yes never touches other labs.
To pause a lab instead of losing it, see astrona stop / astrona start.
Source types, everywhere¶
Every script/manifest reference (bootstrap.init, bootstrap.manifests, testing.*, teardown.init, validation.script) shares the same ResourceItem shape:
- name: "human-readable label"
description: "optional, printed before the step runs"
type: "file" # or "folder" or "url" (manifests: "file" | "folder" | "url")
source: "path/or/URL"
url sources — scripts and manifests alike — are downloaded to a size-capped temp file before running or applying (a manifest URL is never handed to kubectl apply -f <url> directly). Every remote download (scripts, manifests, the lab config, QEMU base images, astrona release binaries) is https:// only, and that includes redirects: a redirect to a plain http:// URL is refused rather than followed. Every download also has an explicit byte cap so a misbehaving remote can't exhaust disk.
Command output¶
astrona run, test, submit, and destroy render progress as a compact
per-phase step view — one line per step (✓ done, ✗ failed, - skipped)
with its elapsed time, grouped under section headers (Bootstrap,
Machine: <name> for a multi-VM qemu lab, and so on). On an interactive
terminal the in-progress step shows a spinner.
The raw output of every underlying command (kind, qemu, kubectl, each
bootstrap script) is not shown by default. Instead:
- It is always written in full to a per-run log file at
~/.astrona/logs/<command>-<lab>-<timestamp>.log. The path is printed at the end of a run. Browse past runs withastrona logs. - If a step fails, the tail of that step's output is printed inline right before the error, and the command exits non-zero.
- Passing
--verbosestreams everything live as it happens and disables the spinner — the pre-step-view behaviour. The log file is still written.
Non-interactive output (piped, redirected, or CI) automatically drops the spinner and ANSI styling and prints one plain line per step.
On a terminal, problems stand out in color: Error: and what went wrong in
red (hints on what to do next follow in the normal color), [WARN] in yellow,
[INFO] in cyan, and grading results as green PASS / red FAIL with a
yellow hint:. Set NO_COLOR=1 to turn colors off everywhere; piped and
redirected output never has them.
When a step fails, its error and the tail of its output are printed with the
path of the full log — astrona logs view opens it.
Questions astrona asks¶
| Question | Answer it up front with |
|---|---|
Throw a lab away? (reset, logs clean, run on a running lab) |
--yes / -y |
| Run a lab from a URL, git repo or bundle? (shows what it will do) | --trust |
| Download and install the astrona version a lab needs? | --install-version |
The last two approve running someone else's code, so they have their own
flags rather than a general -y. Without a terminal astrona never waits for
an answer: it stops and names the flag.
For a qemu lab, a successful astrona run also prints a Connect: block
with the ready-to-paste astrona ssh <name> command for each VM.
For a kind lab with runtime.portForwards, astrona run starts those forwards after
bootstrap and prints a Port forwards: block with each forward's local
URL and status (Ready/NotReady/…). astrona port-forward list shows the
same status any time later.
Next¶
Grading covers exactly what the Proctor checks and how astrona submit/astrona test report the result.