Source: envoyproxy/envoy @
3d6e5f9
Overview
Envoy Proxy is a Layer 4/Layer 7 proxy and communication bus designed for modern service-oriented architectures. Originally built at Lyft, it is now a CNCF graduated project and the default data plane for service meshes like Istio. It handles HTTP/1.1, HTTP/2, HTTP/3, gRPC, TCP, UDP, and WebSocket traffic with a uniform configuration and operational model.
Envoy matters because it solved a real production problem at scale — observability, reliability, and security for microservice communication — and its xDS API became the de facto standard for data plane configuration. Every major service mesh implementation (Istio, AWS App Mesh, Consul Connect) either uses Envoy directly or adopted the xDS protocol.
The codebase is large: ~14K files across source/, api/, test/, contrib/, and docs/. It is written in C++20, built with Bazel, and uses Protocol Buffers extensively for configuration and the data plane API.
Architecture
Envoy’s codebase splits into four major layers:
| Layer | Directory | Role |
|---|---|---|
| API definitions | api/envoy/ | Protobuf-based xDS configuration API (v3) |
| Core library | source/common/ | HTTP, network, router, upstream, config, stats — the embeddable engine |
| Server runtime | source/server/ | Server bootstrap, workers, listener manager, admin, hot restart |
| Extensions | source/extensions/ | Filters, access loggers, tracers, clusters, transport sockets |
api/ # Protobuf definitions (xDS v3)
envoy/config/ # Listener, cluster, route, bootstrap configs
envoy/service/discovery/ # ADS/xDS gRPC service definitions
envoy/extensions/ # Extension-specific protobuf configs
source/
exe/ # main() → MainCommon → InstanceImpl
server/ # Server lifecycle, workers, listener mgmt
common/ # Core engine (network, http, router, upstream)
extensions/
filters/http/ # ~50+ HTTP-level filters
filters/network/ # L4 filters (HCM, Redis, MongoDB, etc.)
filters/listener/ # Pre-accept filters (TLS inspector, proxy protocol)
test/ # Unit, integration, fuzz tests
contrib/ # Out-of-tree extensions (same layout as extensions/)
The envoy/ top-level directory contains public interface headers — almost entirely abstract base classes (pure virtual interfaces). This is a deliberate architectural choice: core code depends on interfaces, not implementations. The source/common/ directory provides the concrete implementations.
The split between
envoy/(interfaces) andsource/common/(implementations) is Envoy's dependency inversion boundary. New features almost always add an interface inenvoy/first, then implement it insource/.
The Spine
Startup
-
source/exe/main.cc— platform-specificmain()that callsMainCommon::main(argc, argv). -
source/exe/main_common.cc— parses CLI options, createsMainCommonBase, which constructsServer::InstanceImplvia a factory lambda. Callsserver->initialize(). -
source/server/server.cc(InstanceBase::initializeOrThrow) — the real bootstrap sequence:- Parse bootstrap config (protobuf)
- Initialize runtime (feature flags), stats, thread-local storage
- Create the cluster manager (upstream connection pools)
- Create the listener manager (binds ports, creates workers)
- Initialize xDS subscriptions (LDS, CDS, RDS, EDS, VHDS)
- Start worker threads
-
Workers —
source/server/worker_impl.cc: each worker owns anEvent::Dispatcher(libevent event loop) and aConnectionHandler. Listeners are added to workers viadispatcher_->post().
Request Lifecycle
A TCP connection arriving on a listener flows through this chain:
TCP accept (Worker's ConnectionHandler)
→ Listener filters (TLS inspector, proxy protocol, etc.)
→ Filter chain matching (by destination/source IP, port, SNI, ALPN)
→ Network filter chain (e.g., HTTP Connection Manager)
→ HTTP filter chain (router, rate limit, ext_authz, cors, etc.)
→ Router → Cluster selection → Connection pool → Upstream
At the network layer, source/common/network/filter_manager_impl.cc manages a linked list of read/write filters. FilterManagerImpl::addReadFilter() adds filters to the upstream_filters_ list; data flows through each filter’s onData() callback. A filter returns FilterStatus::StopIteration to halt the chain.
At the HTTP layer, source/common/http/filter_manager.cc manages the HTTP filter chain. Each ActiveStreamFilterBase wraps a decoder (request) and encoder (response) filter. The commonContinue() method advances iteration through the chain when a filter calls continueDecoding() or continueEncoding().
The HTTP Connection Manager (source/extensions/filters/network/http_connection_manager/) is the bridge between L4 and L7 — it parses HTTP codecs (HTTP/1, HTTP/2, HTTP/3) and drives the HTTP filter chain.
xDS Configuration Flow
Envoy’s dynamic configuration comes through the xDS protocol, defined in api/envoy/service/discovery/v3/:
- LDS (Listener Discovery Service) — dynamic listeners
- CDS (Cluster Discovery Service) — dynamic upstream clusters
- RDS (Route Discovery Service) — dynamic route tables
- EDS (Endpoint Discovery Service) — dynamic cluster membership
- ADS (Aggregated Discovery Service) — multiplexes all of the above on a single gRPC stream for ordering guarantees
source/common/config/utility.h provides the factory lookup mechanism (getAndCheckFactory, getFactoryByName) that maps xDS type URLs to registered factories.
Key Patterns
Filter Chain Architecture
Envoy’s extensibility rests on three filter chain types:
- Listener filters — run before a connection is accepted (e.g.,
tls_inspector,proxy_protocol). Inspect bytes to determine routing. - Network filters — run on an accepted connection (e.g.,
http_connection_manager,echo,redis_proxy). Operate at L4. - HTTP filters — run within the HTTP Connection Manager (e.g.,
router,ext_authz,rate_limit,cors). Operate at L7.
Each filter type has a factory registration pattern: implement a NamedFilterConfigFactory (or NamedHttpFilterConfigFactory), register it, and it becomes available via xDS configuration.
Factory Registration via Registry
source/common/config/utility.h centralizes factory lookup. Every extension registers through Envoy::Registry::registerFactory(). At runtime, configuration is deserialized from protobuf, the type URL is matched to a factory, and the factory creates the filter instance. This is how Envoy achieves plugin-level extensibility without dynamic loading.
Thread-Local Storage (TLS)
Envoy uses a main thread + worker thread model. ThreadLocal::Instance (in source/common/thread_local/) provides a slot-based mechanism where each thread gets its own copy of data. When the main thread updates configuration (e.g., a new route table from RDS), it uses tls_.allocateSlot() and slot.runOnAllThreads() to propagate data without locks on the hot path.
Hot Restart
Envoy supports hot restart with zero connection drops. source/server/hot_restart_impl.cc implements shared-memory communication between the old and new process. The parent drains existing connections while the child takes over listeners. This is critical for production deployments.
Stats Architecture
Stats are defined via macros (ALL_SERVER_STATS in server.h) that generate counter/gauge/histogram structs. This macro pattern is used throughout the codebase — define once, generate both the struct and the stats registration. Stats are flushed to sinks (StatsD, Prometheus, custom) on a configurable interval.
The macro-based stats definition pattern (
GENERATE_COUNTER_STRUCT,GENERATE_GAUGE_STRUCT) is worth understanding if you're extending Envoy — every new component needs its stats defined this way.
Non-Obvious Details
contrib/ is a separate extension namespace. The contrib/ directory mirrors source/extensions/ but contains extensions that aren’t part of the core build. They have a separate CODEOWNERS file and different stability expectations. The contrib binary (contrib/exe/) includes all contrib extensions.
Overload management is deeply integrated. The OverloadManager (source/server/overload_manager_impl.cc) isn’t just a threshold system — it registers actions on worker dispatchers that can stop accepting connections, reject requests, or reset streams under memory pressure. Workers subscribe to overload actions at construction time in worker_impl.cc.
The envoy/ include directory is not generated — it’s the API surface. These headers define the public interface of Envoy. They are abstract classes with a few concrete exceptions for performance (e.g., header maps). The API versioning system (api/API_VERSIONING.md) tracks compatibility across versions.
Connection pools are per-cluster, per-priority, per-thread. The PriorityConnPoolMap (source/common/upstream/priority_conn_pool_map_impl.h) organizes connection pools by priority level. Each worker thread has its own connection pools, avoiding cross-thread synchronization on the data path.
Filter chain matching uses a custom matcher framework. source/common/network/filter_matcher.cc and source/common/matcher/ implement a tree-based matching system that can match on destination IP, source IP, port, SNI, transport protocol, and application protocol — not just simple prefix matching.
xDS has two modes: State-of-the-World (SotW) and Delta. The Delta xDS protocol (DeltaDiscoveryRequest/DeltaDiscoveryResponse in ads.proto) sends only changes rather than the full configuration state. This matters at scale — a proxy with thousands of routes benefits significantly from delta updates.
The
dynamic_modulesdirectory in extensions appears to be a newer WASM-like dynamic loading mechanism. Its relationship to the existing factory registration pattern isn't fully clear from the code alone — it may represent a future direction for out-of-process extensions.
Assessment
Strengths
- Exceptional architectural consistency. The filter chain pattern, factory registration, and protobuf-based configuration are applied uniformly. Once you understand one filter type, you understand all of them.
- Production-grade operational features. Hot restart, overload management, admin interface, comprehensive stats — these aren’t afterthoughts. They’re deeply integrated.
- The xDS API is genuinely a standard. By separating the API into
api/with its own versioning, Envoy created a reusable configuration protocol that other projects implement. - Testing discipline is extraordinary. The
test/directory mirrors the source structure with unit tests, integration tests, and fuzz tests. The project runs under OSS-Fuzz continuously.
Concerns
- The codebase is enormous. The learning curve is steep. A new contributor must understand Bazel, protobuf, Envoy’s threading model, and the filter chain architecture before making meaningful contributions.
- Macro-heavy stats and boilerplate. The stats macro system works but creates indirection. Adding a new filter requires touching multiple files (config, factory, proto, BUILD) with repetitive boilerplate.
- Build complexity. Full builds take significant time and resources. Bazel provides correctness but at the cost of complexity — the
WORKSPACE,MODULE.bazel, and.bzlfiles are non-trivial.
Recommendations
- Start with
REPO_LAYOUT.mdandSTYLE.md. These are the most efficient onboarding documents in the repository. - Read the HTTP Connection Manager first. It’s the architectural keystone — understanding how it bridges L4 connections to L7 filters unlocks the rest of the system.
- Trace a request through the filter chain. Set a breakpoint at
Network::FilterManagerImpl::initializeReadFilters()and follow the chain through toHttp::FilterManager. This single exercise teaches more than reading any document. - Use the
envoy-filter-examplerepository. For extension development, the official filter example repo provides a minimal working template that avoids the full Bazel complexity.
Related
- analyzing-traefik — Another L7 proxy, but Go-based with a simpler architecture. Useful contrast in approach.
- analyzing-fluent-bit — Also a CNCF data-plane project; different domain (logging) but similar plugin-extension patterns.