Side-by-Side Testing (Canary)
Goal: validate a new version in isolation alongside production in a single cluster before committing to a full region rollout.
How it works
A canary release with Karmada primitives uses three object types:
- Base
Deployment— the stable version, propagated to all clusters by aPropagationPolicy. - Canary
Deployment— a separate Deployment running the new version, propagated to a single target cluster by its ownPropagationPolicy. Both Deployments share the sameServiceselector (app: http-probe-app), so traffic splits by pod count automatically. OverridePolicy— patches the base Deployment'sROLLOUT_LABELvalue on the target cluster, triggering a Kubernetes rolling update there to converge on the new version. Finalization applies the new version to the base manifest and removes all temporary resources.
Demo 1 — Canary in one cluster, promote to region
Validate the new version in member1 only before committing to the rest of the region.
member2 and member3 serve stable traffic throughout.
The following diagram depicts the full sequence of operations — from single-cluster canary deployment through promotion and region-wide finalization:
Step 1: Deploy a canary alongside the base on member1
Create a separate canary Deployment running version v2, propagated to member1 only.
http-probe-app-canary-member1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: http-probe-app-canary-member1
labels:
app: http-probe-app
version: canary
spec:
replicas: 1
selector:
matchLabels:
app: http-probe-app
version: canary-member1
template:
metadata:
labels:
app: http-probe-app
version: canary-member1
spec:
containers:
- name: http-probe-app
image: ghcr.io/cmontemuino/http-probe-test-app:v0.5.0
ports:
- containerPort: 8080
env:
- name: ROLLOUT_LABEL
value: v2
resources:
requests:
cpu: 25m
memory: 64Mi
limits:
cpu: 25m
memory: 64Mi
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
---
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
name: http-probe-app-canary-member1-propagation
spec:
resourceSelectors:
- apiVersion: apps/v1
kind: Deployment
name: http-probe-app-canary-member1
placement:
clusterAffinity:
clusterNames:
- member1
Two things to note:
- The canary Deployment uses an additional label
version: canary-member1on its pod template. This label is not present in the base Deployment's pod template, so the canary pods are distinct from the base pods. However, both share theapp: http-probe-applabel, which is all the Service selector matches — so traffic is distributed across all pods from both Deployments proportionally. - The canary
PropagationPolicyselects onlyhttp-probe-app-canary-member1by name, targetingmember1only. The basePropagationPolicyis unaffected — it continues propagating the base Deployment to all three clusters.
kubectl apply -f canary/http-probe-app-canary-member1.yaml
Verify the canary Deployment was created and propagated:
kubectl get deployment http-probe-app-canary-member1
Expected output:
NAME READY UP-TO-DATE AVAILABLE AGE
http-probe-app-canary-member1 1/1 1 1 2m44s
kubectl get resourcebinding http-probe-app-canary-member1-deployment
Expected output:
NAME SCHEDULED FULLYAPPLIED AGE
http-probe-app-canary-member1-deployment True True 2m48s
At this point member1 is running 2 base pods (v1) and 1 canary pod (v2), for 3 pods
total. member2 and member3 run only their 2 base pods. Approximately 1 in 3 requests to
member1 will be served by the canary.
What to observe in the dashboard:
- Replica panel: a new canary pod (yellow
●) appears in themember1column alongside the 2 existing stable pods (green○). Themember2andmember3columns show only stable pods — the canaryPropagationPolicytargetsmember1only. - Traffic panel: the
member1column begins showing yellow-highlighted responses as requests land on the canary pod. The ratio of yellow to white responses reflects the 1 canary pod out of 3 total pods onmember1.
Step 2: Promote v2 to the base deployment on member1
Once the canary has been validated, apply an OverridePolicy to patch the base Deployment's
ROLLOUT_LABEL to v2 on member1. This triggers a Kubernetes rolling update on member1
— the local controller replaces base pods one by one with pods running v2.
http-probe-promote-override-member1.yaml
apiVersion: policy.karmada.io/v1alpha1
kind: OverridePolicy
metadata:
name: http-probe-promote-override
spec:
resourceSelectors:
- apiVersion: apps/v1
kind: Deployment
name: http-probe-app
overrideRules:
- targetCluster:
clusterNames:
- member1
overriders:
plaintext:
- path: /spec/template/spec/containers/0/env/0/value
operator: replace
value: v2
The OverridePolicy patches only the rendered manifest sent to member1 — the base
Deployment spec on the Karmada API is unchanged. member2 and member3 continue running
v1 without any interruption.
kubectl apply -f canary/http-probe-promote-override-member1.yaml
Watch the base Deployment roll on member1:
kubectl --kubeconfig ~/.kube/members.config --context member1 \
rollout status deployment/http-probe-app
What to observe in the dashboard:
- Replica panel: the base pods in
member1begin rolling tov2one by one. During the transition both yellow●(new) and green○(old) base pods are visible in themember1column simultaneously alongside the canary pod. Themember2andmember3columns are unaffected. - Traffic panel: the
member1column shows an increasing proportion of yellow-highlighted responses as more base pods roll tov2. Once all base pods have updated, themember1column becomes fully yellow — the canary pod is now indistinguishable from the promoted base pods since they run the same version.
Step 3: Finalize the promotion
Once you are satisfied that v2 is healthy on member1, finalize the promotion. This step:
- Applies
http-probe-app-v2.yaml— the base manifest withROLLOUT_LABEL: v2— to the Karmada API. Because theOverridePolicyalready hasmember1runningv2, Karmada reconciles the updated manifest and Kubernetes sees no change in the pod spec onmember1— no pods are restarted there.member2andmember3receive the updated manifest for the first time and roll via a standard Kubernetes rolling update. - Deletes the
OverridePolicy— no longer needed since the base manifest now matches the live state. - Deletes the canary Deployment and its
PropagationPolicy.
kubectl apply -f base/http-probe-app-v2.yaml
kubectl delete overridepolicy http-probe-promote-override
kubectl delete -f canary/http-probe-app-canary-member1.yaml
Verify all clusters are now on v2:
kubectl get resourcebinding http-probe-app-deployment -o jsonpath=\
'{range .status.aggregatedStatus[*]}{.clusterName}: {.health} ready={.status.readyReplicas}/{.status.replicas}{"\n"}{end}'
Expected output:
member1: Healthy ready=2/2
member2: Healthy ready=2/2
member3: Healthy ready=2/2
Confirm the running version is v2 on all clusters:
curl -s -H "Host: http-probe.local" http://localhost:8090/info | jq .rollout_label
curl -s -H "Host: http-probe.local" http://localhost:8091/info | jq .rollout_label
curl -s -H "Host: http-probe.local" http://localhost:8092/info | jq .rollout_label
Each command should return "v2".
What to observe in the dashboard:
- Replica panel: the canary pod (yellow
●) disappears from themember1column. All pods across all three clusters now show as stable (green○). Themember2andmember3columns roll tov2as the updated base manifest propagates to them — you will briefly see yellow●pods appear as the rolling update proceeds, then settle back to green○once complete. - Traffic panel: all three columns return to uniform white responses once the base manifest
has propagated and all pods have rolled. The
ver=v2field will be consistent across every response in every cluster column.
Demo 2 — Canary across the region, promote per cluster
Validate the new version in all three clusters simultaneously using a single canary
Deployment propagated to all members, then promote the base one cluster at a time — giving
a deliberate gate between each step before committing to the next.
The following diagram depicts the full sequence of operations — from canary deployment across all clusters through per-cluster promotion and zero-churn finalization:
Note: Before running Demo 2, ensure the cluster is back to
v1. If you ran Demo 1, the cluster will already be onv2. Re-apply the base manifest to reset:kubectl apply -f base/http-probe-app.yaml
Step 1: Deploy a canary across all clusters
A single canary Deployment with a PropagationPolicy targeting all three clusters. Each
member gets 1 canary pod running v2 alongside its 2 base pods running v1.
http-probe-app-canary-all.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: http-probe-app-canary
labels:
app: http-probe-app
version: canary
spec:
replicas: 1
selector:
matchLabels:
app: http-probe-app
version: canary
template:
metadata:
labels:
app: http-probe-app
version: canary
spec:
containers:
- name: http-probe-app
image: ghcr.io/cmontemuino/http-probe-test-app:v0.5.0
ports:
- containerPort: 8080
env:
- name: ROLLOUT_LABEL
value: v2
resources:
requests:
cpu: 25m
memory: 64Mi
limits:
cpu: 25m
memory: 64Mi
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
---
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
name: http-probe-app-canary-propagation
spec:
resourceSelectors:
- apiVersion: apps/v1
kind: Deployment
name: http-probe-app-canary
placement:
clusterAffinity:
clusterNames:
- member1
- member2
- member3
kubectl apply -f canary/http-probe-app-canary-all.yaml
What to observe in the dashboard:
- Replica panel: a canary pod (yellow
●) appears in all three cluster columns simultaneously alongside the 2 stable base pods (green○). Each cluster showsstable=2 canary=1in the column header. - Traffic panel: all three columns begin showing yellow-highlighted responses as requests land on the canary pods. Approximately 1 in 3 requests per cluster hits the canary.
Step 2: Promote the base on member1
Apply an OverridePolicy targeting member1 only. This patches the base Deployment's
ROLLOUT_LABEL to v2 on member1, triggering a Kubernetes rolling update there.
http-probe-promote-override-member1.yaml
apiVersion: policy.karmada.io/v1alpha1
kind: OverridePolicy
metadata:
name: http-probe-promote-override
spec:
resourceSelectors:
- apiVersion: apps/v1
kind: Deployment
name: http-probe-app
overrideRules:
- targetCluster:
clusterNames:
- member1
overriders:
plaintext:
- path: /spec/template/spec/containers/0/env/0/value
operator: replace
value: v2
kubectl apply -f canary/http-probe-promote-override-member1.yaml
Watch the base Deployment roll on member1:
kubectl --kubeconfig ~/.kube/members.config --context member1 \
rollout status deployment/http-probe-app
What to observe in the dashboard:
- Replica panel: the base pods in
member1begin rolling tov2one by one. During the transition both yellow●(new) and green○(old) base pods are visible in themember1column alongside the canary pod. Themember2andmember3columns are unaffected — their canary pods continue alongside their stable base pods. - Traffic panel: the
member1column shows a growing proportion of yellow responses as the base rolls. Themember2andmember3columns maintain their existing 1-in-3 canary ratio.
How progressive promotion works: each subsequent step does not create a new policy — it applies a manifest with the same
OverridePolicyname (http-probe-promote-override) and an expandedclusterNameslist. Karmada reconciles the update immediately, triggering a rolling update on each newly added cluster. The promotion state is fully visible and auditable as a single Karmada object at any point during the rollout.
Step 3: Extend the promotion to member2
Apply the same OverridePolicy with member1 and member2 in clusterNames. Karmada
patches the existing policy in place — no new object is created.
http-probe-promote-override-member1-member2.yaml
apiVersion: policy.karmada.io/v1alpha1
kind: OverridePolicy
metadata:
name: http-probe-promote-override
spec:
resourceSelectors:
- apiVersion: apps/v1
kind: Deployment
name: http-probe-app
overrideRules:
- targetCluster:
clusterNames:
- member1
- member2
overriders:
plaintext:
- path: /spec/template/spec/containers/0/env/0/value
operator: replace
value: v2
kubectl apply -f canary/http-probe-promote-override-member1-member2.yaml
Watch the base Deployment roll on member2:
kubectl --kubeconfig ~/.kube/members.config --context member2 \
rollout status deployment/http-probe-app
What to observe in the dashboard:
- Replica panel: the base pods in
member2begin rolling tov2one by one. During the transition both yellow●(new) and green○(old) base pods are now also visible in themember2column alongside the canary pod. Themember3column is unaffected — its canary pod continues alongside its stable base pods. - Traffic panel: the
member2column shows a growing proportion of yellow responses. Themember3column maintains its 1-in-3 canary ratio.
Step 4: Extend the promotion to member3
Apply the OverridePolicy with all three clusters in clusterNames.
http-probe-promote-override-all.yaml
apiVersion: policy.karmada.io/v1alpha1
kind: OverridePolicy
metadata:
name: http-probe-promote-override
spec:
resourceSelectors:
- apiVersion: apps/v1
kind: Deployment
name: http-probe-app
overrideRules:
- targetCluster:
clusterNames:
- member1
- member2
- member3
overriders:
plaintext:
- path: /spec/template/spec/containers/0/env/0/value
operator: replace
value: v2
kubectl apply -f canary/http-probe-promote-override-all.yaml
Watch the base Deployment roll on member3:
kubectl --kubeconfig ~/.kube/members.config --context member3 \
rollout status deployment/http-probe-app
What to observe in the dashboard:
- Replica panel: the base pods in
member3begin rolling tov2one by one. During the transition both yellow●(new) and green○(old) base pods are visible in themember3column alongside the canary pod. Once complete all base and canary pods across the region are now onv2. - Traffic panel: all three columns are fully yellow — every response carries
v2.
Step 5: Finalize the promotion
Update the base manifest to v2, then delete the OverridePolicy and the canary
Deployment. Because all clusters are already running v2 via the OverridePolicy,
updating the base manifest is a no-op at the pod level — no pods are restarted.
kubectl apply -f base/http-probe-app-v2.yaml
kubectl delete overridepolicy http-probe-promote-override
kubectl delete -f canary/http-probe-app-canary-all.yaml
Verify:
kubectl get resourcebinding http-probe-app-deployment -o jsonpath=\
'{range .status.aggregatedStatus[*]}{.clusterName}: {.health} ready={.status.readyReplicas}/{.status.replicas}{"\n"}{end}'
Expected output:
member1: Healthy ready=2/2
member2: Healthy ready=2/2
member3: Healthy ready=2/2
Confirm the running version is v2 on all clusters:
curl -s -H "Host: http-probe.local" http://localhost:8090/info | jq .rollout_label
curl -s -H "Host: http-probe.local" http://localhost:8091/info | jq .rollout_label
curl -s -H "Host: http-probe.local" http://localhost:8092/info | jq .rollout_label
Each command should return "v2".
What to observe in the dashboard:
- Replica panel: all canary pods (yellow
●) disappear from all three columns. All pods return to stable (green○) runningv2. No pod churn occurs — theOverridePolicyalready had all clusters onv2, so updating the base manifest is purely a source-of-truth update; Kubernetes sees no diff in the pod spec and restarts nothing. - Traffic panel: all three columns continue showing responses from
v2without interruption. The legend at the bottom bar disappears — no two versions are present simultaneously.
The sequence — promote via
OverridePolicyfirst, finalize the base manifest second — is what makes this approach zero-churn. TheOverridePolicyacts as a live patch; the finalize step absorbs that patch into the source of truth and cleans up temporary resources.
Demo 3 — Selective canary, partial promote, and rollback
Demonstrates the full flexibility of independent per-cluster control: deploy canaries selectively to two clusters (skip the third), roll one back, redeploy it, promote only that cluster, then roll back the promotion — all without affecting other clusters at any point. This demo exercises every primitive operation available: canary deploy, canary rollback, per-cluster promotion, promotion rollback, and full cleanup.
Note: Before running Demo 3, ensure the cluster is back to
v1. If you ran Demo 2, re-apply the base manifest to reset:kubectl apply -f base/http-probe-app.yaml
The steps we will execute are:
- Deploy a canary to
member1andmember2— skipmember3entirely - Roll back the canary on
member2 - Re-deploy the canary to
member2 - Promote the base on
member2only via anOverridePolicy - Roll back that promotion on
member2— base returns tov1 - Roll back all remaining canaries — environment returns to baseline
The following diagram depicts the full sequence of operations:
Step 1: Deploy a canary to member1 and member2 (skip member3)
Deploy individual canary Deployments to member1 and member2 — member3 is
intentionally skipped throughout this demo.
kubectl apply -f canary/http-probe-app-canary-member1.yaml
kubectl apply -f canary/http-probe-app-canary-member2.yaml
Verify both canaries are running:
kubectl get resourcebinding http-probe-app-canary-member1-deployment \
http-probe-app-canary-member2-deployment
Expected output:
NAME SCHEDULED FULLYAPPLIED AGE
http-probe-app-canary-member1-deployment True True 30s
http-probe-app-canary-member2-deployment True True 30s
What to observe in the dashboard:
- Replica panel: canary pods (yellow
●) appear in themember1andmember2columns alongside their 2 stable base pods (green○). Themember3column shows only stable pods — no canary was deployed there. - Traffic panel: yellow-highlighted responses appear in the
member1andmember2columns. Themember3column stays fully white.
Step 2: Roll back the canary on member2
Delete the canary Deployment and its PropagationPolicy for member2. The member1
canary is unaffected.
kubectl delete -f canary/http-probe-app-canary-member2.yaml
What to observe in the dashboard:
- Replica panel: the canary pod (yellow
●) disappears from themember2column. Themember1column is unaffected — its canary pod continues alongside its stable base pods. Themember3column remains unchanged. - Traffic panel: the
member2column returns to fully white stable responses. Themember1column continues showing yellow-highlighted responses.
Step 3: Re-deploy the canary to member2
Re-apply the canary manifest for member2. This is the same manifest as Step 1 — the
operation is fully idempotent.
kubectl apply -f canary/http-probe-app-canary-member2.yaml
What to observe in the dashboard:
- Replica panel: the canary pod (yellow
●) reappears in themember2column. Bothmember1andmember2now show canary pods alongside their stable base pods — identical state to after Step 1. - Traffic panel: yellow-highlighted responses resume in the
member2column.
Step 4: Promote the base on member2 only
Apply the OverridePolicy targeting member2 only. This patches the base Deployment's
ROLLOUT_LABEL to v2 on member2, triggering a Kubernetes rolling update there.
member1 has a canary running but its base remains on v1 — the OverridePolicy does
not target it.
kubectl apply -f canary/http-probe-promote-override-member2.yaml
Watch the base Deployment roll on member2:
kubectl --kubeconfig ~/.kube/members.config --context member2 \
rollout status deployment/http-probe-app
What to observe in the dashboard:
- Replica panel: the base pods in
member2begin rolling tov2one by one. During the transition both yellow●(new) and green○(old) base pods are visible in themember2column alongside the canary pod. Themember1column is unaffected — its canary pod continues alongside its unchanged stable base pods. Themember3column remains fully stable throughout. - Traffic panel: the
member2column shows a growing proportion of yellow responses as the base rolls. Themember1column maintains its existing 1-in-3 canary ratio unchanged.
This is the most complex state in the demo: member1 has a canary pod but its base is still
on v1 (no OverridePolicy targeting it), member2 has a canary pod and its base has been
promoted to v2 via the OverridePolicy, and member3 is completely untouched.
Step 5: Roll back the promotion on member2
Delete the OverridePolicy. Karmada removes the patch from member2 and Kubernetes rolls
the base Deployment back to v1. The canary Deployment on member2 remains — only the
base is rolled back.
kubectl delete overridepolicy http-probe-promote-override
Watch the rollback on member2:
kubectl --kubeconfig ~/.kube/members.config --context member2 \
rollout status deployment/http-probe-app
What to observe in the dashboard:
- Replica panel: the base pods in
member2roll back tov1one by one. During the transition both yellow●and green○base pods are briefly visible in themember2column. Once complete, themember2column returns tostable=2 canary=1— the same state as after Step 3. Themember1column is unaffected throughout. - Traffic panel: the
member2column returns to a 1-in-3 yellow ratio once the rollback completes. Themember1column is unchanged.
Step 6: Roll back all remaining canaries
Delete the canary Deployments for both member1 and member2. The environment returns
to the baseline state — only the base Deployment and its PropagationPolicy remain.
kubectl delete -f canary/http-probe-app-canary-member1.yaml
kubectl delete -f canary/http-probe-app-canary-member2.yaml
What to observe in the dashboard:
- Replica panel: canary pods (yellow
●) disappear from bothmember1andmember2columns. All three columns return to fully stable (green○). Themember3column was never touched throughout this entire demo. - Traffic panel: all three columns show only white stable responses. The bottom bar legend disappears — no two versions are present anywhere in the region.
This demo illustrates the key strength of Karmada primitives: every operation — deploy, rollback, promote, rollback promotion — is a targeted, declarative change to a specific policy object. No cluster is ever affected unless you explicitly name it. The base
Deploymentand itsPropagationPolicyare the stable anchor throughout; all canary and override objects are temporary and surgical.