Repository navigation
serving: stage LLMService deletion - #21
Merged
Merged
Conversation
Delete the ModelRoute and wait for it to go, let routes settle, uninstall the release, then wait for its pods before dropping the finalizer. Pods past forceDeleteAfter are deleted with a zero grace period, and past maxWait the finalizer is dropped with outcome TimedOut. Every wait requeues after 5s, status is written before each action, and stages only move forward, so a restart resumes where it stopped. Orphan, foreign releases and suspend behave as before. spec.deletion holds maxWait (default 1h), routeSettle (default 60s) and forceDeleteAfter (off when unset). The annotations serving.modelsphere.dev/deletion-max-wait, route-settle and force-delete-after override them. helm.Release carries the manifest so the controller can find the release's ModelRoute, and RBAC gains delete on pods. Each test case is one YAML file under testdata/controller and testdata/helpers.
The sglang-mock chart can render a ModelRoute and a single mock pod with a preStop and grace period. The e2e suite checks route-before-Deployment ordering, a 20s preStop, force delete of a pod that ignores SIGTERM, and RouteStuck. docs/llmservice.md describes the stages, knobs and status.
huskcass
commented
Oct 23, 2026
huskcass
left a comment
Collaborator
Author
There was a problem hiding this comment.
No blockers.
Verdict: good to merge. The staged finalizer only moves forward, picks up at the recorded stage after a restart, keeps the finalizer while live pods remain unless maxWait or force-delete applies, and the generated CRD and RBAC files are in sync.
— Review Golden
A routeSettle longer than maxWait held the finalizer past the budget. The settle now ends at the earlier of routeRemovedAt+routeSettle and the deadline, then uninstalls. The docs note that annotation changes during deletion apply on the next pass.
huskcass
commented
Oct 23, 2026
huskcass
left a comment
Collaborator
Author
There was a problem hiding this comment.
No blockers in this change.
Verdict: the route-settle fix is correct. The settle wait now ends at min(routeRemovedAt + routeSettle, deadline), the stage moves on at the deadline, and the new case fails without the fix.
— Review Golden
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.
Deleting an LLMService used to uninstall the release and drop the finalizer immediately, so the CR vanished while pods were still terminating and the ModelRoute was removed at the same moment as the Services it routes to. This stages the teardown so new traffic stops before the stack goes, and the CR stays until the release's pods are gone.
Stages
flowchart TD D[deletionTimestamp set] --> S{spec.suspend?} S -- yes --> X[no-op, keep finalizer] S -- no --> O{Orphan or foreign release?} O -- yes --> F[drop finalizer] O -- no --> M{ModelRoute in release manifest?} M -- no, outcome NoRoute --> U M -- yes --> R[RouteRemoving: delete ModelRoute] R --> RG{NotFound?} RG -- no, before deadline --> R RG -- no, past deadline: RouteStuck, its finalizer kept --> U RG -- yes --> ST[RouteSettling: wait routeSettle] ST --> U[Uninstalling: helm uninstall] U --> P[PodsTerminating: list release pods] P -- none left --> REL[Released] --> F P -- past forceDeleteAfter --> FD[ForceDeleting: grace 0, UID precondition] --> P P -- past maxWait: TimedOut --> RELkind: ModelRoute(routing.modelsphere.dev/*,routing.gpucluster.io/*) from the newhelm.Release.Manifest. The operator never strips the route's finalizer.app.kubernetes.io/instance=<release>∪app=<release>-<chart>, ignoring Succeeded/Failed. A pod within its grace always counts; past grace it is gone if its node is NotReady or missing, otherwise after grace + 5m.deleteon pods (only used for the opt-in force delete).Knobs
Precedence: annotation >
spec.deletion> default. Annotations are re-read on every pass, including ones added after the delete. An invalid annotation emits WarningInvalidAnnotationand falls back.0sis a real zero in both layers.spec.deletion.maxWaitserving.modelsphere.dev/deletion-max-wait1hstartedAt; past it the finalizer is droppedspec.deletion.routeSettleserving.modelsphere.dev/route-settle60sspec.deletion.forceDeleteAfterserving.modelsphere.dev/force-delete-afterserving.modelsphere.dev/deletion-policy: OrphanNo other spec field changes.
Status
status.phase: Deleting, aDeletingcondition (reasonsRouteRemoving,RouteSettling,RouteStuck,NoRoute,Uninstalled,WaitingForPods,PodsForceDeleted,TimedOut,Released), one event per transition, plus WarningPodForceDeletedper pod.Tests
internal/controller/serving/testdata/controller/: route deleted before uninstall, NotFound then settle, NoRoute, RouteStuck keeps the route finalizer, pod wait, Succeeded/Failed ignored, NotReady node, force delete, mid-deletion annotation, TimedOut, restart resume, Orphan, foreign release, suspend, annotation-over-spec, invalid annotation, 60s default,0ssettle.testdata/helpers/{deletionKnobs,modelRoutesFromManifest,nextStage,podCounts}/.test/e2e/deletion_test.go,e2ebuild tag): ModelRoute removed before the Deployment, a 20s preStop holdsPodsTerminating,force-delete-after: 5son a pod that ignores SIGTERM (grace 600), andRouteStuckwith the fake autoconfig stopped. It runs in CI (make test-e2e); it was not run locally.gofmt,go vet(with and without-tags e2e),make manifests generateclean,go test -raceon all non-e2e packages.