Run a Canary Release Behind an Existing Service
In namespace default, the following resources exist: Deployment web-app with 5 replicas, labels app=webapp, version=v1 Service web-service with selector app=webapp
Live lab access
This task description is public. Live practice requires full access unless this is the selected free weekly task. AI Lab Coach is available in single-task labs, including the free weekly task. See this week's free task.
Create an account to continue →
On a phone? Read the task and create your account here, then sign in on a desktop or laptop for the terminal lab. Nothing starts until you choose Start.
The task
Task
In namespace default, the following resources exist:
- Deployment
web-appwith 5 replicas, labelsapp=webapp, version=v1 - Service
web-servicewith selectorapp=webapp
Requirements
- Scale Deployment
web-appto 8 replicas (80% of 10 total) - Create a new Deployment
web-app-canarywith 2 replicas, labelsapp=webapp, version=v2 - Both Deployments should be selected by
web-service - Verify the traffic split using endpoints
Note: This is a manual canary pattern where traffic is split based on replica counts.
Original Prepium guide · Written for Kubernetes v1.35 and checked against the official documentation on 6 October 2026. Use a disposable practice cluster.
Split traffic by replica count: a manual canary behind one Service
Kubernetes has no built-in "send 10% to the new version" switch. What it does have is a Service that forwards to every ready Pod matching its selector. If two Deployments produce Pods that both match that selector, the share each version receives follows the share of Pods it owns. That is the whole canary trick, and the exam expects you to build it with labels and prove it with EndpointSlices.
Service quotes (selector: app=quotes)
|-- quotes-stable 3 Pods app=quotes, release=stable
'-- quotes-canary 1 Pod app=quotes, release=canary => about 1 in 4 requests
1. Give every Pod a shared label and a track label
Each Pod template carries two labels. app=quotes is the one the Service selects. release tells the tracks apart, and each Deployment includes it in its own spec.selector, so the two controllers never claim each other's Pods. The image is the small hostname server from the Kubernetes Service debugging guide; it answers on port 9376 with the Pod's name, which makes the split visible. Save this as canary-tracks.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: quotes-stable
namespace: prepium-tutorial
spec:
replicas: 3
selector:
matchLabels:
app: quotes
release: stable
template:
metadata:
labels:
app: quotes
release: stable
spec:
containers:
- name: server
image: registry.k8s.io/serve_hostname
ports:
- containerPort: 9376
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: quotes-canary
namespace: prepium-tutorial
spec:
replicas: 1
selector:
matchLabels:
app: quotes
release: canary
template:
metadata:
labels:
app: quotes
release: canary
spec:
containers:
- name: server
image: registry.k8s.io/serve_hostname
ports:
- containerPort: 9376
---
apiVersion: v1
kind: Service
metadata:
name: quotes
namespace: prepium-tutorial
spec:
selector:
app: quotes
ports:
- port: 80
targetPort: 9376
kubectl create namespace prepium-tutorial
kubectl apply -f canary-tracks.yaml
kubectl -n prepium-tutorial get pods -l app=quotes -L release
The -L release flag adds a RELEASE column, so you can count stable and canary Pods at a glance.
2. Prove both tracks sit behind the Service
The control plane builds EndpointSlices for any Service with a selector and labels them with kubernetes.io/service-name. Each endpoint has a targetRef naming the Pod and a conditions.ready flag:
kubectl -n prepium-tutorial get endpointslices -l kubernetes.io/service-name=quotes \
-o jsonpath='{range .items[*].endpoints[*]}{.targetRef.name}{"\t"}{.conditions.ready}{"\n"}{end}'
You want four lines, three starting with quotes-stable- and one with quotes-canary-, all true. Now sample real traffic from inside the cluster:
kubectl -n prepium-tutorial run probe --image=busybox:1.36 --restart=Never --rm -it -- \
sh -c 'for i in $(seq 1 20); do wget -qO- http://quotes; echo; done'
Roughly a quarter of the replies should name the canary Pod. In kube-proxy's iptables mode each new connection picks a backend at random, so the ratio is a tendency over many requests, not an exact rotation.
3. Move the dial by scaling
The canary share is canary Pods divided by all ready Pods. Promote the canary to half the traffic without touching any label:
kubectl -n prepium-tutorial scale deployment quotes-stable --replicas=2
kubectl -n prepium-tutorial scale deployment quotes-canary --replicas=2
Re-run the EndpointSlice query: two of each. Only ready endpoints count, so a canary Pod failing its readiness probe receives nothing, which is often exactly the safety net you want.
4. Contrast: blue-green switches the selector instead
Blue-green keeps both versions fully scaled and makes the Service choose one by adding the track label to its selector. The cut-over is a single selector change, all or nothing, and rollback is the same change in reverse:
kubectl -n prepium-tutorial patch service quotes --type=merge \
-p '{"spec":{"selector":{"app":"quotes","release":"canary"}}}'
The EndpointSlice now lists only canary Pods. Canary trades that instant switch for gradual exposure.
Common mistakes
- Both Deployments select only
app=quotes. Their selectors overlap, which the Deployment docs warn makes controllers conflict; keep the track label in each selector. - The Service selector includes the stable track label. The canary Pods run happily but never appear in the EndpointSlice, so they get zero traffic.
- A typo in the shared label (
app: quote) on one template. Same symptom: Running Pods missing from the endpoint list. - Editing a Deployment's
spec.selectorafter creation. It is immutable inapps/v1, so the API rejects the change; delete and recreate instead. kubectl set selectorreplaces the whole Service selector, so passing only the track label silently dropsapp.
| Example check | Evidence |
|---|---|
| Service matches both tracks | spec.selector holds only the shared label |
| Replica ratio is as intended | READY counts on both Deployments |
| Both tracks receive traffic | EndpointSlice targetRef names from each Deployment, all ready |
| Tracks stay separate | Each Deployment's selector includes its own track value |
Cleanup: kubectl delete namespace prepium-tutorial. References: Deploy a release using a canary deployment, EndpointSlices, Virtual IPs and service proxies. Next, try rolling updates and rollback or Service selector troubleshooting.
Video walkthrough: CKAD Exam Question 6 - Traffic Split on YouTube. It shows the full solution, so try the task first.
What this tests
Roll out and update deployments, run canary and blue-green strategies, and right-size workloads. On the CKAD exam, Application Deployment tasks are graded purely on what you build in the cluster - not multiple choice - so the only way to get faster is to do them on a real cluster against a clock.
Practice it for real
The public description lets you study the task before signing up. Full access includes this live lab; the selected Task of the Week is free with an account. You solve the task in a real terminal, hit validate, and a programmatic checker scores exactly what you got right and wrong (with partial credit). You can open the solution while practising, then retry and use the failed checks to improve your score.