Istio doesn’t just route traffic between microservices; it fundamentally rewrites how they communicate, making them discoverable, secure, and observable without touching their code.

Let’s watch it in action. Imagine we have two services, frontend and backend.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: frontend-backend
spec:
  hosts:
  - backend
  gateways:
  - frontend-gateway # This gateway is attached to the ingress controller
  http:
  - route:
    - destination:
        host: backend
        subset: v1 # We'll define this subset later

This VirtualService tells Istio that any traffic destined for backend should, by default, be routed to the v1 subset of the backend service. But where does the v1 subset come from? That’s where DestinationRule shines.

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: backend-dr
spec:
  host: backend
  subsets:
  - name: v1
    labels:
      version: v1 # This label must match the Kubernetes pod labels
  - name: v2
    labels:
      version: v2

Here, we’ve defined two subsets for backend: v1 and v2. If your backend pods have the label version: v1, they’ll be routed to the v1 subset. If they have version: v2, they’ll go to v2. Now, we can use VirtualService to split traffic:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: frontend-backend-traffic-split
spec:
  hosts:
  - backend
  http:
  - route:
    - destination:
        host: backend
        subset: v1
      weight: 90 # 90% of traffic goes to v1
    - destination:
        host: backend
        subset: v2
      weight: 10 # 10% of traffic goes to v2

This VirtualService now directs 90% of traffic to backend’s v1 subset and 10% to v2. This is your canary deployment, your gradual rollout, handled entirely by Istio’s control plane (specifically, the istiod component) pushing configuration to the sidecar proxies (Envoy) running alongside your application pods.

The mental model is this: your microservices think they’re talking directly to each other via their service names (e.g., http://backend). But in reality, the frontend service’s Envoy sidecar intercepts this request. It looks up the VirtualService rules for backend, determines the actual destination IP and port (which will be the IP of another Envoy proxy), and forwards the request. The receiving Envoy proxy then forwards it to the actual backend application pod. This interception and intelligent routing is the core of Istio’s traffic management.

You can also enforce TLS between services automatically. If you define a PeerAuthentication policy like this:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: backend-mtls
spec:
  selector:
    matchLabels:
      app: backend # Apply this to backend pods
  mtls:
    mode: STRICT # Enforce mutual TLS

And ensure your frontend service has a VirtualService that refers to the backend host, the frontend’s Envoy proxy will automatically negotiate a TLS connection with the backend’s Envoy proxy. The backend application itself doesn’t need to know anything about TLS certificates.

The most surprising thing is how much of this traffic management is externalized. You can implement circuit breakers, retries, timeouts, fault injection, and traffic mirroring without touching your application code. For instance, to add a retry for 5xx errors on requests to backend:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: backend-retries
spec:
  hosts:
  - backend
  http:
  - route:
    - destination:
        host: backend
        subset: v1
    retries:
      attempts: 3 # Retry up to 3 times
      per_try_timeout: 2s # Each attempt has a 2-second timeout
      retry_on: 5xx # Retry only on 5xx HTTP status codes

This configuration is pushed to the Envoy sidecars. When a frontend Envoy proxy makes a request to backend and receives a 500 error, it will automatically retry the request up to three times, with each attempt having a 2-second timeout. If all attempts fail, it will return the error to the frontend application. This is all handled at the proxy level, providing resilience without code changes.

One aspect that catches many off guard is how Istio’s hosts field in VirtualService and DestinationRule operates. It doesn’t refer to the Kubernetes Service name directly, but rather to the service name as seen by the Envoy sidecar. By default, if you install Istio with the default configuration, this usually aligns with your Kubernetes service names (e.g., backend.namespace.svc.cluster.local or simply backend if in the same namespace). However, when you start customizing Istio’s mesh configuration, especially with external services or custom DNS, understanding what host truly represents in the context of Envoy’s service discovery is crucial for correct routing.

The next hurdle is usually understanding how to integrate external services into your Istio mesh.

Want structured learning?

Take the full Computer Networking course →