Overview
Istio is a service mesh that provides traffic management, security (mTLS, authorization), and observability for microservices running on Kubernetes. It works by injecting an Envoy sidecar proxy into every pod and managing those proxies from a centralized control plane called istiod. The application code requires zero changes β all networking concerns (retries, timeouts, mutual TLS, traffic splitting) are handled transparently by the proxy layer.
This note covers the core architecture: control plane vs data plane, xDS protocol, sidecar injection, iptables traffic interception, the end-to-end request lifecycle, ambient mode, common gotchas, and debugging.
For detailed topic-specific notes, see:
- Istio Envoy Internals β threading model, hot restart, filter pipeline, connection pooling, health checking, access logging
- Istio Traffic Management β VirtualService, DestinationRule, Gateway API, traffic splitting
- Istio Security β mTLS, SPIFFE, PeerAuthentication, AuthorizationPolicy, JWT, ext_authz
- Istio Observability & Extensibility β metrics, tracing, Kiali, WasmPlugin, EnvoyFilter, Telemetry API
For Kubernetes networking fundamentals including kube-proxy, iptables, and ClusterIP routing, see Docker Proxy Networking in K8s.
Architecture: Control Plane vs Data Plane
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CONTROL PLANE β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β istiod β β
β β β β
β β ββββββββββββ ββββββββββββ ββββββββββββ βββββββββββββββββ β β
β β β Pilot β β Citadel β β Galley β β xDS Server β β β
β β β (config β β (CA / β β (config β β (push config β β β
β β β trans- β β cert β β valida- β β to proxies) β β β
β β β lation) β β mgmt) β β tion) β β β β β
β β ββββββββββββ ββββββββββββ ββββββββββββ βββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββ β
β β xDS (gRPC) β
β β SDS (certs) β
βββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ
β DATA PLANE β β
β βΌ β
β ββββ Pod A βββββββββββββββ ββββ Pod B βββββββββββββββ β
β β ββββββββ βββββββββββ β β ββββββββ βββββββββββ β β
β β β App β β istio- β β β β App β β istio- β β β
β β β ββββ€ proxy β β β β ββββ€ proxy β β β
β β β β β (envoy β β β β β β (envoy β β β
β β β β β + pilotβ β β β β β + pilotβ β β
β β β β β -agent)β β β β β β -agent)β β β
β β ββββββββ βββββββββββ β β ββββββββ βββββββββββ β β
β ββββββββββββββββββββββββββ ββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
istiod (Control Plane)
istiod is a single Go binary that consolidates what were historically separate components (Pilot, Citadel, Galley) into one process. Its responsibilities:
| Component | Responsibility |
|---|---|
| Pilot | Watches Kubernetes API for Services, Endpoints, VirtualService, DestinationRule. Translates them into Envoy configuration and pushes via xDS. |
| Citadel (CA) | Acts as a Certificate Authority. Issues SPIFFE X.509 certificates to workloads, handles rotation. Delivers certs via SDS. |
| Galley | Validates Istio custom resource configurations before they are accepted by the API server. |
| xDS Server | Maintains gRPC connections to every Envoy proxy. Pushes configuration updates (listeners, routes, clusters, endpoints, secrets) in real time. |
istiod is stateless by design β it reconstructs all state from the Kubernetes API server on startup. Running multiple replicas provides high availability.
Data Plane (Envoy Sidecars)
Every meshed pod runs an istio-proxy container alongside the application container. This container holds two processes:
ββββ istio-proxy container ββββββββββββββββββββββββ
β β
β βββββββββββββββ βββββββββββββββββββββββββ β
β β pilot-agent ββββββββΆβ Envoy β β
β β β β β β
β β - bootstrap β β - L4/L7 proxy β β
β β generation β β - mTLS termination β β
β β - cert fetch β β - load balancing β β
β β via SDS β β - retries/timeouts β β
β β - health β β - metrics (port 15090) β β
β β checks β β - admin API (port 15000) β β
β β - envoy β β β β
β β lifecycle β β β β
β βββββββββββββββ βββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββ
pilot-agent (not to be confused with Pilot in istiod):
- Generates the Envoy bootstrap configuration at startup
- Implements the SDS server locally β fetches certificates from istiod and serves them to Envoy over a Unix domain socket
- Manages the Envoy process lifecycle (starts, drains, restarts on crash)
- Serves the health check endpoint on port 15021
For deep dive into Envoyβs internal architecture (threading, filters, connection pooling), see Istio Envoy Internals.
Well-Known Ports
| Port | Protocol | Owner | Purpose |
|---|---|---|---|
| 15001 | TCP | Envoy | VirtualOutbound listener β captures all outbound traffic |
| 15006 | TCP | Envoy | VirtualInbound listener β captures all inbound traffic |
| 15000 | HTTP | Envoy | Admin interface (/config_dump, /clusters, /stats) |
| 15004 | gRPC | pilot-agent | Debug interface |
| 15010 | gRPC | istiod | xDS (plaintext, for testing) |
| 15012 | gRPC | istiod | xDS over mTLS (production) |
| 15014 | HTTP | istiod | Control plane metrics and debug |
| 15017 | HTTPS | istiod | Webhook server (sidecar injection, config validation) |
| 15020 | HTTP | pilot-agent | Merged Prometheus metrics + health |
| 15021 | HTTP | pilot-agent | Health check endpoint (/healthz/ready) |
| 15053 | DNS | pilot-agent | Istio DNS proxy (captures DNS queries) |
| 15090 | HTTP | Envoy | Prometheus metrics endpoint (/stats/prometheus) |
xDS Protocol Deep Dive
xDS (Extensible Discovery Service) is the gRPC-based protocol Envoy uses to receive dynamic configuration from a management server (istiod). Instead of static config files, every Envoy proxy maintains a persistent gRPC stream to istiod and receives configuration updates in real time.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β istiod β
β β
β K8s API Watch βββΊ Translation Engine βββΊ xDS Server β
β β β
β VirtualService βββΊ RDS routes β β
β DestinationRule βββΊ CDS clusters β β
β Service/Endpoints βββΊ CDS + EDS β β
β PeerAuthentication βββΊ LDS + filter chains β β
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββ
β gRPC stream (ADS)
β port 15012 (mTLS)
ββββββββββββΌβββββββββββ
βΌ βΌ βΌ
Envoy A Envoy B Envoy C
The Six xDS APIs
| xDS API | Full Name | What It Configures | Envoy Concept |
|---|---|---|---|
| LDS | Listener Discovery Service | How to accept connections β bind address, port, filter chains | listener |
| RDS | Route Discovery Service | How to route HTTP requests β virtual hosts, route match rules, rewrites | route_configuration |
| CDS | Cluster Discovery Service | Upstream service groups β load balancing policy, circuit breaker settings, TLS context | cluster |
| EDS | Endpoint Discovery Service | Actual IP:port of healthy pods backing a cluster β locality, weight, health status | cluster_load_assignment |
| SDS | Secret Discovery Service | TLS certificates and private keys for mTLS, trusted CA bundles | secret |
| ADS | Aggregated Discovery Service | Not a separate resource type β a single gRPC stream that multiplexes all of the above, ensuring ordering guarantees | (transport mechanism) |
xDS Update Flow
K8s Event (e.g., new Pod becomes Ready)
β
βΌ
istiod watches K8s API server
β
βΌ
istiod translates to Envoy config
β
βΌ
istiod pushes via xDS (over existing gRPC stream)
β
ββββΊ LDS push: new filter chain for the service
ββββΊ RDS push: updated route if VirtualService changed
ββββΊ CDS push: updated cluster config
ββββΊ EDS push: new endpoint added to cluster
ββββΊ SDS push: certificate rotation
β
βΌ
Envoy applies config in-memory (hot reload, no restart)
Ordering Guarantees
When using ADS (which Istio always does), updates must follow a safe ordering to avoid traffic blackholes:
CDS βββΊ EDS βββΊ LDS βββΊ RDS
Why this order:
1. CDS first: Create the cluster definition
2. EDS second: Populate it with endpoints (so it's not empty)
3. LDS third: Create the listener that will route to the cluster
4. RDS last: Add routes pointing to the now-populated cluster
If LDS arrived before CDS/EDS, Envoy would have a route
pointing to a cluster that doesn't exist yet β 503 errors.
State of the World (SotW) vs Delta xDS
| Variant | Description | Trade-off |
|---|---|---|
| SotW | Every push sends the complete set of resources of that type | Simple but wasteful at scale β 1 new pod = full EDS push for all endpoints |
| Delta (Incremental) | Only changed resources are sent | Much more efficient for large meshes. Default since Istio 1.22 |
| Delta ADS | Single gRPC stream with incremental updates for all resource types | Best of both worlds β ordering guarantees + efficiency |
Sidecar Injection
Istio injects the sidecar proxy into pods automatically using a Kubernetes Mutating Admission Webhook. No changes to Deployments or Pod specs are needed β the injection happens at pod creation time.
kubectl apply -f deployment.yaml
β
βΌ
K8s API Server receives Pod creation request
β
βΌ
API Server calls Mutating Admission Webhooks
β
βΌ
istiod webhook (port 15017) intercepts the request
β
βΌ
istiod checks namespace label: istio-injection=enabled ?
β
βββ No β Pod created as-is (no sidecar)
β
βββ Yes β istiod mutates the Pod spec:
β
βββ Adds init container: istio-init
β (sets up iptables rules)
β
βββ Adds sidecar container: istio-proxy
β (Envoy + pilot-agent)
β
βββ Adds volumes:
β - istio-envoy (emptyDir for config)
β - istio-data (emptyDir for SDS socket)
β - istio-token (projected SA token)
β
βββ Returns mutated Pod spec to API server
β
βΌ
API Server creates the mutated Pod
β
βΌ
kubelet starts containers in order:
1. istio-init (runs iptables setup, exits)
2. istio-proxy + app container (run concurrently)
The istio-init Container
The istio-init container runs the istio-iptables binary with these key arguments:
istio-iptables \
-p 15001 \ # Outbound capture port (VirtualOutbound)
-z 15006 \ # Inbound capture port (VirtualInbound)
-u 1337 \ # UID of the istio-proxy process (excluded from capture)
-m REDIRECT \ # iptables mode (REDIRECT or TPROXY)
-i '*' \ # Include all outbound IPs for capture
-x "" \ # Exclude no IPs
-b '*' \ # Include all inbound ports for capture
-d 15090,15021,15020 # Exclude these inbound ports from captureIt runs with NET_ADMIN capability (or is replaced by the Istio CNI plugin which runs at the node level, eliminating the need for NET_ADMIN in the pod).
Traffic Interception with iptables
The istio-init container creates custom chains in the NAT table that transparently redirect all TCP traffic through Envoy. This is how Istio achieves zero-code-change traffic capture.
iptables Chains and Rules
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β NAT TABLE β
β β
β PREROUTING chain (inbound traffic entering the pod) β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β -j ISTIO_INBOUND β β
β βββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ β
β βΌ β
β ISTIO_INBOUND chain β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β -p tcp --dport 15008 -j RETURN (HBONE passthrough) β β
β β -p tcp --dport 15090 -j RETURN (Prometheus metrics) β β
β β -p tcp --dport 15021 -j RETURN (health check) β β
β β -p tcp --dport 15020 -j RETURN (merged metrics) β β
β β -p tcp -j ISTIO_IN_REDIRECT (everything else) β β
β βββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ β
β βΌ β
β ISTIO_IN_REDIRECT chain β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β -p tcp -j REDIRECT --to-port 15006 β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β β
β OUTPUT chain (outbound traffic leaving from app) β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β -j ISTIO_OUTPUT β β
β βββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ β
β βΌ β
β ISTIO_OUTPUT chain β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β -s 127.0.0.6/32 -j RETURN (Envoy inboundβapp) β β
β β -m owner --uid-owner 1337 -j RETURN (Envoy's own traffic)β β
β β -m owner --gid-owner 1337 -j RETURN (Envoy's own traffic)β β
β β -d 127.0.0.1/32 -j RETURN (localhost traffic) β β
β β -j ISTIO_REDIRECT (everything else) β β
β βββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ β
β βΌ β
β ISTIO_REDIRECT chain β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β -p tcp -j REDIRECT --to-port 15001 β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
How the Loop is Avoided
The critical trick is the UID 1337 check. The Envoy process runs as user ID 1337. When Envoy sends a packet (after processing), the OUTPUT chain sees --uid-owner 1337 and returns immediately, letting the packet go directly to the network stack. Without this, Envoyβs outbound traffic would be redirected back to itself infinitely.
App sends packet (uid != 1337)
β
βΌ
OUTPUT β ISTIO_OUTPUT β not uid 1337 β ISTIO_REDIRECT β port 15001
β
βΌ
Envoy processes, sends packet (uid == 1337)
β
βΌ
OUTPUT β ISTIO_OUTPUT β uid 1337 β RETURN β packet goes to network
Note: Never run your application container as UID 1337. If you do, all your appβs outbound traffic will bypass the sidecar entirely because iptables will think it is Envoy traffic.
Viewing the iptables Rules
# From inside the sidecar container:
kubectl exec deploy/my-app -c istio-proxy -- iptables -t nat -S
# Or using nsenter from the node:
nsenter -t <PID> -n iptables -t nat -L -vAlternative: Istio CNI Plugin
The Istio CNI plugin moves iptables setup from the istio-init container to a node-level DaemonSet. Benefits:
- Pods no longer need
NET_ADMINorNET_RAWcapabilities - Eliminates the init container race condition (see Gotchas section)
- Required for ambient mode
Request Lifecycle End-to-End
A complete request from Service A to Service B in an Istio mesh traverses the following path:
Service A Pod Service B Pod
ββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββ
β β β β
β βββββββ ββββββββββββ β Network β ββββββββββββ βββββββ β
β β App β β Envoy β β β β Envoy β β App β β
β β A β β (sidecar)β β β β (sidecar)β β B β β
β ββββ¬βββ ββββββ¬ββββββ β β ββββββ¬ββββββ ββββ¬βββ β
β β β β β β β β
βββββββΌββββββββββββΌβββββββββ ββββββββΌβββββββββββββΌββββββ
β β β β
β Step 1 β Step 3 Step 5 β Step 7 β
βΌ βΌ βΌ βΌ
Step 1: App A sends HTTP request to reviews:8080
(App thinks it's connecting directly to reviews service)
Step 2: Kernel intercepts via iptables OUTPUT chain
β ISTIO_OUTPUT β ISTIO_REDIRECT β port 15001
Packet redirected to Envoy outbound listener
Step 3: Envoy outbound processing:
a. Listener 15001 (VirtualOutbound) inspects original dest
b. Routes to the correct cluster based on Host header / SNI
Cluster name: "outbound|8080||reviews.default.svc.cluster.local"
c. Load balancer selects endpoint (e.g., 10.48.2.15:8080)
d. Applies DestinationRule: retries, timeouts, circuit breaking
e. Initiates mTLS handshake with destination Envoy
(presents SPIFFE cert, verifies peer cert)
f. Sends request over encrypted connection
Step 4: Packet travels over pod network (CNI, possibly across nodes)
Source: Pod A IP β Dest: Pod B IP (10.48.2.15:8080)
Step 5: Packet arrives at Pod B's network namespace
Kernel intercepts via iptables PREROUTING chain
β ISTIO_INBOUND β ISTIO_IN_REDIRECT β port 15006
Redirected to Envoy inbound listener
Step 6: Envoy inbound processing:
a. Listener 15006 (VirtualInbound) inspects original dest port
b. Selects filter chain matching port 8080
c. Terminates mTLS, validates peer SPIFFE identity
d. Applies AuthorizationPolicy (RBAC check)
e. Applies any inbound traffic policies
Step 7: Envoy forwards to localhost:8080 (the actual app)
(Envoy connects to 127.0.0.1:8080 as uid 1337,
bypassing iptables redirect)
Step 8: App B processes request, sends response
Response follows the reverse path through both Envoys
Detailed Packet Walk (Kernel Level)
App A: connect(fd, "10.96.5.100:8080") β ClusterIP of "reviews" service
β
βΌ
Kernel: TCP SYN packet created
src=10.48.1.5:49152 dst=10.96.5.100:8080
β
βΌ
iptables NAT OUTPUT: matches ISTIO_OUTPUT
Not uid 1337 β ISTIO_REDIRECT
REDIRECT to 127.0.0.1:15001
(original dst 10.96.5.100:8080 saved in conntrack via SO_ORIGINAL_DST)
β
βΌ
Envoy (port 15001): accepts connection
getsockopt(SO_ORIGINAL_DST) β 10.96.5.100:8080
Looks up route for "reviews" β cluster has endpoints via EDS
Selects endpoint 10.48.2.15:8080
β
βΌ
Envoy: connect(fd, "10.48.2.15:8080") β Direct pod IP
(as uid 1337 β bypasses iptables)
β
βΌ
Kernel: TCP SYN packet created
src=10.48.1.5:49200 dst=10.48.2.15:8080
β kube-proxy / CNI routes to Pod B's node
β
βΌ
Pod B kernel: packet arrives
iptables NAT PREROUTING: matches ISTIO_INBOUND
Port 8080 not excluded β ISTIO_IN_REDIRECT
REDIRECT to 127.0.0.1:15006
β
βΌ
Envoy (port 15006): accepts connection
getsockopt(SO_ORIGINAL_DST) β 10.48.2.15:8080
Selects filter chain for port 8080
Terminates mTLS, runs HTTP filters
β
βΌ
Envoy: connect(fd, "127.0.0.1:8080")
(as uid 1337 β bypasses iptables)
β
βΌ
App B: accepts connection on 0.0.0.0:8080
Istio Ambient Mode (ztunnel + Waypoint Proxy)
Ambient mode is a sidecar-less data plane architecture introduced to address the resource overhead and operational complexity of sidecar injection. It splits mesh functionality into two layers:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AMBIENT MODE ARCHITECTURE β
β β
β ββββ Node 1 ββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β β
β β βββββββββββ βββββββββββ βββββββββββ β β
β β β Pod A β β Pod B β β Pod C β β no sidecars! β β
β β β (app β β (app β β (app β β β
β β β only) β β only) β β only) β β β
β β ββββββ¬βββββ ββββββ¬βββββ ββββββ¬βββββ β β
β β β β β β β
β β ββββββββββββββΌβββββββββββββ β β
β β β β β
β β βββββββ΄ββββββ β β
β β β ztunnel β β DaemonSet, one per node β β
β β β (Rust, β L4: mTLS, L4 authz, telemetry β β
β β β L3/L4) β β β
β β βββββββ¬ββββββ β β
β ββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββ β
β β β
β β HBONE tunnel (HTTP CONNECT over mTLS) β
β β β
β ββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββ β
β β βΌ β β
β β ββββββββββββββββββββββββββ (optional, per-namespace) β β
β β β Waypoint Proxy β L7: HTTP routing, authz, β β
β β β (Envoy, Deployment) β retries, fault injection, β β
β β β β traffic splitting β β
β β ββββββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Mechanism β What for β
βββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β UDS β Pass the namespace fd (one-time control plane handoff) β
βββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β TCP sockets bound via setns() β Ztunnel's listening ports inside pod's netns (data plane capture) β
βββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Veth + IP networking β Actual traffic between pods/nodes β
βββββββββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ztunnel (Zero Trust Tunnel)
- Written in Rust (not Envoy) β purpose-built for L4 only
- Deployed as a DaemonSet on every node
- Handles: mTLS encryption/decryption, L4 authorization, TCP telemetry
- Uses HBONE (HTTP-Based Overlay Network Encapsulation) to tunnel traffic between ztunnels via HTTP CONNECT over mTLS with HTTP/2 multiplexing
- Creates listening sockets inside each podβs network namespace via the Istio CNI agent (no iptables REDIRECT needed for interception)
Waypoint Proxy
- Standard Envoy proxy deployed as a Kubernetes Deployment (not a sidecar)
- Deployed per namespace or per service (not per pod)
- Only needed when L7 features are required (HTTP routing, header-based authz, retries, fault injection)
- If no waypoint is deployed, traffic flows directly between ztunnels (L4 only)
Traffic Flow in Ambient Mode
Without waypoint (L4 only):
Pod A β ztunnel (node A) ββHBONEβββΊ ztunnel (node B) β Pod B
With waypoint (L7 processing):
Pod A β ztunnel (node A) ββHBONEβββΊ Waypoint ββHBONEβββΊ ztunnel (node B) β Pod B
Sidecar vs Ambient Comparison
| Aspect | Sidecar Mode | Ambient Mode |
|---|---|---|
| Proxy per pod | Yes (Envoy sidecar) | No (shared ztunnel per node) |
| Resource overhead | High (Envoy per pod) | Low (90%+ memory reduction reported) |
| L7 features | Always available | Only with waypoint proxy |
| Injection mechanism | Mutating webhook + init container | Istio CNI agent |
| App restart needed to mesh | Yes (pod recreated with sidecar) | No (label the namespace) |
| Traffic interception | iptables REDIRECT | ztunnel sockets in pod netns |
| mTLS | Per-sidecar certificates | Per-ztunnel (shared node identity) |
Common Gotchas
Port Naming Requirements
Istio uses Kubernetes service port names to detect the protocol. If ports are not named with a recognized prefix, Istio treats the traffic as opaque TCP and cannot apply HTTP-level features.
# Correct -- Istio detects HTTP
ports:
- name: http-web # prefix "http-" or "http2-" or "grpc-"
port: 8080
targetPort: 8080
# Incorrect -- treated as opaque TCP, no HTTP routing/retries
ports:
- name: web
port: 8080
targetPort: 8080Recognized prefixes: http, http2, https, grpc, grpc-web, mongo, mysql, redis, tcp, tls, udp.
Alternatively, set the appProtocol field (Kubernetes 1.19+):
ports:
- name: web
port: 8080
appProtocol: http # Istio reads thisProtocol Detection (Auto-Detection)
When no protocol hint is available, Envoyβs HTTP inspector filter sniffs the first bytes of the connection to determine if itβs HTTP. This adds a small latency (detection timeout, default 5s for server-first protocols). For server-first protocols like MySQL, the connection stalls during detection because the server speaks first but Envoy is waiting for client bytes.
App Binding to localhost vs 0.0.0.0
If your application binds to 127.0.0.1 instead of 0.0.0.0, the Envoy sidecar cannot reach it. Envoy forwards inbound traffic to 127.0.0.1:<app-port>, but the kernel only delivers to localhost listeners if the app binds to 0.0.0.0 or 127.0.0.1. In sidecar mode, this works because Envoy connects via localhost. But if you configure the app to listen on localhost only and also have external health checks, those external checks will fail.
Note: Starting with Istio 1.10+, the
ISTIO_META_LOCALHOST_AUTODETECTfeature and changes in VirtualInbound behavior improved localhost handling. However, binding to0.0.0.0remains the safest practice.
Init Container Race Conditions
The istio-init container must complete before the app container starts, but the istio-proxy (sidecar) container starts concurrently with the app. If the app starts faster than Envoy and immediately sends traffic, iptables will redirect it to port 15001 where Envoy is not yet listening, causing connection failures.
Mitigations:
- Use
holdApplicationUntilProxyStarts: truein the mesh config (adds a postStart hook to wait for Envoy readiness) - Use the Istio CNI plugin (eliminates the init container entirely)
istiod Unavailability
If istiod goes down:
- Existing proxies continue working β they use their last-known configuration cached in memory
- New xDS pushes stop β configuration changes (new routes, new endpoints) are not delivered
- Certificate rotation fails β when certs expire, mTLS handshakes will fail
- New pods donβt get sidecars β the mutating webhook is unavailable
- Endpoint changes are not propagated β if pods scale up/down, existing proxies wonβt learn about new endpoints
This is why running multiple istiod replicas is critical for production.
Debugging Istio
Essential Commands
# Check sync status of all proxies (are they up to date with istiod?)
istioctl proxy-status
# OUTPUT:
# NAME CDS LDS EDS RDS ECDS ISTIOD
# app-v1.default SYNCED SYNCED SYNCED SYNCED istiod-abc-123
# app-v2.default STALE SYNCED SYNCED SYNCED istiod-abc-123
# ^^^^^ indicates config push failure
# Dump Envoy's listeners (LDS)
istioctl proxy-config listeners deploy/my-app
# Dump Envoy's routes (RDS)
istioctl proxy-config routes deploy/my-app
# Dump Envoy's clusters (CDS)
istioctl proxy-config clusters deploy/my-app
# Dump Envoy's endpoints (EDS)
istioctl proxy-config endpoints deploy/my-app
# Full Envoy config dump (JSON)
istioctl proxy-config all deploy/my-app -o json
# Check what config istiod WOULD push to a proxy
istioctl experimental describe pod my-app-pod-xyz
# Verify mTLS is active between services
istioctl authn tls-check deploy/my-app reviews.default.svc.cluster.localEnvoy Admin API (Port 15000)
# Port-forward to access the admin API
kubectl port-forward deploy/my-app 15000:15000
# Useful endpoints:
curl localhost:15000/config_dump # Full Envoy configuration
curl localhost:15000/clusters # Upstream cluster health
curl localhost:15000/stats # All metrics counters
curl localhost:15000/stats?filter=http # Filtered metrics
curl localhost:15000/server_info # Envoy version, uptime
curl localhost:15000/logging?level=debug # Change log level at runtimeCheck iptables Rules
# View the NAT table rules in the pod
kubectl exec deploy/my-app -c istio-proxy -- iptables -t nat -S
# Expected output (abbreviated):
# -A PREROUTING -j ISTIO_INBOUND
# -A OUTPUT -j ISTIO_OUTPUT
# -A ISTIO_INBOUND -p tcp --dport 15008 -j RETURN
# -A ISTIO_INBOUND -p tcp --dport 15090 -j RETURN
# -A ISTIO_INBOUND -p tcp --dport 15021 -j RETURN
# -A ISTIO_INBOUND -p tcp --dport 15020 -j RETURN
# -A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT
# -A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006
# -A ISTIO_OUTPUT -s 127.0.0.6/32 -j RETURN
# -A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
# -A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
# -A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
# -A ISTIO_OUTPUT -j ISTIO_REDIRECT
# -A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001See also
- Istio Envoy Internals β threading model, hot restart, filter pipeline, connection pooling, health checking
- Istio Traffic Management β VirtualService, DestinationRule, Gateway API
- Istio Security β mTLS, SPIFFE, AuthorizationPolicy, JWT, ext_authz
- Istio Observability & Extensibility β metrics, tracing, Kiali, WasmPlugin, EnvoyFilter
- Istio Service Discovery, DNS & Listeners β service discovery without mesh membership, DNS resolution, outboundTrafficPolicy, shared listeners
- TLS 1.3 Handshake β mTLS in Istio uses TLS under the hood
- Docker Proxy Networking in K8s β iptables and network namespaces context
- DaemonSet Pod Race Conditions β relevant to istio-init race conditions
- Istio Architecture (official docs)
- Envoy xDS Protocol (official docs)
- Istio Ambient Mode Overview
- Istio Ambient Data Plane Architecture
- Istio Debugging (proxy-cmd)
- Tetrate: iptables Rules in Istio Sidecar Explained
- Jimmy Song: Sidecar Injection, Traffic Intercepting & Routing
Interview Prep
Q: How does traffic interception work without application changes?
A: Istio uses Linux iptables rules in the NAT table to transparently redirect all TCP traffic through the Envoy sidecar. When a pod is created, the istio-init init container (or the Istio CNI plugin) installs iptables rules in the podβs network namespace:
- Inbound: The PREROUTING chain redirects all incoming TCP to port 15006 (Envoyβs VirtualInbound listener), except for Envoyβs own ports (15090, 15021, 15020).
- Outbound: The OUTPUT chain redirects all outgoing TCP to port 15001 (Envoyβs VirtualOutbound listener), except traffic from UID 1337 (Envoy itself, to prevent infinite loops) and traffic to localhost.
The application connects to reviews:8080 normally. The kernel intercepts the SYN packet via iptables, redirects it to Envoy on port 15001. Envoy reads the original destination from SO_ORIGINAL_DST, applies routing rules, selects an upstream endpoint, and opens a new connection (as UID 1337, which bypasses iptables). The application is completely unaware.
Q: Walk through a request lifecycle end-to-end in an Istio mesh.
A: Suppose Service Aβs app sends GET /api/reviews to reviews:8080:
- App A calls
connect("reviews:8080"). The kernel resolves this to the ClusterIP (e.g., 10.96.5.100). - Kernel (Pod A): The OUTPUT chain catches the SYN packet. iptables checks: not from UID 1337, not to localhost β redirects to 127.0.0.1:15001.
- Envoy outbound (Pod A, port 15001): Accepts the connection, reads the original destination (10.96.5.100:8080) via
SO_ORIGINAL_DST. Matches it against its route table (from RDS). The route says clusteroutbound|8080||reviews.default.svc.cluster.local. EDS provides the healthy endpoints. Load balancer picks Pod B (10.48.2.15:8080). Envoy applies DestinationRule policies (retries, circuit breaker). Initiates mTLS with Pod Bβs Envoy using its SPIFFE certificate. Sends the HTTP request over the encrypted connection. - Network: Packet travels from Pod A to Pod B (via CNI, possibly across nodes).
- Kernel (Pod B): The PREROUTING chain catches the incoming packet. iptables redirects to 127.0.0.1:15006.
- Envoy inbound (Pod B, port 15006): Accepts the connection, terminates mTLS, verifies Pod Aβs SPIFFE identity, evaluates AuthorizationPolicy (RBAC). If allowed, forwards to
127.0.0.1:8080(the local app). This connection is from UID 1337, so iptables lets it pass. - App B receives the plain HTTP request on port 8080, processes it, and sends the response back through the same path in reverse.
Q: What is the xDS protocol? Name and explain each xDS API.
A: xDS (Extensible Discovery Service) is the gRPC-based protocol Envoy uses to receive dynamic configuration from a management server. In Istio, istiod is the xDS server. Each Envoy maintains a persistent gRPC stream and receives real-time updates.
The six APIs:
- LDS (Listener Discovery Service): Configures Envoy listeners β what addresses/ports to bind, which filter chains to use. In Istio, the VirtualInbound (15006) and VirtualOutbound (15001) listeners are configured via LDS.
- RDS (Route Discovery Service): Configures HTTP route tables β virtual hosts, route match rules, weighted destinations, retries, timeouts. VirtualService resources map to RDS.
- CDS (Cluster Discovery Service): Configures upstream clusters β load balancing policy, circuit breakers, outlier detection, TLS context. DestinationRule resources map to CDS.
- EDS (Endpoint Discovery Service): Provides the actual IP:port endpoints for each cluster, along with health status, locality, and weight. Kubernetes Endpoints/EndpointSlices map to EDS.
- SDS (Secret Discovery Service): Delivers TLS certificates and private keys, plus trusted CA bundles. Used for mTLS cert rotation without proxy restart.
- ADS (Aggregated Discovery Service): A single gRPC stream that multiplexes all resource types. Istio always uses ADS to ensure safe ordering: CDS β EDS β LDS β RDS. This prevents traffic blackholes from partial configuration.
Istio 1.22+ defaults to Delta xDS (incremental), which sends only changed resources instead of the full set β critical for meshes with thousands of endpoints.
Q: What happens if istiod goes down?
A: Existing Envoy proxies continue operating with their last-known configuration cached in memory. Traffic keeps flowing with the most recent routing rules, mTLS settings, and endpoints.
However, several things break:
- No config updates: New VirtualService/DestinationRule changes are not pushed. Endpoint changes (pods scaling up/down) are not propagated β proxies use stale endpoint lists.
- Certificate rotation stops: When existing certificates expire (default 24h TTL), mTLS handshakes fail and service-to-service communication breaks.
- No new sidecar injection: The mutating webhook is unavailable, so new pods are created without sidecars.
- No new proxy bootstrapping: Newly restarted proxies cannot fetch their initial configuration.
This is why production deployments run multiple istiod replicas (typically 2-3). istiod is stateless and reconstructs all state from the Kubernetes API server on startup. Recovery after restart is automatic β proxies reconnect and receive a full configuration push.
Q: Explain sidecar injection. What is a mutating admission webhook?
A: A Mutating Admission Webhook is a Kubernetes extension point that intercepts API requests (like pod creation) before they are persisted to etcd. The API server sends the pod spec to a registered webhook endpoint, which can modify (mutate) the spec and return it.
Istio registers a MutatingWebhookConfiguration that targets pod creation in namespaces labeled istio-injection=enabled (or istio.io/rev=<tag>). When a pod is created:
- The API server sends the pod spec to istiodβs webhook endpoint (port 15017).
- istiod checks the namespace label and any pod-level annotations.
- If injection is enabled, istiod mutates the pod spec by adding: (a) an
istio-initinit container that runsistio-iptablesto set up traffic interception rules, (b) anistio-proxysidecar container with Envoy + pilot-agent, (c) volumes for config, SDS socket, and projected service account tokens. - The mutated pod spec is returned to the API server and persisted.
The injection is entirely at the Kubernetes API level β existing Deployments and Helm charts donβt need changes. To disable injection for a specific pod, annotate it with sidecar.istio.io/inject: "false".
Q: What is Istio Ambient Mode and why was it introduced?
A: Ambient mode is a sidecar-less data plane architecture that addresses the main pain points of sidecar injection: high resource overhead (one Envoy per pod), operational complexity (pod restarts needed for injection, init container race conditions), and the blast radius of sidecar failures.
Ambient mode splits the data plane into two layers:
ztunnel (Zero Trust Tunnel): A lightweight Rust-based L4 proxy deployed as a DaemonSet (one per node). It handles mTLS encryption, L4 authorization, and TCP telemetry. Traffic between ztunnels is tunneled over HBONE (HTTP CONNECT over mTLS with HTTP/2 multiplexing). ztunnel creates listening sockets inside each podβs network namespace via the Istio CNI agent, so no iptables REDIRECT rules or init containers are needed.
Waypoint Proxy: An optional Envoy-based deployment (per-namespace or per-service) that provides L7 features: HTTP routing, header-based authorization, retries, fault injection, traffic splitting. Only deployed when L7 processing is actually needed.
The key insight is that most services only need L4 security (mTLS + basic authz), so the L7 overhead of a full Envoy sidecar is wasted. Ambient mode reports 90%+ memory reduction compared to sidecar mode. Pods donβt need to restart to join the mesh β just label the namespace.
Q: What are common Istio debugging techniques?
A: The primary debugging tools:
**istioctl proxy-status** (oristioctl ps): Shows whether each proxy is SYNCED or STALE with istiod. STALE means a config push failed or the proxy is disconnected.**istioctl proxy-config**(oristioctl pc): Dumps the actual Envoy configuration for a specific proxy. Sub-commands:listeners(LDS),routes(RDS),clusters(CDS),endpoints(EDS),all(full config dump). Use-o jsonfor full detail. Example:istioctl pc routes deploy/my-appshows exactly which routes Envoy has.- Envoy Admin API (port 15000):
kubectl port-forward deploy/my-app 15000:15000, thencurl localhost:15000/config_dumpfor the full config,/clustersfor upstream health,/statsfor metrics counters. You can change log levels at runtime with/logging?level=debug. - iptables inspection:
kubectl exec deploy/my-app -c istio-proxy -- iptables -t nat -Sto verify the NAT rules are correct. **istioctl analyze**: Static analysis of Istio configuration in a namespace β catches misconfigurations like missing DestinationRules for subsets referenced in VirtualServices.- Access logs: Enable Envoy access logging via MeshConfig (
accessLogFile: /dev/stdout) to see every request with upstream/downstream details, response codes, and latency breakdowns.
Q: How does Istio handle protocol detection?
A: Istio determines the protocol of a connection through three mechanisms, in priority order:
- Explicit declaration: The Kubernetes Service port name starts with a recognized prefix (
http-,grpc-,tcp-, etc.) or theappProtocolfield is set. This is the most reliable method. - HTTP inspection (auto-detection): If no protocol hint is available, Envoyβs HTTP inspector filter reads the first bytes of the connection to determine if they look like HTTP. This works for client-first protocols (HTTP) but causes a detection timeout delay (up to 5 seconds by default) for server-first protocols like MySQL, where the server sends the first bytes. During this timeout, the connection appears to hang.
- Fallback to TCP: If detection times out or fails, the traffic is treated as opaque TCP. No HTTP-level features (retries, header-based routing, HTTP metrics) are available.
The gotcha: if you forget to name your ports correctly, all HTTP traffic is treated as TCP. You get no HTTP metrics, no retries, no header-based routing. The symptom is subtle β everything works, but mesh features silently donβt apply. Always check with istioctl proxy-config listeners deploy/my-app to verify the filter chain type for each port.