DevOpsil
Kubernetes
90%
Needs Review

Istio Service Mesh: Installation, Traffic Management, and mTLS

Aareez AsifAareez Asif5 min read

What Problem Istio Solves

In a Kubernetes cluster without a service mesh, services communicate directly — no encryption, no circuit breaking, no traffic splitting, no visibility into what's talking to what. Every team must implement these in application code.

Istio solves this at the infrastructure layer:

  • mTLS everywhere: all service-to-service traffic encrypted and authenticated by default
  • Traffic management: canary deployments, retries, circuit breaking — in YAML, not code
  • Observability: distributed traces, per-service metrics, and service topology — automatic
  • Policy enforcement: rate limits and access control without application changes

The mechanism: Istio injects an Envoy sidecar container into every pod. All traffic flows through the sidecar, which Istio configures via its xDS control plane (Istiod).


Installation

Istio's recommended install method is istioctl:

# Download istioctl
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.24.0 sh -
export PATH="$PATH:$PWD/istio-1.24.0/bin"

# Verify
istioctl version

# Install with the default profile (good for production)
istioctl install --set profile=default -y

# Profiles available:
# minimal    — control plane only (no ingress gateway)
# default    — control plane + ingress gateway
# demo       — includes egress gateway, higher resource limits
# production — production-hardened settings

Verify installation:

kubectl get pods -n istio-system
# NAME                                   READY   STATUS    RESTARTS
# istiod-xxx                             1/1     Running   0
# istio-ingressgateway-xxx               1/1     Running   0

istioctl verify-install

Enabling Sidecar Injection

Istio injects sidecars automatically when the namespace is labeled:

# Enable injection for a namespace
kubectl label namespace default istio-injection=enabled

# Verify label
kubectl get namespace default --show-labels

# Restart existing pods to inject sidecars
kubectl rollout restart deployment -n default

# Verify sidecars are injected (pods should show 2/2 containers)
kubectl get pods -n default
# NAME                    READY   STATUS    RESTARTS
# myapp-xxx               2/2     Running   0    ← app + envoy sidecar

To check a specific pod:

kubectl describe pod myapp-xxx | grep -A5 "Containers:"
# Shows: myapp (your app) + istio-proxy (Envoy sidecar)

Automatic mTLS

Once sidecars are injected, Istio enables PERMISSIVE mode by default — accepts both plain and mTLS traffic. Switch to STRICT mode to require mTLS for all communication in a namespace:

# strict-mtls.yaml
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: default    # apply to entire namespace
spec:
  mtls:
    mode: STRICT
kubectl apply -f strict-mtls.yaml

# Verify mTLS is working
istioctl x describe pod myapp-xxx
# Should show: "mTLS: yes"

# Check from the Kiali dashboard (if installed) or via CLI
istioctl x authn tls-check myapp-xxx.default

After enabling STRICT mode, pods in default namespace can only receive traffic from other Istio-sidecar-injected pods with valid certificates.


VirtualService: Traffic Routing

VirtualService configures routing rules for traffic directed at a Kubernetes service:

# virtualservice.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: myapp
  namespace: default
spec:
  hosts:
    - myapp    # Kubernetes service name
  http:
    - match:
        - headers:
            x-canary:
              exact: "true"
      route:
        - destination:
            host: myapp
            subset: v2

    - route:
        - destination:
            host: myapp
            subset: v1
          weight: 90
        - destination:
            host: myapp
            subset: v2
          weight: 10

The subset references a DestinationRule:

# destinationrule.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: myapp
  namespace: default
spec:
  host: myapp
  subsets:
    - name: v1
      labels:
        version: v1    # matches pods with label version=v1
    - name: v2
      labels:
        version: v2
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        http1MaxPendingRequests: 100
        http2MaxRequests: 1000
    outlierDetection:
      consecutive5xxErrors: 5
      interval: 30s
      baseEjectionTime: 30s

Apply both:

kubectl apply -f destinationrule.yaml
kubectl apply -f virtualservice.yaml

Retries and Timeouts

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: myapp
spec:
  hosts:
    - myapp
  http:
    - route:
        - destination:
            host: myapp
      timeout: 30s
      retries:
        attempts: 3
        perTryTimeout: 10s
        retryOn: "5xx,gateway-error,connect-failure,retriable-4xx"

Ingress Gateway

The Istio Ingress Gateway replaces (or supplements) a Kubernetes Ingress for external traffic:

# gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: myapp-gateway
  namespace: default
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 443
        name: https
        protocol: HTTPS
      tls:
        mode: SIMPLE
        credentialName: myapp-tls    # Kubernetes TLS secret
      hosts:
        - app.example.com
    - port:
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - app.example.com
      tls:
        httpsRedirect: true    # redirect HTTP to HTTPS
# Bind VirtualService to Gateway
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: myapp
spec:
  hosts:
    - app.example.com
  gateways:
    - myapp-gateway
    - mesh           # also applies within the mesh
  http:
    - route:
        - destination:
            host: myapp
            port:
              number: 80

Observability

Install the Istio add-ons (telemetry stack):

# From the Istio installation directory
kubectl apply -f samples/addons/prometheus.yaml
kubectl apply -f samples/addons/grafana.yaml
kubectl apply -f samples/addons/kiali.yaml
kubectl apply -f samples/addons/jaeger.yaml

# Port-forward to access dashboards
kubectl port-forward -n istio-system svc/kiali 20001:20001
kubectl port-forward -n istio-system svc/grafana 3000:3000
kubectl port-forward -n istio-system svc/jaeger-query 16686:16686

Kiali shows the service topology graph — which services are talking to which, error rates, latency, and mTLS status.


Debugging

# Check Envoy proxy config for a pod
istioctl proxy-config all myapp-xxx.default

# Check routes
istioctl proxy-config route myapp-xxx.default

# Check clusters (upstreams)
istioctl proxy-config cluster myapp-xxx.default

# Check listeners
istioctl proxy-config listener myapp-xxx.default

# Analyze config for issues
istioctl analyze -n default

# Dump Envoy access log
kubectl logs myapp-xxx -c istio-proxy --tail=50

# Check service-to-service connectivity
kubectl exec -it myapp-xxx -- curl http://other-service/api/health

Common Issues

Pods stuck in 2/2 but sidecar not working: Check istioctl x authn tls-check — may be a namespace label issue.

Traffic not routed to VirtualService: Ensure the hosts in VirtualService matches the Kubernetes Service name exactly, and the Gateway is referenced correctly.

mTLS error "RBAC: access denied": An AuthorizationPolicy is denying the traffic. Check with:

istioctl x authz check myapp-xxx.default

High latency after Istio installation: The Envoy sidecar adds ~1ms per hop. For extremely latency-sensitive workloads, exclude the namespace from injection.

Share:

Was this article helpful?

Aareez Asif
Aareez Asif

Senior Kubernetes Architect

10+ years orchestrating containers in production. Battle-tested opinions on everything from pod scheduling to service mesh. I've seen clusters burn and helped rebuild them better.

Related Articles

More in Kubernetes

View all →

Discussion