Architecture
Big picture
Contour is an xDS control plane for Envoy. It watches the Kubernetes API, converts routing objects into an internal DAG (Listener to VirtualHost to Route to Cluster), turns that DAG into Envoy xDS resources, and serves them to Envoy over gRPC. The main entry point is cmd/contour/contour.go:30, which uses kingpin subcommands (serve, bootstrap, certgen, cli, gateway-provisioner); the controller body runs under serve, reaching doServe() at cmd/contour/serve.go:384.
Components
internal/contour: EventHandler
Receives Kubernetes events and drives DAG rebuilds. EventHandler is defined at internal/contour/handler.go:45. It is a single-threaded event loop with a holdoff timer that batches bursts of events before rebuilding.
internal/dag: DAG translation
The core translation logic. It converts Kubernetes objects into a directed acyclic graph. Builder.Build lives at internal/dag/builder.go:59. A set of processors does the work: httpproxy_processor.go, ingress_processor.go, gatewayapi_processor.go, and listener_processor.go.
internal/xdscache (and v3): xDS resource caches
Converts the DAG into Envoy xDS resources (CDS, EDS, LDS, RDS, SDS, RTDS) and caches them. RouteCache.OnChange at internal/xdscache/v3/route.go:62 builds Envoy RouteConfiguration from the DAG. SnapshotHandler at internal/xdscache/v3/snapshot.go:35 produces the go-control-plane snapshot.
internal/xds (and v3): gRPC server
The gRPC server that delivers resources to Envoy. RegisterServer is at internal/xds/v3/server.go:38. ConstantHash at internal/xds/v3/hash.go:23 is the node ID hasher.
internal/k8s and internal/provisioner
internal/k8s holds informers, the status updater, and clients. internal/provisioner is the Gateway API provisioner that creates Deployments and Services from Gateway objects. CRD type definitions live under apis/projectcontour.
How a request flows
Tracing one change, an HTTPProxy edit, end to end to an Envoy RDS update:
- A Kubernetes informer fires.
EventHandler.OnAdd/OnUpdate/OnDelete(internal/contour/handler.go:103-116) send the operation onto theupdatechannel. The handler is registered as an informer event handler incmd/contour/serve.goand constructed atcmd/contour/serve.go:599. - The main loop in
EventHandler.Start(internal/contour/handler.go:134-244) applies the change to theKubernetesCacheand arms a holdoff timer to debounce a burst of events. - When the timer fires and the informer caches are synced, the loop builds a new DAG and hands it to the observer:
latestDAG := e.builder.Build()thene.observer.OnChange(latestDAG)(internal/contour/handler.go:224-226). Status updates are then sent (internal/contour/handler.go:228-231). Builder.Build(internal/dag/builder.go:59-127) runs each registered processor in order (internal/dag/builder.go:79-81) then prunes invalid virtual hosts and empty Listeners (internal/dag/builder.go:87-124). The processor order is built ingetDAGBuilder(cmd/contour/serve.go:1087-1167):ListenerProcessorfirst, thenIngressProcessor, thenHTTPProxyProcessor(internal/dag/httpproxy_processor.go:127), withGatewayAPIProcessoradded only when Gateway API is enabled.observer.OnChangefans out throughComposeObservers(internal/dag/dag.go:52-58) to eachResourceCacheand theSnapshotHandler. For exampleRouteCache.OnChange(internal/xdscache/v3/route.go:62-142) walks the DAG Listeners and VirtualHosts and builds EnvoyRouteConfigurationprotos.SnapshotHandler.OnChange(internal/xdscache/v3/snapshot.go:137-163) collects each cache'sContents(), builds a new go-control-plane snapshot with a fresh UUID version (internal/xdscache/v3/snapshot.go:153), and stores it under theConstantHashnode ID key withSetSnapshot(internal/xdscache/v3/snapshot.go:159).- The xDS gRPC server, registered at
cmd/contour/serve.go:905-906viaRegisterServer(internal/xds/v3/server.go:38-40), then delivers the new RDS to every connected Envoy over ADS.
Key design decisions
- One snapshot for the whole fleet.
ConstantHash.IDalways returns the same string (internal/xds/v3/hash.go:23-36), so any Envoy that connects, regardless of its service-node flag, shares one snapshot. Contour manages a single cluster-wide snapshot rather than per-node state. - EDS is handled separately. The
SnapshotHandlerkeeps a dedicatedLinearCachefor endpoints (internal/xdscache/v3/snapshot.go:50-52) and aMuxCacheroutes byTypeUrl(internal/xdscache/v3/snapshot.go:54-71). The comment (internal/xdscache/v3/snapshot.go:46-49) explains why: with a plain SnapshotCache every EDS stream would be notified of unrelated endpoint changes, so a LinearCache sends updates only for explicitly requested resources. - Debounced rebuilds gated on informer sync. DAG rebuilds are debounced by the holdoff timer, and the loop skips and retries the rebuild until informer caches are synced (
internal/contour/handler.go:212-220).
Extension points
- Three configuration APIs: standard
Ingress, theHTTPProxyCRD (apis/projectcontour), and the Gateway API. - The Gateway provisioner (
internal/provisioner) creates Contour and Envoy workloads from Gateway API objects. - External authorization via Envoy
ext_authz, plus global and local rate limiting, configured throughHTTPProxy.