Kubernetes Using Kubernetes Deployments for Stateless Workloads Created: 25 Jul 2026 Updated: 25 Jul 2026

Kubernetes Canary Deployments

A Canary deployment is a release strategy where you roll out a new version to a small subset of users first. If the new version is healthy, you gradually increase its share of traffic until it serves all requests. If something goes wrong, you scale the canary back to zero — only a small fraction of users were ever affected.

The name comes from the "canary in a coal mine" — a small, early warning signal. In Kubernetes, the canary is a small Deployment running the new version alongside the larger stable Deployment. Because both Deployments share the same app label, a single Service load-balances traffic across all Pods — the ratio of stable-to-canary Pods determines the traffic split.

Compared to Blue/Green (instant switch) and Rolling Updates (automatic gradual replacement), Canary gives you the most control over how much traffic the new version receives and how fast you ramp it up. This makes it ideal for high-traffic services where even a brief spike in errors is unacceptable.

Core Concepts

Step 1: How Canary Works — The Big Picture

The strategy uses two Deployments and one Service:

  1. Stable Deployment — Runs the current production version (e.g., v1.0) with a higher replica count (e.g., 4 replicas).
  2. Canary Deployment — Runs the new version (e.g., v2.0) with a lower replica count (e.g., 1 replica).
  3. Service — Selects Pods based on the shared app label only. Because both Deployments have app: booking-api, the Service sends traffic to all 5 Pods (4 stable + 1 canary).

With this setup, roughly 20% of traffic goes to the canary (1 out of 5 Pods). You control the traffic ratio by adjusting replica counts.

Step 2: Traffic Splitting by Replica Ratio

Kubernetes Services distribute traffic roughly evenly across all matching Pods. This means the traffic percentage is approximately:

canary_traffic% ≈ canary_replicas / (stable_replicas + canary_replicas) × 100

Common progression patterns:

PhaseStable ReplicasCanary ReplicasCanary Traffic %
Start41~20%
Increase32~40%
Majority23~60%
Almost done14~80%
Complete05100%

Note: this is a rough approximation. Kubernetes round-robin distribution is not perfectly even, but it is close enough for most canary use cases.

Step 3: The Label Strategy

The critical difference between Canary and Blue/Green is how the Service selects Pods:

  1. Blue/Green: The Service selects on app + version. Only one environment receives traffic at a time.
  2. Canary: The Service selects on app only. Both Deployments receive traffic simultaneously. An additional track label (track: stable or track: canary) helps you manage the Deployments with kubectl, but the Service does not use it.

Step 4: Monitoring the Canary

The point of a canary is to observe the new version under real traffic before giving it more. Key things to monitor:

  1. Error rate: Are 5xx responses increasing?
  2. Latency: Is the canary slower than the stable version?
  3. Pod restarts: Is the canary crashing?
  4. Resource usage: Is CPU or memory unexpectedly high?

Compare these metrics between stable and canary Pods. If the canary looks healthy after an observation window (e.g., 10–30 minutes), increase its replica count.

Step 5: When to Use Canary

ScenarioCanary?Reason
High-traffic service with strict SLOsYesGradual ramp-up limits blast radius.
Risky changes (new dependency, algorithm change)YesObserve real behaviour before committing fully.
Simple config change or minor patchProbably notRolling update is simpler and faster.
Need precise traffic control (e.g., exact 5%)Use Ingress-based canaryReplica ratio gives rough control; Ingress annotations give precise percentages.

Step 6: Canary vs Blue/Green vs Rolling Update

FeatureCanaryBlue/GreenRolling Update
Traffic splitGradual (you control the ratio)All-at-onceGradual (automatic)
RollbackScale canary to 0Patch Service selectorkubectl rollout undo
Resource overheadSmall (a few extra Pods)2x (full duplicate environment)Minimal (surge only)
User controlFull manual controlBinary (on/off)Automatic (Kubernetes-managed)
Blast radiusSmall (only canary %)All or nothingIncreases over time

Hands-On: Kubernetes Commands

Deploy the Stable Version

kubectl apply -f booking-api-stable-deployment.yaml

Create the Service

kubectl apply -f booking-api-service.yaml

Deploy the Canary

kubectl apply -f booking-api-canary-deployment.yaml

Check All Pods Across Both Deployments

kubectl get pods -l app=booking-api --show-labels

Check the Service Endpoints (Should Include Both Stable and Canary)

kubectl get endpoints booking-api-service

Scale Canary Up (Increase Traffic)

kubectl scale deployment booking-api-canary --replicas=2

Scale Stable Down (Shift Traffic Toward Canary)

kubectl scale deployment booking-api-stable --replicas=3

Promote Canary to Full Production

kubectl scale deployment booking-api-canary --replicas=5
kubectl scale deployment booking-api-stable --replicas=0

Rollback — Remove the Canary

kubectl scale deployment booking-api-canary --replicas=0
kubectl scale deployment booking-api-stable --replicas=4

Clean Up After Promotion

kubectl delete deployment booking-api-stable

Step-by-Step Example

Scenario 1: Deploy a Canary and Observe

We have a .NET 10 Booking API running in production. A new version (2.0) includes a rewritten availability-check algorithm. We want to expose it to a small fraction of traffic first to make sure latency stays low.

1. Deploy the Stable Version (v1.0) with 4 Replicas

apiVersion: apps/v1
kind: Deployment
metadata:
name: booking-api-stable
labels:
app: booking-api
track: stable
spec:
replicas: 4
selector:
matchLabels:
app: booking-api
track: stable
template:
metadata:
labels:
app: booking-api
track: stable
spec:
containers:
- name: booking-api
image: myregistry.azurecr.io/booking-api:1.0
ports:
- containerPort: 8080
env:
- name: ASPNETCORE_URLS
value: "http://+:8080"
- name: BOOKING_DB_CONNECTION
value: "Server=booking-db;Database=BookingDb;Trusted_Connection=True"
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
readinessProbe:
httpGet:
path: /healthz/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz/live
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
kubectl apply -f booking-api-stable-deployment.yaml

2. Create the Service

Notice the selector uses only app: booking-api — it intentionally does not include the track label. This is what allows both Stable and Canary Pods to receive traffic.

apiVersion: v1
kind: Service
metadata:
name: booking-api-service
spec:
type: ClusterIP
selector:
app: booking-api
ports:
- port: 80
targetPort: 8080
kubectl apply -f booking-api-service.yaml

3. Verify Stable Is Running

kubectl get pods -l app=booking-api --show-labels

You should see 4 Pods, all with track=stable.

4. Deploy the Canary (v2.0) with 1 Replica

apiVersion: apps/v1
kind: Deployment
metadata:
name: booking-api-canary
labels:
app: booking-api
track: canary
spec:
replicas: 1
selector:
matchLabels:
app: booking-api
track: canary
template:
metadata:
labels:
app: booking-api
track: canary
spec:
containers:
- name: booking-api
image: myregistry.azurecr.io/booking-api:2.0
ports:
- containerPort: 8080
env:
- name: ASPNETCORE_URLS
value: "http://+:8080"
- name: BOOKING_DB_CONNECTION
value: "Server=booking-db;Database=BookingDb;Trusted_Connection=True"
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
readinessProbe:
httpGet:
path: /healthz/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz/live
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
kubectl apply -f booking-api-canary-deployment.yaml

5. Confirm Traffic Is Split

kubectl get pods -l app=booking-api --show-labels

You should see 5 Pods total: 4 with track=stable and 1 with track=canary.

kubectl get endpoints booking-api-service

The endpoints list should contain 5 IP addresses. Approximately 20% of traffic now goes to the canary Pod.

Scenario 2: Gradually Promoting the Canary

After monitoring the canary for 15 minutes, error rates and latency look normal. Time to increase canary traffic.

6. Scale Canary to 2 Replicas (~40% Traffic)

kubectl scale deployment booking-api-canary --replicas=2
kubectl scale deployment booking-api-stable --replicas=3

Now 2 out of 5 Pods run the canary. Monitor for another observation window.

7. Scale Canary to 3 Replicas (~60% Traffic)

kubectl scale deployment booking-api-canary --replicas=3
kubectl scale deployment booking-api-stable --replicas=2

8. Full Promotion — Canary Becomes Production

kubectl scale deployment booking-api-canary --replicas=5
kubectl scale deployment booking-api-stable --replicas=0

All traffic now goes to version 2.0. Once you are confident, clean up the old Deployment:

kubectl delete deployment booking-api-stable

Scenario 3: Canary Rollback

If at any point the canary shows problems — elevated errors, increased latency, or Pod crashes — immediately scale it to zero:

9. Emergency Rollback

kubectl scale deployment booking-api-canary --replicas=0

All traffic returns to the stable Pods. If you had reduced stable replicas, scale them back up:

kubectl scale deployment booking-api-stable --replicas=4

The canary Deployment still exists with 0 replicas. You can debug, fix, rebuild the image, and try again without deleting anything.

Summary

  1. Canary deployment sends a small percentage of traffic to the new version by running it with fewer replicas alongside the stable version.
  2. The Service selector matches only the app label, so it load-balances across both stable and canary Pods.
  3. Traffic ratio is controlled by the replica counts of each Deployment. More canary replicas = more canary traffic.
  4. Promotion is gradual — scale canary up and stable down in steps, with an observation window at each step.
  5. Rollback is fast — scale the canary to 0 replicas and restore the stable replica count.
  6. The track label (track: stable / track: canary) helps you manage and query Pods, but is intentionally excluded from the Service selector.
  7. Best for: high-traffic services where you want to limit the blast radius of a new release and observe real production behaviour before full promotion.


Share this lesson: