Istio’s canary deployments are fundamentally about controlling traffic flow to different versions of your application, allowing you to roll out new versions gradually without interrupting service.
Let’s see this in action. Imagine you have a v1 of your my-app service running in Kubernetes, and you want to introduce v2 with minimal risk.
# Deployment for v1
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-v1
spec:
replicas: 3
selector:
matchLabels:
app: my-app
version: v1
template:
metadata:
labels:
app: my-app
version: v1
spec:
containers:
- name: my-app
image: my-registry/my-app:v1
ports:
- containerPort: 8080
# Deployment for v2
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-v2
spec:
replicas: 3
selector:
matchLabels:
app: my-app
version: v2
template:
metadata:
labels:
app: my-app
version: v2
spec:
containers:
- name: my-app
image: my-registry/my-app:v2
ports:
- containerPort: 8080
Now, you’ll define Istio resources to manage the traffic. First, a VirtualService that defines how traffic reaches my-app. Initially, it points all traffic to v1.
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: my-app
spec:
hosts:
- my-app.your-namespace.svc.cluster.local
http:
- route:
- destination:
host: my-app
subset: v1
weight: 100
This VirtualService references a DestinationRule which defines the "subsets" – essentially, different versions of your service.
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: my-app
spec:
host: my-app
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
At this point, all requests to my-app.your-namespace.svc.cluster.local go to v1. Now, to start a canary, you modify the VirtualService to send a small percentage of traffic to v2.
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: my-app
spec:
hosts:
- my-app.your-namespace.svc.cluster.local
http:
- route:
- destination:
host: my-app
subset: v1
weight: 90
- destination:
host: my-app
subset: v2
weight: 10 # Sending 10% of traffic to v2
Istio’s Envoy proxies, sitting at the edge of your service or as sidecars, intercept requests to my-app. Based on the VirtualService weights, they intelligently route 90% of incoming traffic to pods labeled version: v1 and 10% to pods labeled version: v2. This 10% is your canary group. You monitor this v2 traffic closely for errors, performance degradation, or unexpected behavior. If all looks good, you gradually increase the weight for v2 and decrease it for v1 until 100% of traffic is on the new version.
The core mechanism is Istio’s VirtualService and DestinationRule. The VirtualService acts as a traffic router, defining rules for directing requests based on criteria like HTTP headers, request paths, or source IP. The DestinationRule then specifies how to route traffic to the different versions (subsets) of a service, often defining load balancing policies or TLS settings for each subset. By manipulating the weight field in the VirtualService’s route section, you precisely control the percentage of traffic that flows to each defined subset.
A common misconception is that VirtualService and DestinationRule are interchangeable; they are not. The VirtualService defines the entry points and routing logic for traffic entering your mesh or between services, while the DestinationRule defines the policies for traffic leaving a service to reach specific versions (subsets) of another service. You cannot define subsets directly in VirtualService; they must be defined in a DestinationRule that the VirtualService then references.
Once you’ve successfully rolled out v2 to 100% of traffic, you can simply delete the v1 deployment and its corresponding subset definition in the DestinationRule to clean up.
The next step is to explore advanced traffic management features like fault injection and mirroring.