Skip to content

Golden snapshot bootstrap

An ActorTemplate says what an actor of this kind looks like (container image(s), entrypoint, env, volumes, compute size, and a workerSelector), and by itself that’s just a record in ateapi’s database. So how does Substrate get from that record to a golden snapshot that lets later actors of this template skip their cold boot? Substrate boots the workload for real on a worker, lets it initialize, and then captures whatever state the running workload ended up with.

In the diagram below, we can follow the whole lifecycle, from the golden actor’s warmup on a worker to the tag that later actors restore from.

How a golden snapshot gets created ateapi's template reconciler builds a template's golden snapshot: it creates a throwaway golden actor in the template's atespace, resumes it so it cold-boots on a real worker, waits out a warmup (zero when every container has a wakeupProbe, otherwise 20 seconds), then suspends it synchronously so atelet uploads the snapshot through the object-store plugin. CreateTag copies the snapshot under a tag's prefix, the golden actor is deleted, and the tag is recorded as status.goldenSnapshotStatus.goldenTag. Later actors of the template are created holding a borrowed copy of that tag's snapshot and restore it on a shared worker instead of cold-booting. How a golden snapshot gets made It's not built. It's run: a throwaway actor really boots on a real worker, then its state is captured and tagged. ① ActorTemplate created kubectl ate create actor-template: a record in ateapi's PostgreSQL store, not a CRD status: empty ② ateapi's template reconciler creates a "golden" actor in the template's atespace, named by the template UID, then calls ResumeActor picked up on the next resync (20 s) ③ REAL worker a shared worker from an eligible pool sandbox (gVisor or micro-VM) no snapshot yet, so atelet Run: a cold boot golden actor boots from scratch from the template's OCI image warmup: 0 s if every container has a wakeupProbe, else 20 s then: SuspendActor (mode = SYNCHRONOUS) checkpoint at the template's fidelity; atelet uploads via its object-store plugin status.goldenSnapshotStatus.snapshotTime = end of warmup ④ Object storage GCS or S3 golden actor's snapshot: .../actors/<uid>/ snapshots/<uuid> CreateTag copies it to: .../tags/<tag-uid> zstd files + manifest.json ⑤ ActorTemplate status.goldenSnapshotStatus .goldenTag = the tag recorded after the golden actor is deleted record tag AFTER THE GOLDEN TAG - what every later actor of this template gets to skip No more ① to ④. The golden tag's snapshot is the new starting line. CreateActor freezes the golden tag into the actor borrowed snapshot: generation 1, owned by the tag, not copied first ResumeActor restores it on any compatible worker serving after restore no cold boot, no warmup

The bootstrap occupies room on a worker for the warmup window, and once the golden tag is recorded, every later actor of this template skips the boot and restores from the tagged snapshot.

Who drives it?

The reconciler is a loop inside ate-api-server rather than a Kubernetes controller, and there is no watch on templates. Every resync interval (--template-resync-interval, 20 s by default) it lists all templates and queues each one whose golden build is neither finished nor failed, skipping templates with goldenSnapshotConfig.mode: DISABLED, and a small pool of workers drains the queue. Each pass over a template holds a PostgreSQL lease on that template, so several ateapi replicas never interleave its transitions.

It drives the golden actor through the same Control RPCs a client would call (CreateActor, ResumeActor, SuspendActor, CreateTag, DeleteActor), in process, acting as the system.

The golden actor

PropertyValue
AtespaceThe template’s own atespace, under the same access policies and storage location as ordinary actors
NameThe template’s UID, so a template recreated under the same name never collides with an old golden actor
Source snapshotNone, so it always cold-boots
Egress policyThe template’s goldenEgressPolicy, if set (preview)

Sequence

sequenceDiagram
  autonumber
  participant U as Client<br/>(kubectl ate)
  participant A as ateapi
  participant TR as template reconciler<br/>(inside ateapi)
  participant W as Worker<br/>(golden actor)
  participant OS as Object storage

  U->>A: CreateActorTemplate
  A-->>U: template stored, status empty

  Note over TR: next resync picks it up,<br/>takes the template lease
  TR->>A: CreateActor(atespace=template's,<br/>name=template UID)
  A-->>TR: SUSPENDED, no snapshot
  opt goldenEgressPolicy set
    TR->>A: CreateActorEgressPolicy
  end
  TR->>A: ResumeActor
  A->>W: Run - cold boot from the template spec
  W-->>A: up (after every wakeup probe answers 200)
  A-->>TR: RUNNING
  TR->>A: status.snapshotTime = now + warmup

  Note over TR: requeue until snapshotTime<br/>(not a blocking sleep)

  TR->>A: SuspendActor(mode=SYNCHRONOUS)
  A->>W: Checkpoint
  W->>OS: upload snapshot
  A-->>TR: SUSPENDED, durable snapshot COMPLETED
  TR->>A: CreateTag(name=template UID,<br/>scope=PUBLISHED)
  A->>OS: copy snapshot under the tag's prefix
  TR->>A: DeleteActor(golden actor)
  A->>OS: delete the actor's own snapshot prefix
  TR->>A: status.goldenTag = tag

The warmup wait

After the golden actor is running, the reconciler stamps status.goldenSnapshotStatus.snapshotTime = now + warmup and requeues the template for the remaining time. The work queue holds the item, so no goroutine is blocked. How long is the warmup? It depends on whether the containers declare wakeup probes.

  • If every container declares a wakeupProbe, the warmup is 0. ResumeActor only returns once every probe answers 200, so the workload is already serving by the time the reconciler sees RUNNING.
  • Otherwise it falls back to a 20 s warmup as a coarse “give the workload time to initialize” budget.

If you rely on the fallback and your workload needs longer (e.g. downloading a model), the golden snapshot captures a partially initialized process, and that is what every actor of this template will restore from. Declaring a wakeupProbe on every container makes the snapshot point deterministic. Keep in mind that the golden actor holds a real slot on a worker for the whole window, so if the eligible pools are tightly sized, the bootstrap competes with regular actors for room.

Why a tag, and why delete the actor?

A tag is an atespace-owned, immutable name for its own copy of one external snapshot. Copying the golden actor’s snapshot into a tag means the golden snapshot outlives the actor that produced it, which lets the reconciler delete the golden actor (and the actor’s own snapshot prefix with it) once the copy is complete. The tag is created with PUBLISHED scope, and it is only usable by actors of the template it was taken under, since CreateActor checks that a source tag’s template UID matches.

What does “ready” mean?

Once status.goldenSnapshotStatus.goldenTag is set, the template’s actors start from the golden snapshot.

  • CreateActor for this template, with no explicit source tag, freezes the golden tag into the new actor, so the actor is born SUSPENDED holding a borrowed snapshot (generation 1, owned by the tag). See Create actor.
  • The actor’s first resume restores that snapshot instead of running the container from scratch. Because the snapshot is tag-owned and shared by many actors, atelet can serve it from a node-local snapshot cache after the first download on that node.
  • The actor’s first suspend writes a snapshot under its own prefix and stops borrowing.

An actor created before the golden tag exists has no snapshot and cold-boots on its first resume, and it does not pick up the golden snapshot later. Once a template has a golden tag, the reconciler skips it. Templates are immutable, so there is no in-place refresh, and to get a new golden snapshot you create a new template.

Failure paths

Each pass is level-triggered, which means it observes the golden actor (and any tag already named after it), takes the one action that state demands, and re-observes. Every action is reentrant, so a pass that dies midway (e.g. a lost ateapi replica, a failed RPC) is picked up by the next one. Ordinary errors (no worker with room, an object-storage blip, etc) are requeued with exponential backoff.

A few outcomes are terminal. For those, the reconciler writes status.goldenSnapshotStatus.errorMessage as <Reason>: <detail> and stops retrying.

ReasonWhen
GoldenActorCrashedThe golden actor reached CRASHED before its snapshot was taken, e.g. because a wakeup probe never answered 200.
GoldenActorInvalidCreateActor rejected the golden actor as invalid.
GoldenTagConflictA tag with the golden name exists but belongs to another actor or template.
GoldenEgressPolicyConflictThe golden actor already has an egress policy that differs from the template’s.
GoldenActorUnexpectedStateSomething else moved the golden actor to DELETING or REVERTING.

A failed build still leaves the template usable, and actors created from it simply cold-boot. To try the build again, recreate the template.

  • ActorTemplate - the resource itself.
  • Create actor - where the golden tag is frozen into a new actor.
  • Resume actor - the restore path a golden-seeded actor takes.
  • Snapshot - tags, fidelity, and durable storage.
  • ateapi - where the reconciler runs.
  • Atespace - the template’s atespace hosts its golden actor and tag.