Istio Tutorial: Service Mesh on Kubernetes
Learn how Istio adds traffic management, mTLS, and observability to Kubernetes services without changing your application code.
What is Istio?
Istio is an open-source service mesh that adds advanced networking features to a Kubernetes cluster. Instead of building retries, timeouts, mutual TLS, canary routing, and distributed tracing into every microservice, you let Istio handle them at the infrastructure layer. The application keeps making ordinary HTTP or gRPC calls, and Istio intercepts that traffic to apply policy and collect telemetry.
A mesh has two parts. The data plane is the set of proxies that sit in the request path and actually move bytes - in Istio these are Envoy proxies. The control plane is a single binary called istiod that turns your high-level configuration into Envoy config and pushes it out. This separation is the core idea: you write declarative intent, and istiod programs the proxies to match.
Istio supports two data-plane modes. The classic sidecar mode runs an Envoy container next to every application container in the same pod. Ambient mode, which reached general availability in Istio 1.24, drops the per-pod sidecar in favor of a per-node proxy called ztunnel for layer-4 traffic, plus optional waypoint proxies when you need layer-7 features. Ambient lowers resource cost and lets teams onboard namespace by namespace.
How Istio works (key concepts)
When a pod is part of the mesh, all of its inbound and outbound traffic is redirected to a proxy. In sidecar mode that redirection is done with iptables rules (or the Istio CNI plugin) inside the pod's network namespace; in ambient mode the node's CNI captures traffic to ztunnel. Either way, the proxy enforces policy before the request reaches your container.
A few resources do most of the work:
- istiod - the control plane. It watches the Kubernetes API and your Istio resources, then streams Envoy configuration to every proxy. It also issues the certificates that power mutual TLS.
- Gateway - configures the load balancer at the edge of the mesh, controlling which ports and hostnames are exposed for ingress or egress. Istio also supports the Kubernetes Gateway API, which is the recommended option for new setups.
- VirtualService - the main routing resource. It matches requests by host, path, header, or weight and forwards them to named destinations.
- DestinationRule - defines subsets of a service (usually by version label) and sets traffic policies such as load-balancing algorithm, connection-pool limits, and outlier detection for circuit breaking.
- PeerAuthentication - controls mutual TLS, for example requiring STRICT mTLS in a namespace so plaintext is rejected.
Because routing is decoupled from deployment, you can shift 5% of traffic to a new version, mirror live traffic to a test build, or inject faults for resilience testing, all without redeploying your app.
Installing Istio
Istio ships a CLI, istioctl, that handles installation and diagnostics. First download the release, which also pulls the sample manifests:
curl -L https://istio.io/downloadIstio | sh -
cd istio-*
export PATH=$PWD/bin:$PATH
istioctl version
For a feature-rich demo install with ingress and egress gateways, use the demo profile. For production, start from the default profile and customize it.
# Install the control plane and gateways
istioctl install --set profile=demo -y
# Tell Istio to inject sidecars into pods in the default namespace
kubectl label namespace default istio-injection=enabled --overwrite
The label is what enables automatic sidecar injection. Any pod created in a labeled namespace after this point gets an Envoy proxy added by Istio's mutating webhook. Existing pods need a restart (kubectl rollout restart) to pick up the sidecar. To run the newer sidecar-less data plane instead, install with istioctl install --set profile=ambient -y, which deploys ztunnel and the Istio CNI rather than per-pod proxies.
Hands-on: canary routing with weighted traffic
Suppose you have a reviews service with two deployments labeled version: v1 and version: v2. First declare the subsets with a DestinationRule, then send 90% of traffic to v1 and 10% to v2 with a VirtualService.
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 90
- destination:
host: reviews
subset: v2
weight: 10
Apply it and watch the split take effect immediately:
kubectl apply -f reviews-canary.yaml
istioctl proxy-config routes deploy/reviews-v1 --name 9080 -o json
To promote v2, edit the weights to 0 and 100 and re-apply - no rebuild, no redeploy, just a config change that istiod sends to all the proxies. The same VirtualService can match on headers (for example, route internal testers by a x-user: beta header) so you can release a version to a small group of real users before everyone else.
When to use Istio
Istio is worth its cost once you have enough services that cross-cutting concerns become repetitive and risky to implement per app. Strong signals: you need mutual TLS everywhere for zero-trust networking, you want progressive delivery (canaries, blue-green, traffic mirroring) decoupled from deployments, or you need consistent metrics (latency, traffic, errors, saturation) and distributed traces across many teams without instrumenting each service by hand.
It is less compelling for a handful of services where a simpler ingress controller and library-level retries already do the job. The mesh adds operational surface - another control plane to upgrade, proxies to budget CPU and memory for, and a learning curve around its CRDs. Ambient mode narrows that cost considerably, so if sidecar overhead was your main objection, it is worth re-evaluating. In general, reach for Istio when uniform security, traffic control, and observability across a growing fleet of services matter more than the added complexity of running the mesh.
Frequently asked questions
What is Istio?
Istio is an open-source service mesh for Kubernetes. It places a programmable data plane between your services to handle traffic routing, mutual TLS, retries, and telemetry, all controlled by a central component called istiod. Applications get these features without code changes.
What is the difference between sidecar and ambient mode?
Sidecar mode injects an Envoy proxy container into every pod, giving full per-pod L7 control at the cost of extra resources. Ambient mode, GA since Istio 1.24, removes per-pod sidecars and uses a node-level ztunnel for L4 plus optional waypoint proxies for L7, lowering overhead and letting you adopt the mesh incrementally.
Do I need to change my application to use Istio?
No. Istio intercepts traffic transparently through the sidecar or ztunnel, so routing, mTLS, and telemetry work without modifying application code. You configure behavior with Kubernetes custom resources like VirtualService and DestinationRule instead.
What is the difference between a VirtualService and a DestinationRule?
A VirtualService defines routing rules - how requests are matched and which destination subsets receive them. A DestinationRule defines the subsets themselves (by pod labels) plus policies like load balancing, connection pools, and outlier detection applied after routing.
Do real production work, free. Live cloud workspaces in your browser - every project you complete lands on your portfolio.
Get started - free