Istio — Architecture#

Architectural style#

Modular Monolith (explicitly self-described), over a Control-Plane / Data-Plane split

Istiod is a single binary (pilot-discovery) that houses multiple distinct subsystems: an xDS gRPC server, service discovery controllers, a built-in certificate authority, webhook handlers (sidecar injection, config validation), and Kubernetes reconciliation controllers. The project’s own architecture/networking/pilot.md calls it a “modular monolith.” The monolith sits entirely on the control plane; the data plane is out-of-process Envoy (C++) sidecars and ztunnel (Rust), which connect back to Istiod over bidirectional gRPC xDS streams. This split is clean and enforced at the process boundary: the Go codebase generates and pushes configuration; Envoy owns all packet forwarding.

A secondary architectural pattern visible in the codebase is the Plugin / Generator pattern for xDS resource generation: a map[string]model.XdsResourceGenerator is keyed by xDS type URL, making it easy to add new resource types without modifying the core push loop.

Component diagram (textual)#

┌──────────────────────────────────────────────────────────────────┐
│  Istiod (pilot-discovery)                                         │
│                                                                   │
│  ┌──────────────────────────────────────────────────────────────┐ │
│  │  bootstrap.Server (server.go)                                │ │
│  │  • Wires all subsystems together on startup                  │ │
│  │  • Owns gRPC/HTTPS listeners, file watchers, readiness probes│ │
│  └────────────────────────┬─────────────────────────────────────┘ │
│             ┌─────────────┼──────────────────┐                    │
│             ▼             ▼                  ▼                    │
│   ┌──────────────┐  ┌──────────────┐  ┌──────────────────────┐   │
│   │ DiscoveryServer│  │ CA / RA      │  │  Webhook Server      │   │
│   │ (pilot/pkg/xds)│  │(security/pkg)│  │  (sidecar inject,    │   │
│   │  - ADS gRPC   │  │  - Citadel   │  │   config validation) │   │
│   │  - push loop  │  │  - gRPC SDS  │  └──────────────────────┘   │
│   └──────┬───────┘  └──────────────┘                              │
│          │                                                         │
│          │ reads snapshot                                          │
│          ▼                                                         │
│   ┌──────────────────────────────────────────────────────────┐    │
│   │  model.Environment  (pilot/pkg/model/context.go)         │    │
│   │  embeds: ConfigStore + ServiceDiscovery + MeshWatcher     │    │
│   │  holds: PushContext (immutable snapshot)                  │    │
│   └────────────┬─────────────────────────────────────────────┘    │
│       ┌────────┴────────┐                                         │
│       ▼                 ▼                                         │
│  ┌──────────┐    ┌─────────────────────────────────────────┐      │
│  │ConfigStore│    │ServiceDiscovery (aggregate)             │      │
│  │(aggregate)│    │ ├─ KubeController (Services/Endpoints/Pods)│   │
│  │ ├─ CRD   │    │ └─ ServiceEntryController               │      │
│  │ ├─ File  │    └─────────────────────────────────────────┘      │
│  │ └─ xDS   │                                                     │
│  └──────────┘                                                     │
│                                                                    │
│   ┌──────────────────────────────────────────────┐               │
│   │  ConfigGeneratorImpl (pilot/pkg/networking/core) │            │
│   │  Generators: LDS/CDS/RDS/EDS/ECDS/NDS/PCDS/SDS │            │
│   └──────────────────────────────────────────────┘               │
└──────────────────────────────────────────────────────────────────┘
        │ xDS (gRPC, bidirectional streaming)
        ▼
 ┌─────────────────────────────┐
 │  Data Plane                 │
 │  Envoy sidecars / ztunnel   │
 │  (C++ / Rust, out-of-process)│
 └─────────────────────────────┘

Core components#

bootstrap.Server#

  • Package: pilot/pkg/bootstrap
  • Responsibility: The top-level server struct that wires all Istiod subsystems together. Owns gRPC listeners (port 15010/15012), HTTPS webhook server (port 15017), HTTP debug/monitoring server (port 15014), file watcher for MeshConfig changes, and lifecycle management via server.Instance (an ordered startup/shutdown list).
  • Key types: Server struct, PilotArgs, readinessProbe func() bool
  • Dependencies: All other packages — it is the composition root. Uses server.New() (from pilot/pkg/server) as a component runner that executes registered AddStartFunc callbacks in order.

DiscoveryServer#

  • Package: pilot/pkg/xds
  • Responsibility: Implements the Envoy xDS ADS (Aggregated Discovery Service) gRPC protocol. Manages connected proxy clients (adsClients map), runs a debounce loop over a pushChannel chan, enqueues work to a PushQueue, and dispatches per-proxy pushes by calling the appropriate XdsResourceGenerator for each xDS type.
  • Key types: DiscoveryServer, Connection (one per proxy), PushQueue, DebounceOptions
  • Dependencies: model.Environment, model.XdsResourceGenerator (via plugin map), model.XdsCache

model.Environment#

  • Package: pilot/pkg/model
  • Responsibility: The aggregate runtime environment for Istiod. Embeds three core interfaces: ConfigStore (Istio CRD access), ServiceDiscovery (service/endpoint enumeration), and Watcher (mesh config change notifications). Also holds the current PushContext — the immutable snapshot used during each push cycle.
  • Key types: Environment struct, PushContext struct
  • Dependencies: ConfigStore, ServiceDiscovery, mesh.NetworksWatcher, trustbundle.TrustBundle

ConfigStore / ConfigStoreController#

  • Package: pilot/pkg/model (interface); implementations in pilot/pkg/config/kube/crdclient, pilot/pkg/config/kube/file, pilot/pkg/config/aggregate
  • Responsibility: Unified access to Istio config resources (VirtualService, DestinationRule, Gateway, AuthorizationPolicy, etc.) regardless of source. The aggregate implementation (configaggregate) fans out reads to multiple backing stores (CRD, filesystem, xDS-over-MCP) and merges results.
  • Key types: ConfigStore interface (Get/List/Create/Update/Delete), ConfigStoreController interface (adds Watch + Run), config.Config wrapper struct
  • Dependencies: pkg/kube/kclient, pkg/config/schema

ServiceDiscovery (Aggregate)#

  • Package: pilot/pkg/serviceregistry/aggregate
  • Responsibility: Aggregates service and endpoint information from multiple registries: the Kubernetes registry (watching Services, Endpoints, Pods) and the ServiceEntry controller (for VM/external workloads). Exposes a unified ServiceDiscovery interface to model.Environment.
  • Key types: aggregate.Controller, serviceentry.Controller, model.Service, model.ServiceInstance, model.IstioEndpoint
  • Dependencies: pkg/kube/kclient, model.XDSUpdater (callback into DiscoveryServer for push triggers)

ConfigGeneratorImpl#

  • Package: pilot/pkg/networking/core
  • Responsibility: Translates the model state (from PushContext + Proxy) into Envoy xDS wire format. Implements the ConfigGenerator interface with methods: BuildListeners (LDS), BuildClusters (CDS), BuildHTTPRoutes (RDS), BuildNameTable (NDS), BuildExtensionConfiguration (ECDS). Each resource type is further split into sub-generators (e.g., InboundFilterChainGenerator, OutboundListenerGenerator) and plugin chain (networking/plugins).
  • Key types: ConfigGenerator interface, ConfigGeneratorImpl struct
  • Dependencies: model.PushContext, model.Proxy, model.XdsCache (for caching encoded proto.Any)

CA / RA (Citadel)#

  • Package: security/pkg/pki/ca, security/pkg/pki/ra, security/pkg/server/ca
  • Responsibility: Issues and rotates X.509 certificates for mutual TLS. Can act as a self-signed root CA (IstioCA), a k8s CSR-based Registration Authority (KubernetesRA), or delegate to an external CA. Exposes a gRPC IstioCertificateService for workload cert requests.
  • Key types: ca.IstioCA, ra.RegistrationAuthority (interface), caserver.Server
  • Dependencies: security/pkg/pki, pkg/spiffe, pkg/security

krt (Kubernetes Reactive Transforms)#

  • Package: pkg/kube/krt
  • Responsibility: A declarative, reactive abstraction for building Kubernetes controllers. Wraps kclient.Client informers into typed Collection[T] objects that propagate change events through a dependency graph. Used experimentally in newer Istio subsystems (ambient mode workload indexing, agentgateway) to replace imperative reconciliation with declarative pipelines.
  • Key types: Collection[T], DebugHandler, Singleton[T]
  • Dependencies: pkg/kube/kclient

Data flow#

Typical scenario: a new VirtualService is applied to the cluster

1. Kubernetes API server emits a WATCH event for the VirtualService CRD.
2. crdclient (ConfigStore) receives the event via kclient informer.
3. crdclient calls model.XDSUpdater.ConfigUpdate(&PushRequest{Full: true, ConfigsUpdated: {VirtualService: ...}}).
4. DiscoveryServer.pushChannel receives the PushRequest.
5. Debounce loop collects events for DebounceAfter (default 100ms) or until DebounceMax (10s).
6. Once stable: PushContext.InitContext() is called — rebuilds the immutable snapshot (services, configs, policies).
7. All connected proxies are enqueued in the PushQueue.
8. Concurrent push workers dequeue proxies, call DefaultProxyNeedsPush() to decide if this proxy is affected.
9. For affected proxies, DiscoveryServer.pushXds() calls the appropriate Generators (LDS, CDS, RDS, EDS).
10. Each Generator reads from PushContext (lock-free, snapshot) and model.Proxy to build proto resources.
11. Resources are checked against XdsCache (keyed by dependency hash); cache hits skip encoding.
12. Encoded proto.Any resources are streamed to the proxy over the ADS gRPC stream.
13. Proxy ACKs; DiscoveryServer records the nonce for version tracking.

Endpoint-only change (pod scale up) follows a shorter path: ServiceDiscovery.EDSUpdate() bypasses PushContext recomputation and triggers incremental EDS pushes only to proxies subscribed to the affected cluster.

Initialization / Bootstrap#

The startup sequence in bootstrap.NewServer() and Start() is strictly ordered:

1. model.NewEnvironment()            — creates empty aggregate environment
2. aggregate.NewController()         — creates service discovery aggregate
3. xds.NewDiscoveryServer()          — creates xDS server (not yet serving)
4. core.NewConfigGenerator()         — creates config translator
5. initServers()                     — allocates HTTP/gRPC server objects
6. initIstiodAdminServer()           — registers debug/monitoring HTTP handlers
7. serveHTTP()                       — starts HTTP server (port 8080/15014)
8. initKubeClient()                  — connects to Kubernetes API
9. initMeshConfiguration()           — loads MeshConfig from ConfigMap, sets up file watcher
10. initMeshNetworks()               — loads NetworksConfig
11. initMeshHandlers()               — registers MeshConfig change callbacks
12. environment.Init()               — finalizes environment wiring
13. maybeCreateCA()                  — creates/loads Citadel CA or RA
14. initControllers()                — starts ConfigStore + ServiceDiscovery controllers
    ├─ initConfigController()        — creates CRD/file/xDS config store, sets up informers
    └─ initServiceControllers()      — creates Kube + ServiceEntry registries
15. InitGenerators()                 — registers LDS/CDS/RDS/EDS/SDS/NDS generators into DiscoveryServer
16. initIstiodCerts()                — creates or loads Istiod's own TLS cert
17. initSecureDiscoveryService()     — creates mTLS gRPC server (port 15012)
18. initSidecarInjector()            — starts webhook for sidecar injection
19. initConfigValidation()           — starts webhook for config validation
20. initDiscoveryService()           — registers xDS handlers on gRPC server

Start():
21. server.Start(stop)               — fires all registered AddStartFunc components
22. waitForCacheSync()               — waits for Kubernetes informers to populate
23. XDSServer.CachesSynced()         — allows xDS server to accept proxy connections
24. Serve gRPC listeners (15010, 15012) and HTTPS (15017)

Dependency injection: Manual wiring — no DI framework (no wire, dig, or fx). bootstrap.Server is the composition root; all components are created and connected explicitly in NewServer(). Inversion of control is achieved purely through interfaces (ConfigStore, ServiceDiscovery, XDSUpdater, XdsResourceGenerator).

Configuration#

Istiod is configured at three levels:

  1. MeshConfig (istio/api/mesh/v1alpha1.MeshConfig): The primary runtime config; loaded from a Kubernetes ConfigMap (istio in namespace istio-system). Contains global mesh settings (trust domain, access log format, proxy defaults, CA address). Watched via a file watcher and a Kubernetes informer; changes trigger a MeshConfigChanged callback, causing partial PushContext recomputation.

  2. Feature flags (pilot/pkg/features): Per-feature environment variables (e.g., PILOT_ENABLE_EDS_DEBOUNCE, PILOT_XDS_AUTH, ENABLE_AMBIENT). Registered with pkg/env at package init time. This is Istio’s internal progressive-rollout mechanism — new behaviors are gated behind these flags.

  3. PilotArgs (pilot/pkg/bootstrap/options.go): Startup flags parsed by Cobra (registry options, domain suffix, namespace, revision, shutdown duration). Passed as a struct through NewServer() but not stored long-term; config is extracted into subsystem-specific structures during init.

  4. Kubernetes CRDs: Istio’s user-facing configuration API — VirtualService, DestinationRule, Gateway, AuthorizationPolicy, PeerAuthentication, Sidecar, Telemetry, etc. Read via crdclient using kclient typed informers.

Key design decisions#

  1. Immutable PushContext snapshot (pilot/pkg/model/push_context.go): Rather than locking the config store during each push, Istiod creates a full snapshot (PushContext) before every push cycle. Generators read from this snapshot without locks, enabling concurrent per-proxy generation. The trade-off is memory (one snapshot in memory at all times) and the discipline required to correctly invalidate only the affected sub-indexes on partial rebuilds.

  2. Debounce + PushQueue pipeline (pilot/pkg/xds/discovery.go): Config changes are batched by a debounce loop before entering the push pipeline. This absorbs bursts of Kubernetes events (e.g., a deployment rollout updating 50 pods) into a single push cycle. The PushQueue further merges concurrent push requests for the same proxy, ensuring each proxy has at most one outstanding push at a time.

  3. XdsResourceGenerator plugin map: Each xDS type URL (LDS, CDS, RDS, EDS, SDS, NDS, ECDS, PCDS, workload) maps to a registered XdsResourceGenerator implementation. This makes it straightforward to add new xDS resource types (e.g., PCDS for RBAC policy, workload for Ambient mode) without changing the push loop. The same mechanism allows the agentgateway integration to substitute entirely different collection-based generators.

  4. krt (Kubernetes Reactive Transforms): An experimental declarative reactive framework (pkg/kube/krt) layered on top of kclient. Instead of writing imperative event handlers with manual state and mutex management, controllers declare collections and transformations. Changes propagate automatically through the dependency graph. This is the architectural direction for Ambient mode (workload indexing, ztunnel config) and is gradually replacing legacy imperative controller code.

  5. Separate sidecar mode vs Ambient mode code paths: The model.AmbientIndexes interface (pilot/pkg/model/service.go) and the PCDS / workload xDS generators provide a parallel config path for Ambient mode (ztunnel + waypoints) that coexists with the sidecar LDS/CDS/RDS path. This avoids a forked codebase but adds significant conditional logic throughout the xDS generation layer. The architectural documentation in architecture/ambient/ captures the design intent for this split.