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

URL: https://www.devopsness.com/blog/istio-getting-started
Author: Kiril Urbonas
Published: 2026-07-30
Updated: 2026-07-30
Categories: DevOps
Tags: Kubernetes, Tutorial, DevOps, Istio, Service Mesh

Summary: 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](/blog/service-mesh-guide). If you're still deciding on a mesh, the [Istio vs Linkerd](/blog/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
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
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
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
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
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: demo
spec:
  mtls:
    mode: STRICT
```

```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
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
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
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
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.

