All Articles

How to Set Up Istio Service Mesh on Kubernetes from Scratch in 2026

Step-by-step guide to installing and configuring Istio service mesh on Kubernetes — traffic management, mTLS, observability, and canary routing with practical examples.

DevOpsBoysMar 24, 20266 min read
Share:Tweet

By the end of this guide, you will have Istio running on your Kubernetes cluster with mutual TLS encryption, traffic routing, canary deployments, and full observability — all from scratch.

What You Will Build

  • Istio service mesh installed with the default profile
  • Automatic sidecar injection for your application namespace
  • A sample application with two versions for canary testing
  • Mutual TLS (mTLS) enforced across all services
  • Weight-based canary routing (90/10 traffic split)
  • Kiali dashboard for service mesh visualization
  • Jaeger for distributed tracing
  • Grafana dashboards for mesh metrics

Prerequisites

  • A Kubernetes cluster (1.27+) with at least 4 GB RAM available
  • kubectl configured and connected to your cluster
  • helm v3 installed (for addons)

If you need a cluster to practice on, DigitalOcean's managed Kubernetes (DOKS) gives you a production-ready cluster in under 5 minutes.

Step 1: Install istioctl

Download the latest Istio release:

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

Verify the installation:

bash
istioctl version

Run the pre-flight check to ensure your cluster is ready:

bash
istioctl x precheck

You should see "No issues found when checking the cluster."

Step 2: Install Istio on Your Cluster

Install Istio with the default profile (includes istiod control plane and an ingress gateway):

bash
istioctl install --set profile=default -y

Verify the installation:

bash
kubectl get pods -n istio-system

You should see:

NAME                                    READY   STATUS    RESTARTS
istiod-xxxxxxxxx-xxxxx                  1/1     Running   0
istio-ingressgateway-xxxxxxxxx-xxxxx    1/1     Running   0

Step 3: Enable Automatic Sidecar Injection

Label your application namespace so Istio automatically injects the Envoy sidecar proxy into every pod:

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

Verify:

bash
kubectl get namespace demo --show-labels

Every pod deployed to the demo namespace will now get an Envoy sidecar automatically.

Step 4: Deploy a Sample Application

Deploy two versions of a simple app to test traffic routing:

yaml
# app-v1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v1
  namespace: demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-app
      version: v1
  template:
    metadata:
      labels:
        app: my-app
        version: v1
    spec:
      containers:
        - name: my-app
          image: hashicorp/http-echo
          args: ["-text=v1 - stable"]
          ports:
            - containerPort: 5678
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v2
  namespace: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
      version: v2
  template:
    metadata:
      labels:
        app: my-app
        version: v2
    spec:
      containers:
        - name: my-app
          image: hashicorp/http-echo
          args: ["-text=v2 - canary"]
          ports:
            - containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
  name: my-app
  namespace: demo
spec:
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 5678

Apply it:

bash
kubectl apply -f app-v1.yaml

Verify that each pod has 2 containers (app + Envoy sidecar):

bash
kubectl get pods -n demo
NAME                         READY   STATUS    RESTARTS
my-app-v1-xxxxxxxxx-xxxxx   2/2     Running   0
my-app-v1-xxxxxxxxx-yyyyy   2/2     Running   0
my-app-v2-xxxxxxxxx-xxxxx   2/2     Running   0

The 2/2 confirms the sidecar is injected.

Step 5: Configure Traffic Management

Create a DestinationRule to define subsets based on version labels:

yaml
# destination-rule.yaml
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: my-app
  namespace: demo
spec:
  host: my-app
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2

Create a VirtualService to route traffic — start with 100% to v1:

yaml
# virtual-service.yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: my-app
  namespace: demo
spec:
  hosts:
    - my-app
  http:
    - route:
        - destination:
            host: my-app
            subset: v1
          weight: 100
        - destination:
            host: my-app
            subset: v2
          weight: 0

Apply both:

bash
kubectl apply -f destination-rule.yaml
kubectl apply -f virtual-service.yaml

Step 6: Canary Routing (Traffic Splitting)

Now shift 10% of traffic to v2 for canary testing:

yaml
# virtual-service-canary.yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: my-app
  namespace: demo
spec:
  hosts:
    - my-app
  http:
    - route:
        - destination:
            host: my-app
            subset: v1
          weight: 90
        - destination:
            host: my-app
            subset: v2
          weight: 10
bash
kubectl apply -f virtual-service-canary.yaml

Test the traffic split:

bash
# From inside the cluster
kubectl run -n demo curl --image=curlimages/curl --rm -it -- sh
for i in $(seq 1 20); do curl -s my-app; echo; done

You should see roughly 18 responses from v1 and 2 from v2. Gradually increase the v2 weight as confidence grows — 10, 25, 50, 100.

Step 7: Enable Mutual TLS (mTLS)

Enforce mTLS across the entire mesh so all service-to-service traffic is encrypted:

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

Verify mTLS is active:

bash
istioctl x describe pod $(kubectl get pod -n demo -l app=my-app,version=v1 -o jsonpath='{.items[0].metadata.name}') -n demo

You should see "mTLS is STRICT" in the output.

To enforce mTLS mesh-wide (all namespaces):

yaml
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT

Step 8: Set Up Observability

Install Kiali (service mesh dashboard), Jaeger (tracing), and Grafana (metrics):

bash
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/kiali.yaml
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/jaeger.yaml
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/grafana.yaml
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/prometheus.yaml

Wait for pods to be ready:

bash
kubectl get pods -n istio-system -w

Access Kiali Dashboard

bash
istioctl dashboard kiali

Kiali shows you a real-time graph of your service mesh — which services talk to which, traffic rates, error rates, and mTLS status. It is the best way to visualize what Istio is doing.

Access Jaeger (Distributed Tracing)

bash
istioctl dashboard jaeger

Jaeger captures distributed traces across your services. Every request that flows through the Envoy sidecars is automatically traced — no code changes needed.

Access Grafana

bash
istioctl dashboard grafana

Istio ships with pre-built Grafana dashboards for mesh traffic, control plane performance, and Envoy stats.

Step 9: Expose Your App with Istio Gateway

To expose your application externally through Istio's ingress gateway:

yaml
# gateway.yaml
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
  name: my-app-gateway
  namespace: demo
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - "my-app.example.com"
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: my-app-external
  namespace: demo
spec:
  hosts:
    - "my-app.example.com"
  gateways:
    - my-app-gateway
  http:
    - route:
        - destination:
            host: my-app
            subset: v1
          weight: 90
        - destination:
            host: my-app
            subset: v2
          weight: 10
bash
kubectl apply -f gateway.yaml

Get the external IP:

bash
kubectl get svc istio-ingressgateway -n istio-system

Production Tips

1. Use the minimal profile for production and only enable what you need:

bash
istioctl install --set profile=minimal \
  --set meshConfig.accessLogFile=/dev/stdout \
  --set meshConfig.enableTracing=true

2. Set resource limits on sidecars to prevent Envoy from consuming too much memory:

yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  meshConfig:
    defaultConfig:
      concurrency: 2
  values:
    global:
      proxy:
        resources:
          requests:
            cpu: 50m
            memory: 64Mi
          limits:
            cpu: 200m
            memory: 256Mi

3. Enable access logging for debugging:

bash
istioctl install --set meshConfig.accessLogFile=/dev/stdout

4. Use PeerAuthentication in PERMISSIVE mode first when migrating existing services, then switch to STRICT once everything is confirmed working.

5. Monitor the control plane — Istiod has its own metrics. Set up alerts for pilot errors, push failures, and configuration rejection counts.

6. Exclude namespaces that do not need the mesh — kube-system, monitoring, and CI/CD namespaces usually should not have sidecars.

bash
kubectl label namespace kube-system istio-injection=disabled

If you want hands-on practice with Istio, service mesh patterns, and Kubernetes networking, the courses at KodeKloud cover these topics with interactive labs where you build real mesh configurations step by step.

Wrapping Up

Istio gives you traffic management, security (mTLS), and observability without changing your application code. The trade-off is complexity — the Envoy sidecars add latency and resource overhead. But for microservices architectures with more than a handful of services, the visibility and control you get is worth it.

Start with the basics — sidecar injection, mTLS, and Kiali. Add traffic management and canary routing when you need them. The key is to adopt Istio incrementally, not all at once.

Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments