Skip to main content
Getting Started with Istio: A Hands-On Tutorial (2026)

Getting Started with Istio: A Hands-On Tutorial (2026)

KU
• 6 min read•43 views

A practical walkthrough to install Istio, turn on automatic mTLS, and run a canary traffic split on Kubernetes.

Istio has a reputation for being heavy, and some of that is earned. But you don't need to understand every CRD to get real value out of it. This tutorial walks through the parts that actually matter on day one: getting the mesh installed, proving that mTLS is on, and shifting traffic between two versions of a service. Everything below is meant to be run, not just read.

Istio moves fast, so treat the commands here as a starting shape and verify against current Istio releases. Profile names, default versions, and Gateway API support all shift between minor versions.

If you want the bigger picture of where a mesh fits, start with our service mesh guide. If you're still deciding on a mesh, the Istio vs Linkerd comparison is worth a read before you commit.

Prerequisites

You need three things:

  • A running Kubernetes cluster. A local kind or minikube cluster with at least 4 GB of RAM works fine for this. Managed clusters (EKS, GKE, AKS) are all fine too.
  • kubectl configured and pointing at that cluster. Confirm with kubectl get nodes.
  • Cluster-admin permissions, since installing Istio creates cluster-scoped resources and CRDs.

That's it. No Helm required for the quick path, though I'll mention it.

Installing Istio

Download istioctl and put it on your PATH:

bash.bash
curl -L https://istio.io/downloadIstio | sh -
cd istio-*/
export PATH=$PWD/bin:$PATH
istioctl version

Now pick a profile. Istio has two data-plane modes worth knowing about:

  • Sidecar: the classic model. Every pod gets an Envoy proxy injected next to it. Mature, well understood, and what most tutorials assume.
  • Ambient: the newer sidecar-less mode. A per-node ztunnel handles L4 and mTLS, and you add waypoint proxies only where you need L7 features. Lower per-pod overhead, but newer.

We'll use the sidecar path here because it's the most portable across versions. Install the demo profile, which turns on the ingress gateway and generous telemetry:

bash.bash
istioctl install --set profile=demo -y
kubectl get pods -n istio-system

You should see istiod and istio-ingressgateway running. If you'd rather manage Istio through GitOps, the Helm charts (istio/base, istiod, istio/gateway) install the same components and are the better fit for production. The Gateway API path uses the same istioctl install but swaps Gateway/HTTPRoute resources for the older ingress gateway. Either works.

Enabling sidecar injection and deploying a sample app

Injection is opt-in per namespace. Label a namespace and Istio's mutating webhook adds the proxy to every new pod:

bash.bash
kubectl create namespace demo
kubectl label namespace demo istio-injection=enabled

Now deploy the Bookinfo sample that ships with the release. It's a small app with a productpage frontend and three versions of a reviews service, which makes it perfect for a traffic-split demo:

bash.bash
kubectl apply -n demo -f samples/bookinfo/platform/kube/bookinfo.yaml
kubectl get pods -n demo

Watch the READY column. Each pod should show 2/2 once it settles: one container for your app, one for the injected Envoy proxy. If you see 1/1, injection didn't happen, and we'll cover why below.

Verifying automatic mTLS

Here's the part people don't believe until they see it. Istio turns on mTLS between proxies automatically, in permissive mode by default, so plaintext still works during migration. To require it, apply a PeerAuthentication in STRICT mode:

yaml.yaml
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: demo
spec:
  mtls:
    mode: STRICT
bash.bash
kubectl apply -f peer-auth.yaml

Now every connection between meshed pods in demo is mutually authenticated and encrypted, and plaintext connections are rejected. You can confirm what a workload negotiated with:

bash.bash
istioctl x describe pod -n demo <productpage-pod-name>

Look for a line reporting that mTLS is enabled. This is the single highest-value thing Istio gives you for the least effort.

Traffic management: a 90/10 canary split

Bookinfo ships three versions of reviews. Without any rules, Kubernetes load-balances across all of them randomly. Let's take control and send 90% of traffic to v1 and 10% to v2.

First a DestinationRule to name the subsets, then a VirtualService to weight them:

yaml.yaml
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: reviews
  namespace: demo
spec:
  host: reviews
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: reviews
  namespace: demo
spec:
  hosts:
    - reviews
  http:
    - route:
        - destination:
            host: reviews
            subset: v1
          weight: 90
        - destination:
            host: reviews
            subset: v2
          weight: 10

Apply it, then refresh the product page a few dozen times. Roughly one in ten requests should hit v2. To promote the canary, bump v2 to weight: 100 and re-apply. No redeploys, no pod restarts.

If you're on the Gateway API path, the same split is expressed as an HTTPRoute with backendRefs carrying weight fields pointing at two Services. The mechanics are identical; only the resource shape differs.

Viewing telemetry

The demo profile makes wiring up dashboards trivial. Install the sample addons and open Kiali:

bash.bash
kubectl apply -f samples/addons/
istioctl dashboard kiali

Kiali gives you a live service-graph with request rates, error rates, and a lock icon on edges where mTLS is active. Prometheus scrapes the metrics, and Grafana has prebuilt Istio dashboards (istioctl dashboard grafana) for latency percentiles and throughput per workload. For a first look, drive some traffic to productpage and watch the graph in Kiali light up.

Common gotchas

  • Sidecar not injected: the most frequent issue. The namespace label must be present before pods start. If you labeled after deploying, restart the workloads: kubectl rollout restart deployment -n demo.
  • STRICT mTLS breaking plaintext clients: anything outside the mesh talking directly to a meshed pod on the plaintext port will get rejected under STRICT. Migrate with PERMISSIVE mode first, confirm all clients are meshed, then flip to STRICT.
  • Resource overhead: each sidecar adds CPU and memory. On dense clusters this adds up. This is exactly the pressure ambient mode is designed to relieve, so evaluate it if per-pod cost is a concern.

Uninstalling cleanly

Tear down in reverse order so you don't strand resources:

bash.bash
kubectl delete -f samples/addons/ --ignore-not-found
kubectl delete namespace demo
istioctl uninstall --purge -y
kubectl delete namespace istio-system

The --purge flag removes the CRDs too, so only use it when you truly want Istio gone.

Next steps

Once the basics click, the natural next moves are: fault injection and request timeouts (both live in VirtualService), authorization policies to lock down which services can call which, and multi-cluster mesh if you're spanning regions. If per-pod overhead bothered you, spend an afternoon with ambient mode on a throwaway cluster.

The call we'd make

For a first install, use the sidecar demo profile, prove mTLS with a STRICT PeerAuthentication, and run one canary split. That combination shows you the two things Istio is genuinely good at, security you didn't have to code and traffic control you didn't have to redeploy for, without drowning you in CRDs. Reach for ambient and the Gateway API path once the fundamentals feel routine, and keep validating every command against the release you actually run.

Explore topics:KubernetesDevOps
React
Share this post
KU

Kiril Urbonas

AI Engineer

560 articles
View all articles by Kiril Urbonas

You might have missed

Evergreen posts worth revisiting.