All Articles

How to Set Up Istio Service Mesh from Scratch (2026)

Step-by-step guide to installing and configuring Istio service mesh on Kubernetes. Covers traffic management, mTLS, observability, canary deployments, and production best practices.

DevOpsBoysMar 28, 20266 min read
Share:Tweet

Service mesh sounds complicated. And honestly, Istio has earned that reputation. But in 2026, Istio's ambient mesh mode has dramatically simplified things — no more sidecar proxies for basic features.

This guide takes you from a bare Kubernetes cluster to a fully functional Istio mesh with mTLS, traffic management, and observability.

Prerequisites

  • Kubernetes cluster 1.28+ (EKS, GKE, AKS, or local with kind/minikube)
  • kubectl configured
  • 4+ GB RAM available in the cluster
  • Helm 3.x installed

Step 1: Install Istio

bash
# Download istioctl
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.23.0 sh -
cd istio-1.23.0
export PATH=$PWD/bin:$PATH
 
# Install with the default profile
istioctl install --set profile=default -y

Verify:

bash
istioctl verify-install
kubectl get pods -n istio-system
NAME                                    READY   STATUS    RESTARTS   AGE
istiod-5b8c6f6d4f-x2k4m               1/1     Running   0          2m
istio-ingressgateway-7d9bc5f8b-9j3kl   1/1     Running   0          2m

Using Helm (For GitOps)

bash
helm repo add istio https://istio-release.storage.googleapis.com/charts
helm repo update
 
# Install base CRDs
helm install istio-base istio/base -n istio-system --create-namespace
 
# Install istiod (control plane)
helm install istiod istio/istiod -n istio-system --wait
 
# Install ingress gateway
helm install istio-ingress istio/gateway -n istio-system

Step 2: Enable Sidecar Injection

Istio works by injecting an Envoy sidecar proxy into each pod. Enable it per namespace:

bash
kubectl label namespace default istio-injection=enabled

Any new pods in the default namespace will automatically get the Envoy sidecar.

Verify after deploying an app:

bash
kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
my-app-7b4f6d8c9-x2k4m    2/2     Running   0          1m

Notice 2/2 — that's your app container plus the Envoy sidecar.

Step 3: Deploy a Sample Application

Let's deploy a multi-service app to demonstrate Istio's features:

yaml
# frontend.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  labels:
    app: frontend
    version: v1
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
        version: v1
    spec:
      containers:
        - name: frontend
          image: nginx:alpine
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: frontend
spec:
  selector:
    app: frontend
  ports:
    - port: 80
      targetPort: 80
---
# backend v1
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend-v1
  labels:
    app: backend
    version: v1
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend
      version: v1
  template:
    metadata:
      labels:
        app: backend
        version: v1
    spec:
      containers:
        - name: backend
          image: hashicorp/http-echo
          args: ["-text=Hello from backend v1"]
          ports:
            - containerPort: 5678
---
# backend v2
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend-v2
  labels:
    app: backend
    version: v2
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend
      version: v2
  template:
    metadata:
      labels:
        app: backend
        version: v2
    spec:
      containers:
        - name: backend
          image: hashicorp/http-echo
          args: ["-text=Hello from backend v2"]
          ports:
            - containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
  name: backend
spec:
  selector:
    app: backend
  ports:
    - port: 80
      targetPort: 5678
bash
kubectl apply -f frontend.yaml

Step 4: Traffic Management — Canary Deployments

Route 90% of traffic to backend v1, 10% to backend v2:

yaml
# destination-rule.yaml
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: backend
spec:
  host: backend
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2
---
# virtual-service.yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: backend
spec:
  hosts:
    - backend
  http:
    - route:
        - destination:
            host: backend
            subset: v1
          weight: 90
        - destination:
            host: backend
            subset: v2
          weight: 10
bash
kubectl apply -f destination-rule.yaml
kubectl apply -f virtual-service.yaml

Gradually shift traffic by updating the weights. Once v2 is validated, go to 100%:

yaml
http:
  - route:
      - destination:
          host: backend
          subset: v2
        weight: 100

Header-Based Routing

Route beta testers to v2:

yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: backend
spec:
  hosts:
    - backend
  http:
    - match:
        - headers:
            x-beta-user:
              exact: "true"
      route:
        - destination:
            host: backend
            subset: v2
    - route:
        - destination:
            host: backend
            subset: v1

Step 5: mTLS — Zero-Trust Networking

Istio can enforce mutual TLS between all services automatically. Enable strict mTLS:

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

Now all service-to-service communication in the default namespace is encrypted with mTLS. No certificate management needed — Istio handles certificate issuance, rotation, and validation automatically through its built-in CA.

Verify mTLS is working:

bash
istioctl x describe pod frontend-7b4f6d8c9-x2k4m
Pilot reports that pod is STRICT mTLS and target rules match:
  backend.default.svc.cluster.local:80 -> backend (STRICT mTLS)

Authorization Policies

Restrict which services can talk to each other:

yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: backend-policy
  namespace: default
spec:
  selector:
    matchLabels:
      app: backend
  action: ALLOW
  rules:
    - from:
        - source:
            principals:
              - "cluster.local/ns/default/sa/frontend"
      to:
        - operation:
            methods: ["GET"]
            paths: ["/api/*"]

Only the frontend service account can access backend, and only via GET requests to /api/*.

Step 6: Observability — Kiali, Jaeger, Prometheus

Install the observability addons:

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

Kiali — Service Mesh Dashboard

bash
istioctl dashboard kiali

Kiali shows:

  • Real-time service topology graph
  • Traffic flow with request rates
  • Error rates per service
  • mTLS status for all connections
  • Configuration validation

Jaeger — Distributed Tracing

bash
istioctl dashboard jaeger

Istio automatically adds trace headers to all requests. You can see the full request path across services without instrumenting your code.

Grafana — Metrics Dashboards

bash
istioctl dashboard grafana

Pre-built dashboards for:

  • Request volume, latency, and error rates (RED metrics)
  • TCP connection metrics
  • Control plane performance

Step 7: Expose Services with Istio Gateway

yaml
# gateway.yaml
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
  name: app-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - "app.example.com"
    - port:
        number: 443
        name: https
        protocol: HTTPS
      tls:
        mode: SIMPLE
        credentialName: app-tls-cert
      hosts:
        - "app.example.com"
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: app-routes
spec:
  hosts:
    - "app.example.com"
  gateways:
    - app-gateway
  http:
    - match:
        - uri:
            prefix: /api
      route:
        - destination:
            host: backend
            port:
              number: 80
    - route:
        - destination:
            host: frontend
            port:
              number: 80

Step 8: Fault Injection for Testing

Test your application's resilience:

yaml
# Inject 5-second delay for 10% of requests
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: backend-fault
spec:
  hosts:
    - backend
  http:
    - fault:
        delay:
          percentage:
            value: 10
          fixedDelay: 5s
      route:
        - destination:
            host: backend
yaml
# Return 503 for 5% of requests
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: backend-abort
spec:
  hosts:
    - backend
  http:
    - fault:
        abort:
          percentage:
            value: 5
          httpStatus: 503
      route:
        - destination:
            host: backend

Ambient Mesh — Istio Without Sidecars

Istio 1.22+ introduced ambient mesh mode, which removes the need for sidecar proxies:

bash
istioctl install --set profile=ambient -y

Instead of injecting sidecars into every pod, ambient mesh uses:

  • ztunnel — a per-node L4 proxy for mTLS and basic traffic management
  • waypoint proxies — optional L7 proxies deployed only where needed

Enable ambient mode for a namespace:

bash
kubectl label namespace default istio.io/dataplane-mode=ambient

Benefits:

  • No sidecar resource overhead (saves ~128 MB per pod)
  • No need to restart pods when upgrading Istio
  • Simpler debugging (your app only has 1 container)
  • L7 features available on-demand via waypoint proxies

Production Checklist

bash
# 1. Verify Istio health
istioctl analyze --all-namespaces
 
# 2. Check proxy sync status
istioctl proxy-status
 
# 3. Enable access logging
istioctl install --set meshConfig.accessLogFile=/dev/stdout
 
# 4. Set resource limits for sidecars
# In your IstioOperator or Helm values:
# proxy.resources.requests.cpu: 100m
# proxy.resources.requests.memory: 128Mi
 
# 5. Enable strict mTLS cluster-wide
kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT
EOF

Learn More

Istio has a steep learning curve but massive payoff for microservices architectures. For structured learning, KodeKloud's Kubernetes and service mesh courses provide hands-on labs where you can practice Istio configurations safely.

For running your own Istio-enabled cluster, DigitalOcean Kubernetes offers affordable managed clusters that work well for service mesh experimentation.


Istio isn't simple. But neither is managing security, traffic, and observability for dozens of microservices without it. Pick your complexity.

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