Architecture
Big picture
Istio is two layers. The control plane is istiod, the pilot-discovery binary built from pilot/cmd/pilot-discovery (Makefile.core.mk:213). It watches the Kubernetes API and Istio config CRDs, builds Envoy configuration, and pushes it to the data plane over xDS, a gRPC discovery protocol. The data plane is Envoy: a sidecar per pod in the classic model, or in ambient mode a per-node ztunnel for L4 plus an optional waypoint Envoy for L7. The single control loop is "config changes, recompute, push to every connected proxy".
Components
istiod (pilot)
The control plane core lives in pilot/. Its entry point is pilot/cmd/pilot-discovery/main.go:27, which calls app.NewRootCommand() (pilot/cmd/pilot-discovery/app/cmd.go:41). Inside, the xDS server is DiscoveryServer (pilot/pkg/xds/discovery.go:65). It owns the config model, the service registry, and xDS generation and delivery.
istioctl
The CLI is istioctl/, built from istioctl/cmd/istioctl (Makefile.core.mk:212). It covers install, analyze, and proxy-config inspection.
security (CA)
security/ is the certificate authority. It issues SPIFFE-based workload certificates, the identity backing mutual TLS in the mesh.
istio-cni
cni/ is the CNI plugin, built from cni/cmd/istio-cni (Makefile.core.mk:218). It sets up the traffic redirection that steers a pod's traffic through the sidecar or, in ambient mode, the node ztunnel.
Shared libraries
pkg/ holds shared code, including the generic xDS server scaffolding in pkg/xds/server.go. operator/ and manifests/ hold the Helm charts and install profiles.
How a request flows
The representative operation is a config change propagating to every Envoy. Trace it through pilot/pkg/xds.
- A config change (for example a
VirtualService) reachesDiscoveryServer.ConfigUpdate(req). It clears the xDS cache for Address kinds, then drops the request ontopushChanneland returns (pilot/pkg/xds/discovery.go:326-343). handleUpdatesdelegates todebounce()(pilot/pkg/xds/discovery.go:351-352). Debounce collapses a burst of events into one, merging thePushRequestvalues, bounded by a minimum quiet time and a maximum delay (pilot/pkg/xds/discovery.go:355). This is the defense against a thundering herd of pushes.- After debounce,
Push(req)runs (pilot/pkg/xds/discovery.go:288-307). It saves the oldPushContext, stamps a new version withNextVersion(), builds a fresh immutablePushContextsnapshot viainitPushContext, attaches it toreq.Push, and callsAdsPushAll(req). AdsPushAllcallsStartPush, which walks every connected client and enqueues the request (pilot/pkg/xds/ads.go:566-592).- A separate goroutine drains the queue, gated by a
concurrentPushLimitsemaphore so only so many proxies are pushed at once. - Each connection runs
pushConnection(con, ev)(pilot/pkg/xds/ads.go:478-503). It refreshes proxy state, then asksProxyNeedsPush. If the proxy's scope does not depend on the change, it is skipped. Otherwise each watched resource is pushed inPushOrder: clusters, endpoints, listeners, routes, secrets, then the ambient address and workload types (pilot/pkg/xds/ads.go:505-516). pushXds(con, w, req)(pilot/pkg/xds/xdsgen.go:112) looks up the generator for the resource's TypeUrl withfindGenerator, callsgen.Generate(...), assembles aDiscoveryResponse, and writes it to the gRPC stream.
ACK and NACK flow the other way. Envoy returns a request carrying a nonce, handled by processRequest (pilot/pkg/xds/ads.go:139) and Stream (pilot/pkg/xds/ads.go:187). The WatchedResource.NonceSent and NonceAcked fields track whether the last response was accepted.
Key design decisions
Istio rebuilds the entire PushContext on every config change rather than mutating shared state. Push() retires the old context and constructs a new immutable snapshot (pilot/pkg/xds/discovery.go:288-306); all per-proxy computation then reads that single snapshot. This prevents proxies from seeing inconsistent config while a push is in flight and lets a single version (PushVersion) propagate to Envoy. The cost is rebuilding indexes each time, which is why debounce sits in front of it to bound the frequency. On top of that, ProxyNeedsPush and PushRequest.ConfigsUpdated skip proxies whose scope does not depend on the change. This combination is the answer to convergence-time and CPU/memory pressure at very large proxy counts (eBay case study).
Ambient is a separate data-plane path. L4 is handled by the per-node ztunnel (Rust, in istio/ztunnel), which shares mTLS and routing across pods on the node. A waypoint Envoy is added per namespace or service only when L7 features are required. The same istiod drives both over xDS, with extra TypeUrls for the workload and address types (pilot/pkg/xds/ads.go:511-516).
Extension points
- Configuration CRDs:
VirtualService,DestinationRule,Gateway,Sidecar, authentication and authorization policies, andTelemetry, all indexed in the push context. EnvoyFilterfor patching generated Envoy config directly.- The injection webhook and the
istio-cniplugin for wiring workloads into the mesh. - The xDS generator registry on
DiscoveryServer.Generators, keyed by TypeUrl, for custom resource generation.