Model Deployment Status
A ModelDeployment reports its phase, ready replicas and serving endpoint in status. Use these
fields and its conditions to diagnose a deployment that is still starting or has lost capacity.
Contents
Status
Phase and role summary
status.phase is the field to read first: Starting, Ready, Degraded or Deleting. Degraded
means some required serving capacity is ready and some is not: either some role replicas are down, or
all role replicas are ready but a declared router has no ready replica.
status.roles[] carries name, kind, desired, ready, quotaReserved, unmanaged,
assignedFlavors, parallelism and endpoints per role.
kubectl get modeldeployments shows status.roleSummary in its ROLES column. It counts current
Ready instances by kind: R is managed router Pods, S is ordinary servers, E is encoders,
P is prefillers, and D is decoders. For example, 1R2S means one Ready router and two Ready
servers; 1R1E1P1D means one Ready router, one Ready encoder, one Ready prefiller and one Ready decoder.
During a replacement, status combines desired and observed fields. See
Rollout behavior
. desired and parallelism describe the current
spec; they can differ from running replicas while replacement waits. ready counts replicas still
serving their deployed configuration. unmanaged and cache conditions read deployed command mode.
Without a router, the R part is absent; a declared kind with nothing Ready shows zero. Several
roles of the same kind are summed. A serving instance made of several Pods still counts once; use
status.roles[].desired to see the requested count.
Serving endpoints
Without a router, status.endpoint is the address the deployment-wide Service serves on, in the form
<scheme>://<name>.<namespace>.svc:<port>. The scheme and port come from the first role that is not
Encode, and the scheme is https only when that role passes --ssl-certfile or --ssl-keyfile.
With a router, it is absent until the router is ready, then uses the router Service’s http transport.
Each role, including Encode, also gets its own <deployment>-<role> Service.
status.router publishes the contract used to render the router: its implementation and address,
the cache pool’s client endpoint, the metrics names and port, and one entry for every role containing
the effective kind, selector, direct endpoint and any dialable KV-event endpoints. The event entries
are absent when the corresponding rendered Pods do not carry publisher configuration.
This router contract names metrics sources for routing; it is not a live utilization sample.
The separate Model Deployment Metrics
subresource reads the current
router and engine Pod endpoints and reports partial coverage explicitly.
Only --ssl-certfile or --ssl-keyfile turn the scheme to https. The rest of the --ssl-*
family does not: both engines enable TLS for those two alone, so any other ssl argument passed by
itself leaves an ordinary HTTP server.
Readiness gates
ready counts replicas whose engine answered, not replicas whose process started, except on the
three configurations listed below, which carry no gates and keep the weaker meaning. One still loading its
model counts as not ready for as long as that takes.
Both figures count replicas, which is the same as counting Pods only while a replica is one Pod.
A role at size: 2 with replicas: 2 runs four Pods and reports desired: 2. A replica of several
Pods is ready only when every member it declares is, so a role showing 1/2 with three of its
four Pods running is reporting one whole instance, not three quarters of the role.
A gated replica carries a startup gate, a readiness gate and a liveness gate, all reading the
engine’s own GET /health on the port the Service targets, so ready == desired and “the endpoint
answers” are one fact rather than two.
The KV cache store’s leader answers a route of the same name that is deliberately not a readiness signal; see KV Cache Election and Failover . They are different programs, and only the engine’s route is read here.
The startup gate allows thirty minutes per attempt, not in total. A replica that has not answered by then is restarted and gets the same budget again, so a model that never finishes loading is a restart loop rather than a replica stuck at not-ready.
The liveness gate never sees that window, because the kubelet suppresses liveness and readiness alike until the startup gate succeeds.
A replica that stops answering loses readiness first, and is restarted only if it stays quiet for longer. The liveness gate’s failure threshold is wider than the readiness gate’s.
Readiness withdraws a replica from the
Serviceand is undone by answering again; a restart throws away a model that took the startup budget to load. Different thresholds keep those two events different, and the liveness gate is what gives a quiet replica a way back, since this operator deletes a replica only to shed one the spec no longer declares or to turn over an outdated one.
The operator tells the engine where to listen. It renders --host 0.0.0.0 and --port <the port the Service targets> into the engine’s own command line, so the address traffic is sent to and the
address the engine opens are one decision. The two engines do not agree on a default (vLLM opens
every interface on 8000, SGLang opens 127.0.0.1:30000), and an engine left on its own would be
unreachable on one of them.
Both flags are filled when missing. A role that passes either through extraArgs keeps its own
value, and the operator adds only the one that is absent.
A role that enables TLS is still gated, over HTTPS, on the criterion above. Only the transport moved (the address is still the operator’s own), and the kubelet does not verify the server certificate on a probe, so a self-signed pair is graded like any other.
Three configurations carry no gates at all: a role that replaces the command through
roles[].command; a role that moves where its engine listens, with --host or --port, or that
demands a client certificate a probe has none to present, with --ssl-cert-reqs set to anything but
0 or 1 alongside a certificate or key; and a role declaring its port as UDP or SCTP.
The --ssl-cert-reqs values: that flag is an integer, and only 0 (CERT_NONE, the default) and
1 (CERT_OPTIONAL) let a certificate-less probe through, so a role passing either keeps all three
gates. Everything else withdraws them: 2, a value outside that enum, one that is not a number,
and the flag with no value at all. Losing a gate costs a signal, while fitting one to a listener
that refuses it restarts a replica that is serving.
The first two fail one way and the third the other. Gating an address the operator does not know would restart a replica answering every request; an HTTP engine speaks TCP whatever the declaration says, so a gate would pass while the published endpoint forwards a protocol nothing answers. Replicas of all three are Ready as soon as their process starts.
Quota and flavors
quotaReserved is how many of the role’s replicas hold a quota reservation. Each replica is its own
Workload and reserves on its own, so a role sits anywhere between zero and desired while capacity
arrives; a role that shared one Workload passed all-or-nothing, and this figure could not exist.
The field is always present once written, and its zero is an observed zero: it is counted from Pod
and Workload lists that succeeded, and a failed list writes no status at all rather than a zero. The
two readings call for opposite actions: a visible 0 means the pass read everything and no replica
holds quota, so investigate capacity; a field that is not there means the pass could not see, so wait,
or look at the operator rather than the pool.
assignedFlavors answers which accelerator models did this role’s replicas actually get, read from
each replica’s own Workload’s per-PodSet assignment. It is a deduplicated, sorted list, because
each replica is its own Workload and Kueue assigns a flavor per Workload, so two replicas of one role
can land on different flavors.
It is absent rather than empty while no assignment exists, and absent does not only mean “not
admitted yet”. The field reports the flavor of a replica’s accelerator credits: a role admitted onto
a pool carrying no accelerator at all reports nothing here while being perfectly healthy: its
Workloads hold assignments, naming a flavor for cpu.
One entry means every assigned replica of the role names it. Several entries mean the replicas were assigned different flavors, the signal to investigate and not a degraded form of one answer.
Which replica carries which flavor is deliberately not here: the ordinal such an answer would key
on is the converger’s internal slotting rather than a promise this API makes, and a reader who
needs the mapping reads the replicas’ own Pods. A flavor reported here is read through the same
lens the per-accelerator admission gate uses, so the two cannot disagree.
Parallelism
parallelism reports the parallelism the role’s own arguments declare, with the provenance of
the reading. Every pass reads each managed role’s own argument stream — its extra arguments,
under the engine’s own parser rules: registered spellings and their abbreviations, a repeated
flag resolved last-wins, everything past a bare -- ignored, and the environment read only
where the engine itself reads it.
Nothing is defaulted into the read: a degree the role does not declare is null, an explicit
1 and an explicit local 0 are preserved as written, and an observed mode carries its
explicit boolean — the absence of a mode key is not false.
parallelism.loadBalance derives the load-balancing mode from the role’s arguments: the vLLM
external-balance flag or an assigned data-parallel rank derives External, the hybrid and
multi-port flags derive Hybrid and MultiPort, flags that disagree leave it Unknown with
the flags named in parallelism.source.unreadableReason, and anything else balances
Internal.
parallelism.source says where the reading came from: kind (ExtraArgs, Command,
UnmanagedCommand), whether the reading itself was complete, and an unreadableReason
naming what could not be established — the source when complete is false, or a derived fact
such as the load-balancing mode when the declared flags disagree, which a complete reading can
still carry.
A role that replaced its command line is not read at all — the replaced argv is the role’s
own book — so its source is Unknown with complete: false and reason Unmanaged, and no
degree, mode or balance is claimed from it.
A stream that cannot be parsed, including an effective environment value that is not an
integer, is likewise Unknown with the flag and value named — never a silently defaulted
degree.
Endpoint eligibility
endpoints separates what the operator qualified from what Routers were actually observed to
select, and the two never collapse into one number:
endpoints.eligibleis how many endpoints the qualification list holds, written only when that list is complete.nullmeans the set was never observed; an explicit0means it was observed and is empty. It is named for eligibility and never presents itself as serving.endpoints.serving.stateis the actual per-Router confirmation:Confirmedmeans complete, fresh, identity-bound Router views agree;NotConvergedmeans complete views disagree;Unknownmeans a required view is missing, stale or indeterminate;NotConfiguredmeans the deployment declares no router at all, so there is no Router process whose answer could be missing. OnlyConfirmedcarriesendpoints.serving.value, and its zero is as real as any other number.
Replica eligibility depends on five criteria. An unknown result preserves the previous eligibility decision. MemberSetComplete (every member the role declares exists), MembersReady (each
answered its engine’s health route), GroupForward (each member’s forward path was observed to
carry a request), NoPlannedRetirement (no retirement operation holds the replica) and
NoPendingReplacement (nothing is already on its way to replace it).
| Criterion result | What it does |
|---|---|
| verified | passes; the replica contributes to endpoints.eligible |
| failed | revokes the replica from the list immediately, whether or not the other criteria passed |
| unknown | holds: it never revokes on its own and never restores |
A criterion that cannot be evaluated is not evidence of a fault. An engine that is still loading, a Router view that has gone stale and a replica whose group was not observed are all cases where the operator cannot tell.
endpoints.eligiblestaysnullin each, because a short list here would be contradicted by a later pass.
A replica that was qualifying and stops is restored only when all criteria pass again and the group still holds the same members. A replica replaced in the meantime is a different group, and the answer that held for the old one does not carry over to it.
The EndpointEligibility condition says the same thing at the deployment level: while no
qualification observation exists it is Unknown with reason NotObserved — not False,
which would say the operator disqualified endpoints it saw. A pass that has qualified nothing
reports the condition and leaves the per-role counts null.
Retirement
status.retirement is present only while a retirement operation is running, at most one at a
time; absent means no operation. It names the role and replica ordinal it retires (ordinal
0 is a real position and is always encoded), the metadata.generation it was admitted
against, and the target member Pod and Workload UIDs its deletions carry as preconditions.
state is the protocol step (Admitted, Disqualified, Withdrawing, Draining,
Deleting, Settling, Aborted, Completed), reason names what put it there, and
startedAt/deadline/phaseStartedAt carry the budgets across a controller restart.
lastConsumedRetryToken is the retry directive token already consumed, persisted before the
annotation that carried it is cleared.
Three kinds of removal start an operation, and they are the ones that take capacity away from a
role the spec still declares. A replicas reduction starts one for the ordinals it no longer
declares, a shed of surplus replicas starts one for the whole set at once, and a replica of a role
the spec has dropped — by a scale-away or a role rename — starts one.
Everything else that deletes a replica deletes it without an operation, including the rollout that turns an outdated replica over.
The three are the removals for which a withdrawal and a drain are worth their cost, since each one takes away capacity the deployment had. A rollout replaces a replica with an identical one and has nothing to protect, so it does not pay for a protocol.
A second removal arriving mid-operation is refused, and the replica is held. At most one
operation runs at a time, and the replica a second intent named stays where it is, so it is not
taken out from under the first. Look for reason naming the refusal when a scale-down appears to
stall behind another removal.
Each step has its own budget, and the operation has a total. A phase that runs out ends the operation, and the total is the earlier of the two:
| Step | Budget |
|---|---|
Withdrawing |
30 s |
Draining |
240 s |
Settling |
30 s |
| whole operation | 300 s |
Aborted is retained capacity, not a rollback: a budget exhausted before deletion leaves every
member and the Workload in place, and the reason names the step that ran out. Completed is the
one state that clears the field, because a reservation left behind would keep holding a slot no
operation occupies.
The drain step is where a replica still holding requests is caught before anything is cut; it is described in Model Deployment Shutdown .
Conditions
Ten conditions describe states that a single phase cannot represent. They are independent: “quota reserved but
cache not attached” is a real and actionable state. Nine are described below; WeightsReady is
described with the artifact it reports on, in Model Artifact
.
DomainRegistered
Whether the referenced Binding resolved and its domain was read.
| Value | Reason | Action |
|---|---|---|
True |
Registered |
nowhere; the domain in status.kvCache is current |
True |
NotApplicable |
nowhere; the deployment declares no kvCache, so there is no Binding to resolve |
False |
BindingNotFound |
create the Binding — an admin doing so is what grants access |
False |
BindingNotReady |
wait for it, or look at the pool it points at |
False |
BindingDeleting |
find who deleted it; the replicas keep writing to the domain they attached to |
QuotaReserved
Whether every one of the deployment’s Workloads holds Kueue quota. Every
replica is its own group with its own Workload, so a deployment of N replicas is N Workloads. True
is therefore an answer about the whole set and not about whichever Workload sorts first, because half
a deployment holding quota is not the deployment holding quota.
A message that names groups names them by role, never by instanceType: two roles can name one
type, so a type would point at two roles’ groups at once while a role names exactly the set that
waits. The queue an operator has to free follows from the named role’s own instanceType.
It mirrors the Workloads’ own conditions, so it stays current after admission rather than
reporting only the moment a Workload was admitted.
| Value | Reason | Meaning |
|---|---|---|
True |
Reserved |
every replica has quota reserved; with one role the message names its cluster queue |
False |
Pending |
at least one replica is waiting for quota; the message names how many, and where they wait |
False |
PodGroupIncomplete |
fewer replicas exist than a role declares, so Kueue composes no Workload at all for the missing ones; the message carries <have>/<want> and the role’s cluster queue |
False |
PreemptedInPart |
a higher-priority workload reclaimed some replicas’ quota while others are still admitted; the message says whether any role kind has no admitted replica left, and so whether the loss is of capacity or of a whole role |
False |
Parked |
the set failed to assemble for long enough that the joint check deactivated its Workloads; an identical re-apply does not clear it |
False |
NoQueueInReservedNamespace |
the deployment is in a reserved namespace, which has no LocalQueue, so it will never be scheduled |
Unknown |
AdmissionInFlight |
a group is complete and has no Workload yet — Kueue composes it asynchronously, so absence is admission in flight, not refusal |
Unknown |
NoReplicas |
no replica has been created yet |
Unknown |
AllReplicasTerminating |
every replica is on its way out, so none holds quota to report on |
PodGroupIncomplete, NoQueueInReservedNamespace and AdmissionInFlight all observe no Workload
but mean different things. The first clears when the missing replica exists; the second is permanent
because reserved namespaces deliberately have no LocalQueue; the third clears when Kueue composes the
Workload. Telling them apart is why the reason exists.
Pending, PreemptedInPart and Parked all say the set does not hold quota, and they are separate
because the action differs. Pending resolves itself when capacity appears. Parked is over already:
those workloads were deactivated and no longer ask for anything. PreemptedInPart is the one to act
on, and the replicas that kept their quota hold accelerators until either the reclaimed replicas are
admitted again or the deployment is deleted.
How long the wait is worth making depends on what preempted the deployment, which is outside this object and this operator. The condition reports the state and leaves that decision with you.
The cost of the wait lands on the queue, not on this deployment. The replicas that kept their quota
hold accelerators while the set cannot serve in full, and Kueue accounts that quota as used: the
next workload drawing from the same ClusterQueue is refused with insufficient unused quota. A
deployment preempted in part starves whatever is queued behind it.
What to do about it:
- Add capacity. The reclaimed replicas are admitted again on their own once capacity returns. The state self-heals but is UNBOUNDED: no timeout, no give-up, nothing makes the holder yield, so capacity that never comes back leaves it standing.
- Delete and recreate the deployment. Deleting is the other release for the quota the kept replicas hold.
- Move the roles apart. Each role draws from the
ClusterQueueits owninstanceTyperesolves to; giving the roles different types keeps one preemption from reclaiming both halves out of the same queue.
CacheAttached
Whether the cache is observed to be in effect, which is a different question from whether it was configured. It is judged downstream of the engine and never on a rendered flag or a log line.
| Value | Reason | Meaning |
|---|---|---|
True |
NotApplicable |
the deployment declares no kvCache, so there is no cache to attach |
True |
CacheActive |
a ready replica reports succeeding store operations |
True |
CacheActive |
no replica gave an account, and the reuse domain holds data — this attributes to the domain, which is shared by every deployment on its Binding |
False |
CacheOperationsFailing |
a ready replica reports store operations of which none succeeded; the engine is serving without the cache |
Unknown |
Unmanaged |
a role took over its command line, so the operator rendered no cache client |
Unknown |
NoReplicaReady |
no replica is ready, so no engine has an account to give |
Unknown |
NoObservationAvailable |
ready replicas gave no account and the domain reports nothing held |
NoObservationAvailable is Unknown rather than False because an attached deployment that is
simply idle looks exactly like an unread one: no supported engine signals “the connector
initialized” before any traffic, so silence is the most common steady state. A connector that
cannot come up keeps its replica from ever becoming Ready, which is reported there.
ReplicasUpToDate
Whether the running replicas match the desired explicit spec hash and meet
the deployment’s minimum modeldeployment.gpustack.ai/render-version. The explicit spec hash
includes the resolved KV cache binding, so a replica carrying the current spec still differs from
the desired hash once that connector stops resolving — the state behind RolloutHeldByCache.
| Value | Reason | Meaning |
|---|---|---|
True |
UpToDate |
every replica matches the explicit spec hash and meets the minimum render version; when any replica runs on an older compatible render-version, the message reports how many, the current render version, and modeldeployment.gpustack.ai/min-render-version |
False |
RolloutInProgress |
replicas differ from the render and turn over one per role per pass; this pass deleted at most one replica per role, and the pass that finds it gone creates the replacement |
False |
ReplacementInProgress |
every surviving replica matches the render but the declared count is short — what removed them is not this condition’s to say, and a preemption reports itself on the quota condition; the pass creates each replacement once the departed replica’s ordinal reads empty |
False |
RolloutHeldByCache |
replicas that differed from the render were left in place: no connection resolved this pass, and recreating them on that alone would rebuild every replica whenever the store blinks |
False |
RolloutHeldByWeights |
replicas that differed from the render were left in place: the model weights their replacements need are blocked, so deleting them would only remove serving replicas; WeightsReady says what blocks them |
Unknown |
RolloutNotObserved |
the pass accounted for no replica at all, so it established nothing either way |
A pass answers only for the replicas it read or created from the render it just performed; one that
could account for none reports Unknown, rather than a zero indistinguishable from “all up to
date”. This happens in ordinary operation — a teardown, and the pass between a rollout’s delete and
the create that answers it, both write status this way.
ReplacementInProgress and RolloutInProgress answer opposite questions — whether an edit landed,
and why capacity moved when nothing was edited. While a replacement waits, watch the departed Pod:
the replacement is created once no Pod for that ordinal reads on the API server.
The answer is not left standing between passes. After a steady deployment it would keep reporting
Truewhile every replica is on its way out, because a pass that finds every replica departing accounts for none of them.
RolloutHeldByCache is the condition to read when an edit appears to have no effect; it is the only
condition on the object that names a held rollout.
The state does not imply an edit is waiting. During an outage the render carries no connector, so every attached replica differs from it whether or not anyone changed the deployment, and an ordinary store blink puts every deployment on the pool into this state. The message names what would be delayed, never that something is.
Not every change waits: a
replicaschange proceeds during an outage — the ordinals it adds do not exist yet and are created without a connector, and the ordinals it sheds leave on the ordinary path. An edit that changes a running replica’s renderedPodis what waits. Scaling out does not release it: replicas added during the outage turn over once more when the store returns, and the running replicas still wait. The store’s own recovery bounds the wait.The stall is deliberate: the alternative — recreating every replica whose render lost its connector — deletes every replica of every deployment on a pool for a few seconds of store unavailability. Measured, a withheld edit lands within seconds of the store returning.
RoleKindsReady
Whether every role kind the deployment declares has at least one ready
replica. It is deliberately not replica completeness: “every role has all the replicas it asked
for” is what phase answers, by summing every role’s counts before judging.
| Value | Reason | Meaning |
|---|---|---|
True |
AllKindsReady |
every kind present has at least one ready replica |
False |
KindsNotReady |
at least one kind has none; the message names the kinds |
Unknown |
NoRoleStatuses |
the pass accounted for no role at all, so there is nothing to judge |
A deployment whose roles all share one kind (the configuration used when no role names a kind, since it
defaults to Server) reports True as soon as any replica is ready. That is not a blind spot. The
degradation is real and phase reports it as Degraded; this condition answers a different question
and is silent on that configuration by design.
On an encode, prefill and decode deployment, RoleKindsReady is True only when Encode,
Prefill and Decode each have at least one ready replica.
A role that took over its command line counts toward its kind like any other. Such a role gets no
readiness probe, so its Ready says its containers started rather than that anything answered on the
serving path. status.roles[].unmanaged is published per role for a reader who needs the stronger
reading.
Read this condition alongside phase: it reports readiness by role kind, while phase reports
replica completeness. Neither guarantees that an inference request succeeds; that also depends on
the engine and Service connectivity.
KVEventsPublishing
Whether every role that produces cache blocks has publisher configuration
in its rendered Pods. It reports configuration, not live traffic: a configured publisher that later
crashes remains True. Encode holds no KV cache and produces no cache blocks, so it is excluded.
| Value | Reason | Meaning |
|---|---|---|
True |
Publishing |
every producing role is configured to publish |
True |
NotApplicable |
an unrouted deployment declares only Server roles |
False |
PublisherDisabled |
a producing role’s rendered Pods do not enable publishing |
False |
NoRouter |
a prefill/decode pair has no router to consume events |
Unknown |
RoleUnmanaged |
a producing role replaced its command line, so the operator cannot inspect what it does |
RouterReady
Whether the managed router rendered and has a ready replica. A render refusal is reported here rather than returned as an error, so the rest of the status (phase, the other conditions, the role counts) keeps computing with the cause named.
| Value | Reason | Meaning |
|---|---|---|
True |
Ready |
the router’s Deployment has ready replicas |
True |
NotApplicable |
the deployment declares no spec.router |
False |
RenderFailed |
the router’s object set could not be rendered; the message carries the refusal |
False |
NoReadyReplicas |
the router’s Deployment exists but no replica is ready |
Unknown |
NotDeployed |
the router’s Deployment has not been created yet |
EndpointEligibility
Whether the deployment’s endpoints have been qualified at all. It
answers for the reading, not for the endpoints: until endpoint qualification exists it is
Unknown/NotObserved, because a False would say the operator disqualified endpoints it
saw, and no pass that reports this has seen any.
| Value | Reason | Meaning |
|---|---|---|
Unknown |
NotObserved |
no endpoint qualification has been observed for any role yet; the per-role endpoints.eligible counts are null, not zero |
ElasticResize
Whether an elastic scale stands blocked or terminally refused. It is
written only for a deployment with an elasticEp role; Elastic EP
owns the
resize lifecycle it reports on.
| Value | Reason | Meaning |
|---|---|---|
True |
NoRefusal |
no elastic scale command stands refused, whether none exists, one is in flight, or one has just completed |
False |
ScaleRefused |
the engine’s definite refusals spent the retry bound and the operation was abandoned; the message carries the engine’s own last refusal |
Unknown |
WorldUnprovable |
the operation record is gone and the live width disagrees with the spec or cannot be named, so no corrective scale can start; the message carries the desired width, the observed width and the proof that failed |
A refusal still inside its retry bound stays True: it is not terminal yet. WorldUnprovable
levels back to True on its own once a later pass proves the live world again.
Scaler annotations
Two annotations on the ModelDeployment — modeldeployment.gpustack.ai/scaler-binding and
modeldeployment.gpustack.ai/scaler-decision — record which ModelDeploymentScaler owns the
replica count and which decision last changed it. They sit in metadata.annotations, not in
status, because the single-writer fence is enforced at admission and a status field cannot
refuse a write.
Binding fence owns the payload of both annotations, the admission rules for bound and unbound deployments, and the escape hatch when a scaler is gone; GitOps sync covers how a GitOps agent ignores the fields the scaler owns.
The scaler’s own status reports what it observed, what it recommends and what blocks it;
Scaler status
owns that contract.
Group status
status.roles[].groups[] describes each serving group the operator supervises. A group is the set
of Pods that form one logical serving replica of a role, and its identity is the role, the replica
ordinal and the current sorted member UID set. A replaced member is a different set, so a previous
qualification does not apply to the new one.
| Field | Meaning |
|---|---|
role |
the role the group belongs to |
ordinal |
the replica slot the group occupies within its role |
generation |
the sorted member UID set the answers below were taken against |
state |
the group’s eligibility state |
reason |
the fact that holds the group, or that it qualifies |
members |
the group’s current members, ordered by seat |
lastQualificationTime |
when the group last entered Eligible for this generation |
serving |
the Router’s answer for this group |
servingReason |
why that answer is what it is, when it is not a confirmation |
Each member entry carries the member Pod’s name and uid, the seat it claims, and its ready
value: True, False or Unknown. A member nobody has reported on is Unknown, not False.
The group entry answers what the role’s counts cannot. endpoints.eligible counts endpoints and
endpoints.serving counts a Router’s confirmation; the group entry names the members, the
generation those answers were taken against, and the fact that holds the group.
The eight states are the operator’s own.
| State | Meaning |
|---|---|
Eligible |
every leg verified and nothing else holds the group |
Withdrawn |
a definite fault withdrew the group |
AwaitingMembers |
the member set is not established |
AwaitingReady |
the members are not all Ready=True |
AwaitingProbe |
the forward path could not be observed |
AwaitingObservation |
the group verified every leg and waits on a valid Router observation |
Unsupported |
no observation exists for this shape |
Unknown |
the pass has not evaluated the group yet |
AwaitingObservation names the one hold the qualification cannot name. A group that passed every
leg and waits on a valid Router observation is AwaitingObservation, while a group that passed every
leg and is retained across an observation loss is Eligible, because the observation is not what holds
it.
The retention is proved by the fresh membership, and that membership has to be complete, unchanged, safe and current. It is read uncached when the collection runs, not taken from the reconciled list, and every member of it has to have reported ready. A member that disappears, is replaced or becomes unready invalidates the retention and the restoration. A later failed read cannot erase a change the collection already proved.
The reason names one fact and never two. When a leg of the whole-group predicate holds the group, the reason names that leg; when the Router’s observation holds it, the reason names the observation. A member fault and a lost Router view are told apart by the reason as well as by the state, and the Router’s own reason never takes the group’s place.
The qualification time is the transition, not the latest pass. A group that stays Eligible keeps
the instant it earned, and a group held in the same generation keeps it too, so an observation loss
does not erase it. A group that leaves Eligible and comes back records the new instant. A new
generation inherits no timestamp: the membership it names was never qualified.
Elastic EP collective
An Elastic EP role publishes one group at ordinal zero, and that entry replaces the replica-derived
groups a role of several Pods would otherwise publish. Its members are the role’s current GPU
workers, the API/DP master included and the ancillary Ray head excluded. A worker on its way out is
not a member, and a collective with no workers publishes no group at all. Each member’s seat records
its admission ordinal, not its runtime engine rank.
The collective’s state is Unknown, and the master’s own qualification does not change it. The
reason says that the whole elastic collective has not been evaluated as one group. The serving answer
carries no count: Unknown with a Router, and NotConfigured without one. There is no qualification
time, because an instant belongs to a transition into Eligible and the collective is never eligible.
A count and a group never describe the same membership on this shape. The role’s own counts are its replica counts, computed from the master the elastic path reads, because the master is the replica the Router observes. The group is computed from the whole worker set, because the collective is what a reader asks about when a rank fails.