Canary progression Unreleased: coming in v1.3

Canary steps move a share of each component's Instances to a new revision, then advance at once, on a timer, on a promote or on Prometheus analysis.

A canary moves a new revision onto a share of a component's Instances at a time, and waits at a gate after each step. The gate opens at once, after a timed pause, when you promote the step, or when Prometheus analysis passes. A rollback, from you or from a failed analysis, returns every Instance to the stable revision. You declare a canary in the canary block of a rollout group in an InferenceService. To act on a running canary, see Promote or roll back a canary.

A canary group's components must run on OMENative. Set spec.deploymentMode: OMENative, or ome.io/deploymentMode: OMENative in the component's annotations. A multi-node component needs this too, even though it runs on OMENative by default.

Example

The InferenceService chat rolls its engine out in three steps:

chat.yaml
apiVersion: ome.io/v1beta1
kind: InferenceService
metadata:
  name: chat
  namespace: prod
spec:
  deploymentMode: OMENative
  model:
    name: llama-3-2-1b-instruct
  engine:
    minReplicas: 4
    maxReplicas: 4
  rollout:
    groups:
      - components: [engine]
        canary:
          scaleDownDelaySeconds: 60
          readyTimeout: 20m
          steps:
            - capacity: "25%"
              traffic: 10
              pause:
                duration: 10m
            - capacity: "50%"
              traffic: 50
              pause: {}
            - capacity: "100%"
              traffic: 100
Step capacity Instances on the new revision traffic Gate
1 "25%" 1 of 4 10 Timed: the step advances 10 minutes after its new Instance is ready.
2 "50%" 2 of 4 50 Manual: the step waits for the alpha kubectl ome rollout promote.
3 "100%" 4 of 4 100 Immediate: the canary completes 60 seconds after all 4 new Instances are ready.

Each step first waits for its new Instances to be ready, and the canary fails if a step waits longer than 20 minutes. kubectl ome rollout status shows the canary's step, gate and phase.

A canary runs when a change, such as a new image, gives the engine a new revision while an earlier one serves. So the first revision of chat rolls out without a canary, and adding the group starts nothing until the next change. The canary keeps the plan it started with: an edit to the canary block waits for the next run. The alpha kubectl ome rollout repin applies it to the running canary, as Repin a drifted rollout plan shows.

Canary prefill and decode together

When an InferenceService declares both an engine and a decoder, a canary group that names one must name both, and the two roll out as one canary. The InferenceService chat-pd uses the model and the prefill-decode runtime that Serve a prefill-decode model creates, on GPU nodes with RDMA NICs:

chat-pd.yaml
apiVersion: ome.io/v1beta1
kind: InferenceService
metadata:
  name: chat-pd
  namespace: prod
spec:
  deploymentMode: OMENative
  model:
    name: llama-3-2-1b-instruct
  runtime:
    name: srt-llama-3-2-1b-instruct-pd
  engine:
    minReplicas: 4
    maxReplicas: 4
  decoder:
    minReplicas: 2
    maxReplicas: 2
  router:
    minReplicas: 1
    maxReplicas: 1
  rollout:
    groups:
      - components: [engine, decoder]
        canary:
          steps:
            - capacity: "50%"
              traffic: 50
              pause: {}
            - capacity: "100%"
              traffic: 100

Each step's capacity applies to each component. Step 1 moves 2 of the 4 engine Instances and 1 of the 2 decoder Instances to the new revision. It waits for all three to be ready, then for a promote. The router is in no group, so it rolls out on its own and balances requests across both revisions.

Fields

Field Default What it sets
steps Required The steps, in order.
steps[].capacity Required How many of each component's Instances run the new revision: a percentage such as "25%", or an integer. The last step's percentage must be "100%". See New-revision capacity.
steps[].traffic Required The new revision's weight in status, from 0 to 100. It can't fall from one step to the next, must be 100 at the last step, and above 0 needs a capacity above zero.
steps[].pause None A duration makes a timed gate, and pause: {} a manual one. See How a step advances.
steps[].analysis None Prometheus checks that gate the step. See Canary metric analysis.
readyTimeout The operator's, 15m with the chart How long a step can wait for its new Instances. Above zero. See readyTimeout.
scaleDownDelaySeconds None The canary completes at least this many seconds after the last step's new Instances are ready. Until then, a rollback still works and a last-step analysis keeps sampling. It isn't a drain window: a "100%" last step has already replaced the old Instances, and a rollback doesn't wait for it.
prometheus The operator's default source Where analysis queries go: providerRef or serverAddress. See Canary metric analysis.

New-revision capacity

capacity takes one of these forms:

Form Example Instances on the new revision
A quoted whole-number percentage, from "0%" to "100%" "25%" That share of the desired replicas, rounded up: "25%" of 3 is 1.
An unquoted integer 2 That many, up to the desired replicas.
A quoted number without % "2" None: admission rejects it.

A component's desired replicas are its minReplicas. Set minReplicas on each component in the group, in the InferenceService itself. Without it, a step can wait for Instances that never start until its ready timeout, or start its gate before any new Instance is ready. Set replica defaults explains why defaults from the runtime or the chart don't count.

A step replaces Instances; it doesn't add any. The rest of the desired replicas stay on the previous revision, and Instances that an autoscaler adds above them run the new revision. Each component works out its own count, as the prefill-decode example shows. For a multi-node component, capacity counts whole Instances, each a leader and its workers.

How a step advances

A step starts by moving its capacity to the new revision, and waits in Pending until every component in the group has that many new Instances ready. At that moment, OME records the step's traffic, and the step's gate decides when the next step starts:

Gate The step sets The step advances when
Immediate Only capacity and traffic Its new Instances are ready.
Timed pause with a duration The duration has passed since its new Instances were ready.
Manual pause: {} You promote it with kubectl ome rollout promote.
Analysis analysis Its metric checks pass, after the optional initialDelay and no sooner than the step's pause.duration. See Canary metric analysis.

GATE in kubectl ome rollout status shows these names. If a step's ready Instances drop below its count, the step goes back to Pending, and its clock starts again once its new Instances are ready. The one capacity loss a step doesn't wait out is a crash-looping canary pod: it parks the canary at Failed before OME checks any gate. At the last step, the gate holds the canary in Promoting until it passes.

Phases

The canary's phase is in status.components.<component>.rolloutPhase of the group's primary component: the router when the group includes it, otherwise the engine. PHASE in kubectl ome rollout status shows it:

Phase Meaning
Pending The step waits for its new Instances to be ready.
Canarying The step's new Instances are ready, and OME checks its gate.
Paused The canary waits at a gate before the last step, or holds after a repin.
Promoting The last step's new Instances are ready, and the canary waits for its gate and for scaleDownDelaySeconds.
Stable The canary completed.
Failed A step ran past its ready timeout, a canary pod kept dying after it served (see Crash-looping canary pods), or an analysis rollback found a stable revision with no live Instance.
RollingBack A rollback is moving Instances back to the stable revision.
RolledBack Every Instance is back on the stable revision.

While a canary runs

When The canary
A change gives the group a newer revision Starts again at step 1 with the newest revision, and keeps the same stable revision. A Failed canary starts again too, when the newest revision is neither its own nor the stable one: see readyTimeout.
You pause the rollout Holds its step, but its clocks keep running, so it can advance or fail as soon as you resume. See Pause and resume a rollout.
You run the alpha kubectl ome rollout rollback, or its analysis rolls it back Records 0% for the new revision at once, and moves every Instance back to the stable revision. The component stays on the stable revision until a change produces a revision other than those two. See Roll back the canary.
A rollback finds a component's stable revision with no live Instance From the analysis, the rollback doesn't start: the canary parks at Failed where it stands, records the reason StableRevisionMissing, and emits the Warning event CanaryStableRevisionEmpty. From you, it proceeds, with the same Warning event. See When the stable revision runs no Instance.
A step runs past its ready timeout Goes to Failed. A stalled analysis with onInconclusive: RollbackOnStall rolls it back instead.
A canary pod keeps dying after it served Parks at Failed where it stands, returns the step's recorded traffic to the stable revision, and emits the Warning event CanaryPodCrashed. See Crash-looping canary pods.

Traffic weight in status

When a step's new Instances are ready, OME records the step's traffic as the new revision's weight and the rest as the stable revision's. It writes both in status.components.<component>.traffic of the primary component:

Field Value
revisionName The name of the revision's own Service, <isvc>-<component>-rev-<hash>.
percent The revision's weight. A revision with a weight of 0 is left out.
tag canary for the new revision, stable for the previous one.
latestRevision true for the new revision.
pairingProtocol The revision's spec.rollout.pairingProtocol, when it has one.

kubectl ome rollout status shows the weight in TRAFFIC, as target% -> observed%, and explain shows the canary's revisions: see kubectl ome rollout. At step 1 of the example, status records 10% for the new revision, but it has a quarter of the ready Instances and gets about a quarter of the requests.

readyTimeout

The ready timeout bounds how long a step waits in Pending for its new Instances, and how long an analysis step goes without a conclusive result. The first of these that's set applies:

Order Setting Notes
1 The ome.io/rollout-ready-timeout annotation on the InferenceService A duration, such as 30m. OME ignores zero or less, and admission warns (RolloutTimeoutTooShort) about a value under a minute.
2 readyTimeout in the group's canary Above zero.
3 defaultReadyTimeout in the rollout block of the inferenceservice-config ConfigMap 15m with the ome-resources chart (ome.controller.rollout.defaultReadyTimeout). The kustomize manifests don't set it.

With none set, a step never times out: it waits in Pending until its Instances are ready. A crash-looping canary pod still parks the canary at Failed, timeout or none. The timeout counts from when the step last entered Pending. For an analysis, it counts from the last conclusive sample, or from when the step's new Instances were ready if there's none yet.

When it expires, the phase is Failed. The canary keeps its Instances and its recorded traffic, the stable Instances keep serving, and promote refuses. Apply a fix that gives the group a revision other than the failed canary's and the stable one, and a fresh canary starts at step 1 toward it, with the same stable revision. A rollback also works on a failed canary: every Instance returns to the stable revision, as When a step never becomes ready shows. A crash-looping canary pod parks the canary the same way, except that the step's recorded traffic returns to the stable revision at once.

Crash-looping canary pods

A canary pod that keeps dying after its Instance served parks the canary where it stands. OME reads the crash before the step's capacity, its ready timeout and its gate, so the park happens at any step, with or without a ready timeout set:

  • The phase goes to Failed at the current step, and status.components.<component>.canary.failed.reason records CapacityTimeout, the same reason as a ready-timeout park. The event tells the two apart.
  • The step's recorded traffic returns to the stable revision: status records 0% for the new revision, where a ready-timeout park keeps the step's weight.
  • OME emits the Warning event CanaryPodCrashed on the InferenceService, naming the component, the pod, its termination detail and the step, counted from 0. In a group of several components, a crash in any member's canary pod parks the group's canary.

The canary keeps its Instances, and the stable Instances keep serving. Recovery is the same as for any Failed canary: a rollback returns every Instance to the stable revision, and a fix that gives the group a revision other than the parked canary's and the stable one starts a fresh canary at step 1.

OME reads a crash loop only from the pods of the new revision, and only from their runner container, ome-container: a stable pod's crash, or a sidecar or init container that restarts, parks nothing. The pod's Instance must have served first:

The runner The canary
Crashes before its Instance is first ready Waits in Pending, under the ready timeout. The crash park never fires.
Dies once after its Instance served Waits out the dip in capacity: the kubelet restarts the container in place, or the Instance restart policy rebuilds the Instance.
Dies a second time after its Instance served Parks at Failed, whether the kubelet has restarted the runner again or is backing off. Deaths count from when the canary started, across its steps.
Dies in the rebuilt pod, after a rebuild for a death during the canary Parks at Failed: the Instance's second death under one repair.

When a canary pod keeps crashing shows the event and the recovery.

What admission rejects

The webhook rejects a canary with a message that names the field and gives one of these reasons in parentheses:

Reason Cause
CanaryRequiresOMENative A component in the group doesn't declare OMENative. deploymentMode="" means it sets neither the field nor the annotation.
CanaryInvalid A step or readyTimeout breaks a rule in Fields, or a capacity isn't one of the forms in New-revision capacity.
CanaryInvalid The group names a component other than router, engine or decoder, or one the InferenceService doesn't declare, or sets maintainRatio.
CanaryInvalid The InferenceService has an engine and a decoder, and the group names one: a canary group naming either must name both, or for the decoder, must include its entrypoint Component "engine".
MultipleCanaryGroups Two canary groups cover the same unit: the router, or the engine and decoder.
GroupOrderingNotHonored A canary group sits beside other groups without spec.rollout.groupOrdering: Concurrent.
SoakNotHonored The canary group sets soak.
InvalidDuration The ome.io/rollout-ready-timeout annotation isn't a duration.
AnalysisInvalid An analysis breaks a rule in Canary metric analysis.

The API server itself rejects more than 20 steps, or a traffic outside 0 to 100. Rollout groups lists the rules for every group.