Repository navigation
WorkerPools resolve their default SandboxConfig and keep configs in use from being deleted - #2393
Draft
Zoe Zhao (zoez7) wants to merge 1 commit into
Draft
Zoe Zhao (zoez7) wants to merge 1 commit into
Zoe Zhao (zoez7) wants to merge 1 commit into
Conversation
Zoe Zhao (zoez7)
force-pushed
the
workerpool-sandbox-config-resolve
branch
4 times, most recently
from
October 9, 2026 16:53
816c0e1 to
116b4ec
Compare
Zoe Zhao (zoez7)
force-pushed
the
workerpool-sandbox-config-resolve
branch
from
October 9, 2026 17:13
116b4ec to
642b1b0
Compare
atecontroller now resolves every spec.sandboxClasses entry to a SandboxConfig: the one configRef names, or, when configRef is omitted, the newest config of the class annotated sandboxconfig.ate.dev/is-class-default: "true" (ties on creation time go to the name that sorts first). While no default is available, a pool keeps the config it already uses, even one being deleted or no longer annotated. The result is recorded in the new status.sandboxClasses, and the new SandboxConfigResolved condition reports failures (SandboxConfigNotFound, SandboxConfigDeleting, SandboxClassMismatch, DefaultConfigNotFound) together with a Warning event. While a pool does not resolve, the controller neither creates nor updates its Deployment, and status.sandboxClasses keeps the last resolved config. Two new kubectl columns show the resolved config and the condition's status. When a WorkerPool resolves a SandboxConfig, the controller puts the sandboxconfig.ate.dev/workerpool-protection finalizer on the config before recording it in status, so a config is never in use without it. The finalizer is added nowhere else and stays until the config is deleted. A new SandboxConfigProtectionReconciler releases it: once such a config is marked for deletion, it removes the finalizer only when no WorkerPool's status names the config, confirming against the API server rather than its cache. Release lives in its own reconciler because WorkerPools carry no finalizer: a deleted pool is gone at once, and only a loop that watches WorkerPools can notice that a config has lost its last user. A config no pool has ever used carries no finalizer and deletes at once. A config that is being deleted takes no new pools. ate-setup's DeleteAteSystem strips the finalizer once atecontroller is gone, so a leftover WorkerPool cannot block deleting the SandboxConfig CRD. The fake-workersync benchmark controller compares WorkerPool status with DeepEqual, because == no longer compiles now that status has slice fields.
Zoe Zhao (zoez7)
force-pushed
the
workerpool-sandbox-config-resolve
branch
from
October 9, 2026 17:54
642b1b0 to
6f6c294
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #587. This is step 3 of the SandboxConfig management design (linked from #587), after #2332 (WorkerPool
spec.sandboxClasses) and #2333 (versioned SandboxConfig and theis-class-defaultannotation).This step resolves each WorkerPool's SandboxConfig, records it in status, gates the worker Deployment on it, and keeps a config in use from being deleted. The worker Deployment does not read the resolved config yet: the binaries an actor boots with still come from the SandboxConfig its ActorTemplate names. Pointing workers at the pool's config is the next step.
Changes
WorkerPool status reports the resolved config.
Unpinned: the pool follows the class default.
Pinned: the pool resolves to
gvisor-release1even after a newer default is created.configRef, the pool resolves to that config. It must exist and be of the same class.configRef, the pool resolves to the newest config of its class annotatedsandboxconfig.ate.dev/is-class-default: "true". If two were created in the same second, the name that sorts first wins.Resolved.DefaultConfigNotFoundis only reported when there is nothing to fall back on: the pool never resolved, or it switched sandbox class.kubectl get workerpoolgains two columns:SANDBOXCONFIG, the resolved config, andRESOLVED, the status of theSandboxConfigResolvedcondition.An unresolved pool gets no new or changed workers.
SandboxConfigResolved=Falsereports one of these reasons:SandboxConfigNotFoundSandboxClassMismatchSandboxConfigDeletingDefaultConfigNotFoundstatus.sandboxClasseskeeps the last resolved config while the condition isFalse. It records what the pool last resolved to; the condition says whether the current spec can be met.A SandboxConfig in use cannot be deleted.
sandboxconfig.ate.dev/workerpool-protectionfinalizer on the config before recording it instatus.sandboxClasses, so a config is never in use without it. The finalizer is added nowhere else and stays until the config is deleted. A config no pool has ever used carries no finalizer and deletes at once.SandboxConfigProtectionReconcilerremoves the finalizer only once no WorkerPool'sstatus.sandboxClassesnames it. It asks a cache index first; when the cache finds no user, it lists WorkerPools from the API server, bypassing the cache, before it removes the finalizer. A config pools stopped using is gone within one reconcile; one that pools use stays Terminating until they move or are deleted.configRefto it reportsSandboxConfigDeleting, and it stops being a class default. A pool already on it keeps resolving to it: a pinned pool until itsconfigRefis changed, an unpinned pool until another default exists. A Terminating config therefore only loses users.status.sandboxClasses. Every SandboxConfig is also reconciled when atecontroller starts, so a config whose last user went away while atecontroller was down is released then.Supporting changes
ate-setup deletestrips the finalizer from every SandboxConfig that carries it. It runs after the namespace, and with it atecontroller, is gone and the CRDs have been deleted, which removes every WorkerPool. Only atecontroller releases the finalizer, so without this step a config a pool had used would stay Terminating and thesandboxconfigsCRD could not finish deleting. The step is a no-op when the CRD is already gone.role.yaml): atecontroller gainsget/list/watch/patchonsandboxconfigs, for resolution, the watch, and the finalizer, andcreate/patchonevents.k8s.ioevents.spec.sandboxClasses[].configRef.nameis bounded to 253 characters, and the godoc of the fields Add sandboxClasses with an optional SandboxConfig ref to WorkerPool #2332 added now starts with the serialized field name.fake-workersynccompares status withDeepEqual, because==no longer compiles now that the status has slice fields.TestMainin the controllers package now runs the protection reconciler and seeds a gvisor class default, so pools withoutconfigRefresolve. The shipped-manifest test inpkg/api/v1alpha1cleans up with a fresh context and asserts the delete; no controller runs there, so the config never gains the finalizer.Breaking change
A WorkerPool whose class has no resolvable SandboxConfig gets no new or changed workers. An existing Deployment keeps its workers but stops scaling or updating until the pool resolves. Before upgrading, make sure each class your pools use either has a config annotated
is-class-default: "true"or is pinned withconfigRef. The bundledgvisor-defaultand the micro-VM config fromhack/install-microvm-deps.share already annotated.If you roll atecontroller back past this PR, nothing removes the finalizer from configs that carry it anymore. Clear it by hand with
kubectl patch sandboxconfig <name> --type=merge -p '{"metadata":{"finalizers":null}}'.