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.
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
defaultprofile - 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
kubectlconfigured and connected to your clusterhelmv3 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:
curl -L https://istio.io/downloadIstio | sh -
cd istio-*
export PATH=$PWD/bin:$PATHVerify the installation:
istioctl versionRun the pre-flight check to ensure your cluster is ready:
istioctl x precheckYou 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):
istioctl install --set profile=default -yVerify the installation:
kubectl get pods -n istio-systemYou 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:
kubectl create namespace demo
kubectl label namespace demo istio-injection=enabledVerify:
kubectl get namespace demo --show-labelsEvery 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:
# 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: 5678Apply it:
kubectl apply -f app-v1.yamlVerify that each pod has 2 containers (app + Envoy sidecar):
kubectl get pods -n demoNAME 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:
# 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: v2Create a VirtualService to route traffic — start with 100% to v1:
# 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: 0Apply both:
kubectl apply -f destination-rule.yaml
kubectl apply -f virtual-service.yamlStep 6: Canary Routing (Traffic Splitting)
Now shift 10% of traffic to v2 for canary testing:
# 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: 10kubectl apply -f virtual-service-canary.yamlTest the traffic split:
# 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; doneYou 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:
# peer-authentication.yaml
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: demo
spec:
mtls:
mode: STRICTkubectl apply -f peer-authentication.yamlVerify mTLS is active:
istioctl x describe pod $(kubectl get pod -n demo -l app=my-app,version=v1 -o jsonpath='{.items[0].metadata.name}') -n demoYou should see "mTLS is STRICT" in the output.
To enforce mTLS mesh-wide (all namespaces):
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICTStep 8: Set Up Observability
Install Kiali (service mesh dashboard), Jaeger (tracing), and Grafana (metrics):
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.yamlWait for pods to be ready:
kubectl get pods -n istio-system -wAccess Kiali Dashboard
istioctl dashboard kialiKiali 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)
istioctl dashboard jaegerJaeger captures distributed traces across your services. Every request that flows through the Envoy sidecars is automatically traced — no code changes needed.
Access Grafana
istioctl dashboard grafanaIstio 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:
# 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: 10kubectl apply -f gateway.yamlGet the external IP:
kubectl get svc istio-ingressgateway -n istio-systemProduction Tips
1. Use the minimal profile for production and only enable what you need:
istioctl install --set profile=minimal \
--set meshConfig.accessLogFile=/dev/stdout \
--set meshConfig.enableTracing=true2. Set resource limits on sidecars to prevent Envoy from consuming too much memory:
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: 256Mi3. Enable access logging for debugging:
istioctl install --set meshConfig.accessLogFile=/dev/stdout4. 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.
kubectl label namespace kube-system istio-injection=disabledIf 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.
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
Cilium Complete Guide: eBPF-Powered Kubernetes Networking and Security in 2026
Master Cilium — the eBPF-based CNI that's become the default for Kubernetes networking. Covers installation, network policies, Hubble observability, and service mesh mode.
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.
How to Set Up Tailscale for Zero-Trust Access to Your DevOps Infrastructure
Step-by-step guide to setting up Tailscale for secure access to Kubernetes clusters, databases, and internal tools without traditional VPNs.