After looking at hundreds of Kubestead rollout configs shipped by SRE and platform teams, a few patterns emerge clearly. Not as taxonomy for its own sake, but because the pattern you choose determines which failure modes are visible before users report them — and which ones stay invisible until 3 AM.
We're not saying any single pattern is universally correct. A canary strategy that works well for a stateless API gateway can fail badly on a service with a stateful session cache or a database migration in flight. What follows is a field guide to what actually ships — and where each pattern breaks down.
Pattern 1: The Flat Percentage Canary
The most common pattern. Traffic is split at a fixed percentage — typically 5% or 10% — and held there for a fixed evaluation window before promotion. The simplicity is the point: one threshold, one clock, one decision.
In Kubestead's rollout spec this looks like a single canaryStep with a setWeight directive followed by a pause block that accepts either a duration or a metric gate:
canarySteps:
- setWeight: 10
- pause:
duration: 10m
- setWeight: 100
This pattern works well for services where a 10-minute window gives the error rate enough time to manifest. It fails when the failure mode is latency-sensitive — a P99 regression that only appears under the specific load pattern of peak traffic may not be visible in a 10-minute window on Tuesday afternoon. Teams who get burned by this start adding a second step at 30% to extend observation time before full rollout.
Pattern 2: The Progressive Step Canary
Rather than jumping from 10% to 100%, traffic increases through a programmed staircase: 1% → 5% → 20% → 50% → 100%, with an analysis gate at each step. Each step validates error rate and latency before advancing. A single gate failure triggers immediate rollback from wherever in the staircase the rollout currently sits.
This is more expensive in time — a full staircase with 5-minute analysis windows at each step takes 20-25 minutes minimum. The tradeoff is much earlier detection at low blast radius. A regression that produces a 0.8% error rate becomes detectable at 1% traffic with enough baseline samples. At 10% flat it might look like noise for several minutes.
One growing e-commerce platform running around 80 microservices standardized on a 1-5-20-100 staircase for all customer-facing services after a flat-percentage canary missed a database connection pool exhaustion bug at production load. The bug only manifested above 15% traffic, and by then 15% of checkout attempts were failing.
Pattern 3: The Metric-Gated Canary Without Fixed Duration
Instead of advancing on a timer, the rollout advances only when the canary metrics cross a success threshold. The evaluation window is open-ended: keep sampling until you have enough data to be confident, or until a failure threshold is breached.
Kubestead implements this via the analysisTemplate reference in each canary step, rather than a pause.duration. The template specifies a PromQL query, a success condition, a failure condition, and an interval:
analysisTemplate:
name: error-rate-gate
spec:
metrics:
- name: http-error-rate
interval: 60s
successCondition: result[0] < 0.005
failureCondition: result[0] > 0.02
provider:
prometheus:
query: |
sum(rate(http_requests_total{status=~"5.."}[2m]))
/ sum(rate(http_requests_total[2m]))
The benefit is that a clean deploy promotes quickly without waiting for a fixed clock. A regression fails fast regardless of how long the window is. The risk is that low-traffic services may never accumulate enough samples to reach the success threshold, leaving rollouts perpetually paused. Teams using this pattern need minimum-sample-count guards or a fallback duration ceiling.
Pattern 4: The Header-Routed Internal Canary
Not all canary traffic should come from random production users. Some regressions — security behavior changes, internal API contract changes, admin functionality — should be validated against internal traffic first. This pattern uses a service mesh header rule to route requests with a specific header (e.g., X-Canary: true) to canary pods, while all other traffic continues to stable.
This is available in Istio via VirtualService header matching rules, and Kubestead can layer its own analysis on top of the header-filtered traffic subset. The pattern is particularly useful during a database schema expand phase — you want internal tooling traffic (where you control retries and can tolerate a brief error) hitting the new schema before customer-facing traffic does.
The limitation is real: header-routed canaries don't validate under real traffic distribution. If the regression only appears at random-user concurrency patterns, a header-routed canary won't catch it. We're not saying header routing is a substitute for percentage-based canary analysis — it's a precursor step, not a replacement.
Pattern 5: The Shadow Canary (Request Mirroring)
Traffic is mirrored asynchronously to the canary deployment — the stable version responds to the user; the canary version processes a copy of the request in parallel and discards the response. The canary's error rate, latency, and side effects (database writes, downstream service calls) are monitored, but canary failures are invisible to end users.
Istio supports this via HTTPRoute.mirror. It's the highest-safety option for validating a new version under real load before it touches a single user. The operational cost is significant: canary instances must handle full production traffic rate (doubled infrastructure cost), side effects must be idempotent or carefully isolated, and any mutation the canary makes to shared state must be safe to duplicate.
Most teams use shadow mode for 30-60 minutes before switching to a percentage-based canary. It's a validation warm-up, not a rollout strategy by itself. Once shadow metrics are clean, confidence is high enough to start the actual traffic promotion.
Where Teams Get the Pattern Wrong
The most common mistake is applying the same canary pattern to every service in the mesh regardless of service characteristics. Stateless read-heavy services and stateful services with session affinity have completely different blast radii. A flat 10% canary on a service that holds in-memory session state means 10% of users are stuck mid-session — and sessions don't recover cleanly on rollback if state was mutated.
The second mistake is treating canary success thresholds as absolute rather than service-specific. An error rate threshold of 0.5% may be generous for a payment processing service and impossibly tight for a background job scheduler that regularly encounters transient external API errors. Metric thresholds need to be calibrated against the service's baseline, not copied from a template.
Pattern selection is also not static. A service that starts as a stateless microservice often acquires state over time — a cache, a write path, eventually a database dependency. The canary strategy that worked at launch may be undersized for what the service does today.
A Note on Rollback Speed Across Patterns
One factor that doesn't get enough attention: rollback time varies significantly by pattern. A flat 10% canary with a 10-minute window can require up to 10 minutes of error exposure before triggering rollback. A metric-gated canary with a 60-second polling interval and a failure threshold catches the same regression in under 2 minutes.
Kubestead's rollback mechanism is pod count reduction — it scales the canary ReplicaSet to zero and re-routes traffic to stable. On a cluster with adequate resource headroom, this takes 15-30 seconds from trigger to zero canary traffic. The bottleneck is usually analysis detection latency, not the mechanical rollback itself. Optimizing your analysis polling interval matters more than most teams realize.