6.6 Using Labels for Canary Deployments and Traffic Splitting
Right, so you’ve got a new version of your application. It’s a masterpiece of code, a symphony of microservices. You’re also not an idiot, so you’re not about to just slam this new code into production for all your users at once. That’s a great way to turn a deployment into a panic-induced incident report. This is where the beautiful, simple power of labels comes in for canary deployments and traffic splitting. Forget complex service meshes for a second; you can do a shocking amount with just a few well-placed labels and a Kubernetes Service.
Think of it like this: your Service isn’t a load balancer that sends traffic to pods. It’s a selector that sends traffic to anything that matches its label key-value pairs. This is the fundamental trick. We’re going to run two different deployments simultaneously—your stable, v1 app and your shiny new canary, v2 app. They’ll both have the app label that your Service is looking for, so they’ll both receive traffic. The magic is in how we control the proportion of traffic each one gets.
The Basic Setup: Your Stable Service
First, let’s establish your baseline. You have a Deployment for your stable version. It has a set of labels, the most important one being something like app: my-awesome-app. Your Service uses a selector to find pods with that exact label.
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-awesome-service
spec:
selector:
app: my-awesome-app
track: stable # We'll use this later for control
ports:
- protocol: TCP
port: 80
targetPort: 8080
# deployment-stable.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-awesome-app-stable
spec:
replicas: 3
selector:
matchLabels:
app: my-awesome-app
track: stable
template:
metadata:
labels:
app: my-awesome-app
track: stable
version: v1.0.0
spec:
containers:
- name: app
image: my-app:v1.0.0
ports:
- containerPort: 8080
This setup is boring, reliable, and serves 100% of the traffic to your v1.0.0 pods. Notice the track: stable label on both the Service selector and the Pods. This is our first line of defense.
Releasing the Canary
Now, you build v2.0.0. Instead of updating the stable deployment, you deploy a second deployment. This canary deployment will have most of the same labels, crucially including app: my-awesome-app, so the Service will find it. But it will have a different track label. The Service selector includes track: stable, so it will ignore this canary. For now. This is your safety net.
# deployment-canary.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-awesome-app-canary
spec:
replicas: 1 # Note: only 1 replica! This is key.
selector:
matchLabels:
app: my-awesome-app
track: canary
template:
metadata:
labels:
app: my-awesome-app
track: canary # Different from the Service selector
version: v2.0.0
spec:
containers:
- name: app
image: my-app:v2.0.0
ports:
- containerPort: 8080
After applying this, you’ll have four pods: three stable, one canary. But because your Service’s selector is app: my-awesome-app,track: stable, it will only send traffic to the three stable pods. The canary pod is running but idle. You can now kubectl exec into it, run some quick curls, or point some internal tooling at it to verify it starts up correctly. This is your “oh crap” moment detector. If v2.0.0 has a catastrophic startup error, you just delete the canary deployment and no user ever saw it.
Shifting Traffic with Selector Surgery
Okay, the canary pod is running happily. Now for the traffic shift. This is where you perform a bit of Kubernetes sleight of hand. You’re going to edit the Service in-place. This is one of those rare moments where a direct kubectl edit service is perfectly acceptable.
You change the Service’s selector from track: stable to just app: my-awesome-app.
# The updated service selector
spec:
selector:
app: my-awesome-app
# track: stable <- This line is removed!
Apply that change. Instantly, the Service’s logic changes. It now finds all pods with app: my-awesome-app, regardless of the track label. You have 3 stable pods and 1 canary pod, so traffic will be roughly split 75% to stable and 25% to the canary. You’ve just performed a canary release.
This works because the Kubernetes Service is a relatively simple load balancer; it uses an algorithm (like round-robin) to distribute connections across all endpoints it finds. The number of replicas in each deployment directly controls the traffic weight. 3-to-1 replica ratio? 75/25 split. 9 stable and 1 canary? 90/10 split. It’s beautifully simple.
The Gotchas and The Graceful Way Out
This isn’t perfect, and you need to know the sharp edges.
Connection-Based, Not Request-Based: This is the biggest caveat. The Service operates at the TCP/UDP level, not HTTP. It balances connections, not individual requests. If you have a few long-lived connections (like a websocket), the traffic split will be much less accurate. For true HTTP request-based splitting, you need an Ingress controller or a Service Mesh (like Istio). But for most simple web services, this connection-based split is “good enough” for a first canary.
Rolling Back is Crucial: You’re watching your metrics like a hawk, right? The canary starts throwing a 2% error rate. You need to roll back fast. To do this, you simply reverse the process: edit the Service again and put the
track: stableselector back. Instantly, all traffic flips back to the stable pods. Then you can go and delete the faulty canary deployment at your leisure. The rollback is near-instantaneous because it’s just a metadata change on the Service object.Sticky Sessions: This method knows nothing about sessions. A user might hit v1, then v2, then v1 again. If you require stateful stickiness, this naive approach won’t work for you.
The real beauty of this method is its brutal simplicity. You didn’t need to install anything new. You used the basic, primitive building blocks of Kubernetes as they were intended. You labeled your stuff, and you used a selector to control it. It’s a perfect example of understanding the “why”—the Service is just a label-matching mechanism—to orchestrate a powerful “how.” Now go deploy with a bit more confidence. Just keep one hand near the “edit service” command.