WorkerPool
WorkerPool is one of Substrate’s CRDs, and it’s how you ask for N
pre-warmed worker pods of a given sandbox class, ready to host actors. From a
single pool, atecontroller resolves which
SandboxConfig the pool runs and maintains a
Kubernetes Deployment.
The spec
apiVersion: ate.dev/v1kind: WorkerPoolmetadata: name: counter-microvm namespace: ate-demo-counter-microvm labels: workload: counter-microvm # matched by ActorTemplate.workerSelectorspec: replicas: 2 # required workerImage: .../ateworker-microvm # required sandboxClasses: # required; exactly one entry today - name: microvm # gvisor | microvm; immutable configRef: # optional; else the class default name: microvm template: # optional pod metadata, scheduling, resources resources: limits: { cpu: "1", memory: ... } # labels, annotations, nodeSelector, tolerations, # priorityClassName, nodeAffinity, serviceAccountNamestatus: replicas: 2 readyReplicas: 2 sandboxClasses: - name: microvm configRef: { name: microvm } # the SandboxConfig in use conditions: [{ type: Resolved, status: "True" }]The spec has four fields, and only template is optional. The status
block is what atecontroller reports back.
replicas(required,>= 0) is the desired worker pod count.workerImage(required) is the worker container image, eitherateworker-gvisororateworker-microvmto match the class.sandboxClasses(required, exactly one entry) is the runtime family the pool runs. The class name drives the worker pod shape and can’t change after creation.configRefoptionally names a cluster-scoped SandboxConfig of the same class, and without it the pool uses the newest SandboxConfig of that class annotatedsandboxconfig.ate.dev/is-class-default: "true".template(optional) holds extra labels and annotations for the Deployment and pods (theate.devdomain is reserved),nodeSelector,tolerations,priorityClassName,nodeAffinity,resources, andserviceAccountName(defaultateworker).
kubectl get workerpools prints the desired, current, and ready replica
counts, the SandboxConfig in use, and whether the class resolved.
Labels and capacity
Two settings on a pool matter most to the scheduler. Both of them end up on the Worker records that it places actors against.
- The pool’s own
metadata.labelsare copied onto every Worker record, and they’re what template and actor selectors match. (spec.template.labelsonly label the Kubernetes objects.) spec.template.resources.limitsbecome each worker’s CPU and memory capacity, which the scheduler fills with actors’ declared limits. Kubernetes schedules the pods by their requests, so a request below the limit keeps the pool placeable without changing what actors may use. Each worker also reports an actor-count limit of its own.
What atecontroller does with it
flowchart LR
WP[WorkerPool CRD] --> CTR[atecontroller<br/>WorkerPool reconciler]
SC[(SandboxConfig)] -. "resolve configRef<br/>or class default" .-> CTR
CTR -- SSA apply --> D["K8s Deployment<br/>{name}"]
D --> P1[pod 1]
D --> P2[pod 2]
D --> PN[...]
P1 --> S[atecontroller<br/>worker syncer]
P2 --> S
PN --> S
S -- "Create / Mark /<br/>Drain / DeleteWorker" --> API[ate-api-server]
click CTR "/components/atecontroller/" "atecontroller"
click API "/components/ateapi/" "ate-api-server"
click SC "/concepts/sandboxconfig/" "SandboxConfig"
As the diagram above shows, the WorkerPool reconciler turns a pool into a Kubernetes Deployment. Along the way, it resolves and protects the pool’s SandboxConfig and keeps the pool’s status up to date.
- It resolves the class’s
configRef(or the class default), checks that the config’s class matches, and reports the result instatus.sandboxClasseswith aResolvedcondition. A missing, deleting, or mismatched config setsResolvedtoFalse, in which case an existing Deployment keeps running on the last resolved config and a new pool waits. - It protects the config, so a SandboxConfig that a pool’s status names carries a finalizer and can’t be deleted out from under the pool.
- It server-side-applies a Deployment named after the pool, owner-referenced
by it, with the replica count,
workerImage, and the pod shape described below. - It reports status (
replicas,readyReplicas, and the label selector). The CRD exposes ascalesubresource, sokubectl scaleworks.
The reconciler doesn’t create Worker records or work out runtimes. A separate worker syncer in atecontroller watches the pods and registers each one with ate-api-server (see Worker lifecycle), and atelet is what turns the resolved SandboxConfig into the runtimes each worker advertises.
The worker pod shape
Every pod in the Deployment runs a single container named worker. The list
below covers its privileges, host state, identity, capacity, ports, placement,
and shutdown.
- The container isn’t privileged. It runs as root with a fixed capability set for its sandbox class, and seccomp and AppArmor are unconfined because the sandbox mounts filesystems and pivots root. The micro-VM class adds the capabilities virtiofsd needs.
- The host’s
/var/lib/ateis mounted at the same path as in atelet, so the two share sockets, actor directories, and cached binaries. - A projected pod certificate identifies the worker to atelet and to the router, and a separate trust bundle verifies the egress gateway.
- The container’s own CPU and memory limits are projected into it through the downward API.
- Ports 443 and 8443 carry router-to-atunnel traffic, and 8080 serves
/readyz, which fails until the worker has registered. - Pods tolerate the
ate.dev/sandboxClass=<class>:NoScheduletaint, so a cluster can reserve nodes per class. Micro-VM pods request oneate.dev/kvmfrom atelet’s device plugin, which keeps them on KVM-capable nodes, and mount/dev/net/tun. - A one-hour termination grace period gives hosted actors time to finish checkpoints, and rollouts replace at most 10% of pods at a time with no surge.
Isolation and eligibility
Pools are the capacity and placement boundary, and because binding is selector-based, you can use them in several ways.
- To separate sandbox runtimes, since a
gvisorpool and amicrovmpool never share workers and a snapshot only restores on its own class. - To steer workloads with labels, by labeling a pool (e.g.
tier: gpu) and gating templates to it withworkerSelector. - To size workers differently per workload class and scale them independently.
- To isolate failure domains.
Scaling
When you edit spec.replicas (or use kubectl scale), atecontroller updates
the Deployment, and new pods register themselves with no actor downtime. On
scale-down a terminating worker is marked DRAINING and takes no new actors.
Actors that suspend during the grace period stay resumable, while actors still
running when the pod disappears are moved to CRASHED.
Related
- Worker - what a pool member actually is.
- SandboxConfig - where a pool’s sandbox binaries come from.
- ActorTemplate - the
workerSelectorthat picks pools. - Worker lifecycle - the state machine.
- atecontroller - the reconciler and worker syncer.