Rolling Update vs Blue-Green vs Canary on Kubernetes
7 min readBytePatterns
Rolling update vs blue-green vs canary: what a Deployment does natively, how the others are built with Services and weights, and what each costs to undo.
Rolling update vs blue-green vs canary in one line: a rolling update replaces old Pods with new ones a few at a time inside one Deployment, blue-green runs a complete second copy and moves all traffic to it in one switch, and canary sends a small, growing share of real traffic to the new version before everyone gets it. Only the first is built into Kubernetes: a Deployment's strategy is RollingUpdate (the default) or Recreate. Blue-green and canary are patterns you assemble from two Deployments plus a Service selector, replica ratios or weighted routes. Facts come from the Kubernetes and Gateway API documentation listed at the end, as of October 2026.
| Strategy | How Kubernetes does it | Undo |
|---|---|---|
| Rolling update | native: strategy: RollingUpdate, bounded by maxSurge and maxUnavailable | kubectl rollout undo: another rolling update |
| Blue-green | two Deployments; the Service selector points at one | point the selector back: instant while blue still runs |
| Canary | two Deployments; replica ratio, or weighted backendRefs in an HTTPRoute | set the canary weight to 0 |
The problem it solves
Every release is a bet that the new version works. Readiness probes catch a version that cannot start or cannot serve. They do not catch one that serves wrong answers, leaks memory over an hour or breaks one customer's workflow. The three strategies differ in how much traffic you put at risk while you find out, how much spare capacity you pay for, and how fast you can take the bet back.
The intuition
Rolling update: one Deployment, gradual replacement. Change the Pod template and the Deployment creates a new ReplicaSet, scales it up and scales the old one down in steps, never exceeding maxSurge extra Pods and never dropping more than maxUnavailable below the desired count. A new Pod takes traffic once it is ready. Both versions serve side by side during the rollout, in proportion to their ready Pods, and kubectl rollout undo goes back to the previous ReplicaSet, which the Deployment keeps (ten old ReplicaSets by default). The undo is itself a rolling update, so it takes about as long as the rollout did. Recreate, the other native strategy, kills every old Pod before creating new ones: no mixed versions, but a gap in service.
Blue-green: two full copies, one switch. Blue is the live Deployment, labelled version: blue; green is a second Deployment with the new image and version: green. The Service selects app: api, version: blue. You bring green up to full size, test it through a separate Service, then change the selector to version: green: all new connections go to green at once. Rollback is changing the selector back, which is instant only while blue is still running, so you pay for double capacity until you are confident enough to scale blue down.
Canary: a little real traffic first. The canary is a small second Deployment of the new version. The Kubernetes documentation describes the basic form: both Deployments share the labels the Service selects, differ in a track: stable or track: canary label, and the replica counts set the traffic ratio, three to one in its example. That ties the percentage to Pod counts, so 1% needs 99 stable Pods. With Gateway API, an HTTPRoute lists both Services as backendRefs with weights, so the share is independent of Pod counts: 5%, then 25%, then 50%, watching error rates and latency at each step, then promote by updating the stable Deployment. Undo is setting the canary weight to 0.
What each needs from probes. All three rely on readiness: a rolling update counts only ready Pods as available, a Service only sends traffic to ready endpoints, and a canary Pod that is not ready gets nothing. None of them can rely on readiness alone to judge a release; blue-green and canary add a decision step, a test pass or a metrics check, that readiness cannot make.
When to use which
- Most stateless services: rolling update. It is built in, needs at most
maxSurgeextra Pods, and a bad image that never becomes ready stalls safely. - Releases that must flip atomically, or must be tested at full size before users see them: blue-green, if you can afford double capacity for the switch window and both versions can share the database.
- High-traffic services where a subtle bug is costly: canary with weighted routing and automated checks on error rate and latency.
- Versions that must not run side by side, for example an incompatible change to a single-writer store:
Recreate, accepting the downtime, or a blue-green switch.
Watch it run
The lesson's animation starts with scheduling, a Pending Pod filtered and scored onto a node, then runs a native rolling update: four v1 Pods with maxSurge 1 and maxUnavailable 0. Changing the image creates a v2 ReplicaSet, one surge Pod starts, counts as available only after it passes readiness, and only then does v1 shrink by one, until every Pod runs v2 and the v1 ReplicaSet waits at zero for a rollback. A v3 with a bad image never becomes ready, the rollout stalls with v2 still serving, and kubectl rollout undo removes it. That is the rolling-update column below; blue-green and canary are built from the same Deployments and Services.
Scheduling & Rolling Updates
Step 1 of 13
A new Pod requests 500m of CPU. Until the scheduler binds it to a node, it is Pending.
The same interactive animation as the lesson — step through it with the controls.
The code
The non-native two, sketched as manifests. Blue-green is a selector change; canary is two weighted backends in one rule:
# illustrative: blue-green. Deployments api-blue and api-green differ only in image and label
apiVersion: v1
kind: Service
metadata: { name: api }
spec:
selector: { app: api, version: blue } # the whole switch: change blue to green
ports: [{ port: 80, targetPort: 8000 }]
# kubectl patch service api -p '{"spec":{"selector":{"app":"api","version":"green"}}}'
---
# illustrative: canary with Gateway API. Services api-stable and api-canary
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: { name: api }
spec:
parentRefs: [{ name: public }]
rules:
- backendRefs:
- { name: api-stable, port: 80, weight: 95 }
- { name: api-canary, port: 80, weight: 5 } # then 25, 50, promote
A toy model of four replicas moving to v2, not a controller. Each row is the share of requests on v2 and the number of Pods running at the end of a step. The rolling update with maxSurge 1 and maxUnavailable 0 moves a quarter at a time with one extra Pod; blue-green holds eight Pods and flips from 0% to 100%; the canary walks its weights with a canary Deployment sized to its share:
import math
N = 4 # replicas of v1 serving production
# (share of requests on v2, Pods running) at the end of each step
def rolling(): # maxSurge 1, maxUnavailable 0: one v2 Pod per step, then one v1 goes
return [(k / N, N + (k < N)) for k in range(N + 1)]
def blue_green(): # a full green copy starts, is tested privately, then one selector flip
return [(0, 2 * N), (0, 2 * N), (1, 2 * N), (1, 2 * N), (1, N)]
def canary(weights=(0, 0.05, 0.25, 0.5, 1)): # route weights; canary Pods sized to weight
return [(w, N if w in (0, 1) else N + max(1, math.ceil(N * w))) for w in weights]
PLANS = {"rolling": rolling(), "blue-green": blue_green(), "canary": canary()}
print("step " + "".join(f"{name:>14}" for name in PLANS))
for t in range(5):
print(f"{t:>4} " + "".join(f"{f'{p[t][0]:.0%} {p[t][1]}p':>14}" for p in PLANS.values()))
print("peak Pods:", {name: max(pods for _, pods in p) for name, p in PLANS.items()})
# step rolling blue-green canary
# 0 0% 5p 0% 8p 0% 4p
# 1 25% 5p 0% 8p 5% 5p
# 2 50% 5p 100% 8p 25% 5p
# 3 75% 5p 100% 8p 50% 6p
# 4 100% 4p 100% 4p 100% 4p
# peak Pods: {'rolling': 5, 'blue-green': 8, 'canary': 6}
Now ship a v2 that passes readiness but fails every request, at 1,000 requests per step, with an alert after 300 failures. The rolling update leaks errors gradually but undoes slowly, Pod by Pod; blue-green fails everything at once but undoes instantly; the canary caps the damage at its small share:
RPS, ALERT = 1000, 300 # requests per step; alert after 300 failed requests
def bad_release(plan, undo_steps):
"""v2 passes readiness but fails every request. Count failures until it is gone."""
failed = 0
for t, (share, _) in enumerate(plan):
failed += int(share * RPS)
if failed >= ALERT:
# undo: rolling must roll v1 Pods back one step at a time; others flip at once
for k in range(1, undo_steps(share) + 1):
failed += int(max(share - k / N, 0) * RPS)
return t, failed, undo_steps(share)
return None
UNDO = {"rolling": lambda share: round(share * N), # one step per v2 Pod to replace
"blue-green": lambda share: 0, # selector back to blue
"canary": lambda share: 0} # weight back to 0
for name, plan in PLANS.items():
t, failed, undo = bad_release(plan, UNDO[name])
print(f"{name:>10}: alert at step {t}, {failed} failed requests, undo in {undo} steps")
# rolling: alert at step 2, 1000 failed requests, undo in 2 steps
# blue-green: alert at step 2, 1000 failed requests, undo in 0 steps
# canary: alert at step 2, 300 failed requests, undo in 0 steps
The numbers are a toy's, but the shape is general: exposure is the share of traffic on the new version multiplied by the time to notice, plus the time to undo.
The complexity
- Extra capacity: rolling,
maxSurgePods (one here); blue-green, a full second copy for the whole switch window; canary, the canary Pods only. - Time to full rollout: rolling, one round per batch of Pods, each waiting for readiness; blue-green, the time to start green plus testing, then one flip; canary, the sum of its observation windows, usually the slowest on purpose.
- Time to undo: rolling, roughly the rollout time again; blue-green and canary, one change to a selector or a weight, as long as the old version is still running.
Where it goes wrong
- Treating readiness as release validation. Readiness proves a Pod can take traffic, not that its answers are right. Blue-green tests and canary metrics exist for that gap.
- Scaling blue down too early. Once blue is gone, the "instant" rollback is a fresh deploy of the old version.
- Canary by replica ratio at low traffic. One canary Pod among three stable ones is 25% of traffic, not a gentle start; use weighted routing for small shares.
- Incompatible schema changes. During a rolling update or canary both versions run at once against the same database, and blue-green switches only the stateless tier. Make migrations backward compatible, expand first and contract later.
- Sticky clients. Long-lived connections stay on the old Pods after a selector flip or weight change; the switch applies to new connections, so drain deliberately.
When it shows up in interviews
As part of "design the deployment pipeline" or straight as "how would you release safely?". The interviewer wants the native rolling update with sensible maxSurge and maxUnavailable, then when you would pay for blue-green or canary, and how you would roll back. The knobs of the native strategy are in Kubernetes rolling updates: maxSurge and maxUnavailable, the probe that gates all three is in liveness vs readiness vs startup probe, and the rollout itself is animated in the Scheduling and Rolling Updates lesson.
How to say it in an interview
"Kubernetes gives me rolling updates natively: a new ReplicaSet scaled up as the old one scales down, bounded by maxSurge and maxUnavailable, with readiness deciding when a new Pod counts, and rollout undo to go back, which is another rolling update. Blue-green is two Deployments and a Service selector flip: atomic, instant rollback, double capacity while blue stays up. Canary is a second small Deployment that gets a weighted share of traffic, ideally through an HTTPRoute, grown while I watch error rates. Readiness gates all three, but only blue-green tests or canary metrics catch a version that is up and wrong."
Frequently asked questions
Does Kubernetes support blue-green deployment natively?
Not as a strategy. A Deployment offers only RollingUpdate and Recreate. Blue-green is built from two Deployments with different version labels and a Service whose selector you switch from one to the other. Progressive delivery controllers automate the switch and the checks, but underneath it is the same selector or routing change.
What is the difference between a canary and a rolling update?
A rolling update moves all traffic to the new version as fast as Pods become ready, with no pause to judge it. A canary deliberately holds the new version at a small share, by replica ratio or route weights, while you check its metrics, and only then promotes it. Both run two versions side by side.
Which deployment strategy has the fastest rollback?
Blue-green and canary: rollback is one change, a Service selector back to blue or a canary weight back to 0, as long as the old version is still running. A rolling update's kubectl rollout undo is a new rolling update to the previous ReplicaSet, so it takes about as long as the original rollout.
Sources
- Deployments — Kubernetes Documentation
- Managing Workloads: canary deployments — Kubernetes Documentation
- Service — Kubernetes Documentation
- Traffic Splitting — Gateway API (Kubernetes SIG Network)