Azure 45: Istio VirtualService + DestinationRule for canary
easy⏱ 5 mincourseazure
DestinationRule subsets
The DestinationRule registers the two subsets (stable, canary) that the VirtualService can route to. They're selected by Kubernetes labels (role: stable, role: canary) that Argo Rollouts attaches to pods automatically as it scales each ReplicaSet. No subsets = no canary; the chart always defines both.
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: odyssey-api-destination-rule
spec:
host: odyssey-api
subsets:
- name: stable
labels: { app: odyssey-api, role: stable }
- name: canary
labels: { app: odyssey-api, role: canary }
VirtualService weights
The VirtualService has a single HTTP route with TWO destinations (same host, different subsets) and a weight: on each. Initial state is 100 stable / 0 canary. During a rollout, Argo updates these weights step by step: 10/90, 50/50, 100% canary, then a final promote that flips canary → stable.
http:
- name: primary
route:
- destination: { host: odyssey-api, subset: stable, port: { number: 80 } }
weight: 100
- destination: { host: odyssey-api, subset: canary, port: { number: 80 } }
weight: 0
mTLS via PeerAuthentication
On top of routing, Istio enforces mutual TLS between sidecars when a PeerAuthentication policy is set to STRICT. Lythia applies it once at the namespace level; every pod with sidecar injection participates. A pod without a sidecar (or from outside the mesh) gets a 503 — perfect for detecting accidental bypass.
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: apps
spec:
mtls: { mode: STRICT }
Simulate a traffic split
Write splitTraffic(stableWeight, canaryWeight, requests) that returns { stable, canary } counts using deterministic round-robin so tests are reproducible. Throw if weights don't sum to 100, or if either is negative. Also write mtlsAllowed(callerHasSidecar, calleeHasSidecar, mode) returning a boolean (STRICT: both must be true; PERMISSIVE: any). Log a simulation for 10/90, 50/50, 0/100, and a couple of mTLS combinations.
Always pin the route name in canary specs
Argo Rollouts updates the VirtualService by route name (primary is the convention). If you have multiple routes (e.g., one for /api/v1 and one for /healthz) and don't pin the right route, Argo will silently update the wrong one. Always set trafficRouting.istio.virtualService.routes: [primary].
Quiz: what happens at 100% canary?
After Argo shifts to 100% canary and runs an analysis, it promotes by swapping labels: the canary ReplicaSet is relabeled role: stable, and the old stable scales to 0. The VirtualService goes back to 100/0 — pointing at what is now structurally the canary subset (which contains the new code). Argo cleans up the old ReplicaSet after scaleDownDelaySeconds.