Overview

Envoy is the high-performance L4/L7 proxy (written in C++) that forms the data plane of Istio. Every meshed pod runs an Envoy instance as a sidecar (istio-proxy container). This note covers Envoy’s internal architecture in depth: the request processing pipeline, threading model, hot restart mechanism, filter system, connection pooling, health checking, and access logging.

For the Istio control plane architecture (istiod, xDS, sidecar injection, iptables interception), see Istio Architecture Deep Dive. For traffic management CRDs (VirtualService, DestinationRule, Gateway API), see Istio Traffic Management.


Envoy Request Processing Pipeline

Envoy processes every request through a pipeline of four core abstractions: Listener β†’ Filter Chain β†’ Router β†’ Cluster β†’ Endpoint. Understanding this model is essential for debugging Istio because every VirtualService and DestinationRule maps directly to these Envoy concepts.

                    Incoming Connection
                           β”‚
                           β–Ό
                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                   β”‚   LISTENER    β”‚  Binds to IP:port
                   β”‚  (LDS config) β”‚  e.g., 0.0.0.0:15006
                   β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β–Ό
                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                β”‚  FILTER CHAIN MATCH β”‚  Match on dest IP, port, SNI,
                β”‚                     β”‚  ALPN, transport protocol
                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β–Ό            β–Ό            β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚  Filter  β”‚ β”‚  Filter  β”‚ β”‚  Filter  β”‚  Network filters:
        β”‚  Chain 1 β”‚ β”‚  Chain 2 β”‚ β”‚  Chain N β”‚  - TCP proxy
        β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  - HTTP conn manager
             β”‚                                    - Authz filter
             β–Ό                                    - RBAC filter
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                            - WASM filters
     β”‚ HTTP FILTERS  β”‚
     β”‚               β”‚
     β”‚ - Router      β”‚ ◄── Uses RDS route config
     β”‚ - Fault       β”‚
     β”‚ - CORS        β”‚
     β”‚ - Lua/WASM    β”‚
     β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚  Route match (host + path + headers)
             β–Ό
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚   CLUSTER     β”‚  Logical group of endpoints
     β”‚  (CDS config) β”‚  e.g., "outbound|8080||reviews.default.svc.cluster.local"
     β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
             β–Ό
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚  LOAD BALANCERβ”‚  Round-robin, least-request,
     β”‚               β”‚  random, ring-hash, Maglev
     β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
             β–Ό
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚   ENDPOINT    β”‚  Actual pod IP:port
     β”‚  (EDS config) β”‚  e.g., 10.48.2.15:8080
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

How Envoy Processes a Request (Step by Step)

  1. Listener accepts connection β€” The VirtualInbound (15006) or VirtualOutbound (15001) listener accepts the redirected connection
  2. Filter chain selection β€” Envoy examines the original destination IP/port (preserved by iptables REDIRECT via SO_ORIGINAL_DST) and selects the matching filter chain
  3. Network filters execute β€” For HTTP traffic, the HttpConnectionManager (HCM) filter parses HTTP and runs HTTP filter chains
  4. Route matching β€” The Router filter matches the request against route configurations (from RDS). A route entry specifies which cluster to forward to
  5. Cluster selection β€” The matched route points to a cluster. The cluster has a load balancing policy and health check configuration
  6. Endpoint selection β€” EDS provides the list of healthy endpoints. The load balancer picks one
  7. Connection to upstream β€” Envoy opens (or reuses) a connection to the selected endpoint. If mTLS is configured, Envoy performs a TLS handshake using certificates from SDS

The VirtualOutbound Listener (Port 15001)

This listener uses a special useOriginalDst: true setting. Instead of handling traffic itself, it inspects the original destination (before iptables redirected it) and hands the connection off to the listener that matches that destination. If no specific listener exists, it forwards through a passthrough cluster (direct connection to the original destination).

The VirtualInbound Listener (Port 15006)

This listener has multiple filter chains, each matching a specific destination port. For example, if the application listens on port 8080, there will be a filter chain matching destination port 8080 with the appropriate protocol-specific filters (HTTP or TCP).


Threading Model

Envoy uses a multi-threaded architecture with a strict thread-local design that avoids locks on the hot path. There are three categories of threads:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        ENVOY THREADING MODEL                                 β”‚
β”‚                                                                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚                        MAIN THREAD                                      β”‚  β”‚
β”‚  β”‚                                                                         β”‚  β”‚
β”‚  β”‚  - Startup / shutdown coordination                                      β”‚  β”‚
β”‚  β”‚  - xDS API processing (receives config from istiod)                     β”‚  β”‚
β”‚  β”‚  - Runtime config reloads                                               β”‚  β”‚
β”‚  β”‚  - Stats flushing (periodic aggregation from workers)                   β”‚  β”‚
β”‚  β”‚  - Admin API server (port 15000)                                        β”‚  β”‚
β”‚  β”‚  - Cluster / listener management (creates, updates, drains)             β”‚  β”‚
β”‚  β”‚                                                                         β”‚  β”‚
β”‚  β”‚  Does NOT handle any data-plane traffic                                 β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                               β”‚                                              β”‚
β”‚                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                   β”‚
β”‚                    β”‚  Thread-Local Store  β”‚  Config snapshots pushed          β”‚
β”‚                    β”‚   (TLS mechanism)    β”‚  from main β†’ workers via          β”‚
β”‚                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  read-copy-update (RCU)           β”‚
β”‚                               β”‚                                              β”‚
β”‚          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                         β”‚
β”‚          β–Ό                    β–Ό                    β–Ό                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”‚
β”‚  β”‚  WORKER       β”‚    β”‚  WORKER       β”‚    β”‚  WORKER       β”‚                  β”‚
β”‚  β”‚  THREAD 0     β”‚    β”‚  THREAD 1     β”‚    β”‚  THREAD N     β”‚                  β”‚
β”‚  β”‚               β”‚    β”‚               β”‚    β”‚               β”‚                  β”‚
β”‚  β”‚  - Own event  β”‚    β”‚  - Own event  β”‚    β”‚  - Own event  β”‚                  β”‚
β”‚  β”‚    loop       β”‚    β”‚    loop       β”‚    β”‚    loop       β”‚                  β”‚
β”‚  β”‚    (libevent) β”‚    β”‚    (libevent) β”‚    β”‚    (libevent) β”‚                  β”‚
β”‚  β”‚               β”‚    β”‚               β”‚    β”‚               β”‚                  β”‚
β”‚  β”‚  - Owns its   β”‚    β”‚  - Owns its   β”‚    β”‚  - Owns its   β”‚                 β”‚
β”‚  β”‚    connectionsβ”‚    β”‚    connectionsβ”‚    β”‚    connectionsβ”‚                  β”‚
β”‚  β”‚               β”‚    β”‚               β”‚    β”‚               β”‚                  β”‚
β”‚  β”‚  - Listener   β”‚    β”‚  - Listener   β”‚    β”‚  - Listener   β”‚                 β”‚
β”‚  β”‚    filter     β”‚    β”‚    filter     β”‚    β”‚    filter     β”‚                  β”‚
β”‚  β”‚    chains     β”‚    β”‚    chains     β”‚    β”‚    chains     β”‚                  β”‚
β”‚  β”‚               β”‚    β”‚               β”‚    β”‚               β”‚                  β”‚
β”‚  β”‚  - Upstream   β”‚    β”‚  - Upstream   β”‚    β”‚  - Upstream   β”‚                 β”‚
β”‚  β”‚    conn pools β”‚    β”‚    conn pools β”‚    β”‚    conn pools β”‚                  β”‚
β”‚  β”‚  (per-worker) β”‚    β”‚  (per-worker) β”‚    β”‚  (per-worker) β”‚                 β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β”‚
β”‚                                                                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚                     FILE FLUSH THREAD(S)                                β”‚  β”‚
β”‚  β”‚  - Writes access logs to disk                                           β”‚  β”‚
β”‚  β”‚  - Separate from workers to avoid blocking on I/O                       β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key design principles:

  • Non-blocking event loop: Each worker thread runs a libevent-based event loop. All I/O (socket reads/writes, DNS, TLS handshakes) is asynchronous. A single worker can handle thousands of concurrent connections without blocking.
  • Connection affinity: Once the kernel accepts a connection on a listener socket, it is assigned to one worker thread for its entire lifetime. All downstream and corresponding upstream processing happen on that same thread β€” no cross-thread locking needed.
  • Thread-Local Storage (TLS): The main thread distributes configuration updates (new clusters, routes, secrets) to workers using a read-copy-update mechanism. Each worker holds a thread-local read-only snapshot of the config. Workers never contend on shared mutable state.
  • Worker count: Defaults to the number of hardware threads (cores). In Istio sidecar mode, pilot-agent typically sets --concurrency to match the CPU limit of the istio-proxy container (or 2 by default if no limit is set).

The kernel distributes new connections across worker threads using SO_REUSEPORT β€” each worker has its own listener socket bound to the same address, and the kernel load-balances incoming SYN packets across them.


Hot Restart

Envoy supports zero-downtime binary upgrades and config reloads through a hot restart mechanism. This is how pilot-agent can restart Envoy without dropping connections:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    HOT RESTART SEQUENCE                         β”‚
β”‚                                                                β”‚
β”‚  Time ──────────────────────────────────────────────────►      β”‚
β”‚                                                                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”‚
β”‚  β”‚  Old Envoy Process (epoch N)                     β”‚          β”‚
β”‚  β”‚                                                   β”‚          β”‚
β”‚  β”‚  Accepting ──► Draining ──────────────► Exit      β”‚          β”‚
β”‚  β”‚  connections    (stops accepting new    (after     β”‚          β”‚
β”‚  β”‚                  connections, finishes  drain      β”‚          β”‚
β”‚  β”‚                  in-flight requests)    period)    β”‚          β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β”‚
β”‚             β”‚                                                  β”‚
β”‚             β”‚  1. New process starts                           β”‚
β”‚             β”‚  2. Connects to old process via                  β”‚
β”‚             β”‚     Unix domain socket                           β”‚
β”‚             β”‚  3. Shared memory region for                     β”‚
β”‚             β”‚     stats counters (so counters                  β”‚
β”‚             β”‚     don't reset across restarts)                 β”‚
β”‚             β”‚  4. Old process transfers listen                 β”‚
β”‚             β”‚     sockets via SCM_RIGHTS                       β”‚
β”‚             β–Ό                                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”‚
β”‚  β”‚  New Envoy Process (epoch N+1)                   β”‚          β”‚
β”‚  β”‚                                                   β”‚          β”‚
β”‚  β”‚  Initializing ──► Accepting connections           β”‚          β”‚
β”‚  β”‚  (receives        (takes over listener            β”‚          β”‚
β”‚  β”‚   sockets,         sockets, serves                β”‚          β”‚
β”‚  β”‚   loads config)    new connections)                β”‚          β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The hot restart process in detail:

  1. pilot-agent launches a new Envoy process with an incremented restart epoch.
  2. The new process connects to the old process over a Unix domain socket (the β€œhot restart RPC” channel).
  3. The old process transfers its listener sockets to the new process using Unix SCM_RIGHTS (file descriptor passing). This allows the new process to immediately begin accepting connections on the same addresses.
  4. Both processes share a shared memory region that holds stats counters. This ensures metric counters (e.g., total requests served) are not reset across restarts.
  5. The old process enters a drain period (configurable via --drain-time-s, default 600s in Istio). During draining, the old process stops accepting new connections but continues processing existing in-flight requests to completion.
  6. Once the drain period expires (or all connections close), the old process exits.

Note: In Istio sidecar mode, hot restart is less commonly triggered because Envoy receives configuration changes dynamically via xDS without needing a restart. Hot restart is more relevant when the Envoy binary itself is upgraded or when pilot-agent detects a crash and relaunches Envoy.


Filter Types in Depth

Envoy’s extensibility is built around a three-tier filter model. Filters execute in a chain, and each filter can inspect, modify, or terminate the request/response at its stage.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       ENVOY FILTER PIPELINE                               β”‚
β”‚                                                                           β”‚
β”‚  Connection arrives at listener                                           β”‚
β”‚         β”‚                                                                 β”‚
β”‚         β–Ό                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚  LISTENER FILTERS  (L3/L4, pre-connection)                          β”‚ β”‚
β”‚  β”‚                                                                      β”‚ β”‚
β”‚  β”‚  Execute BEFORE a filter chain is selected.                          β”‚ β”‚
β”‚  β”‚  Can inspect raw bytes, TLS ClientHello, proxy protocol header.      β”‚ β”‚
β”‚  β”‚                                                                      β”‚ β”‚
β”‚  β”‚  Examples:                                                            β”‚ β”‚
β”‚  β”‚  - tls_inspector: reads SNI + ALPN from ClientHello (no decryption) β”‚ β”‚
β”‚  β”‚  - http_inspector: sniffs first bytes to detect HTTP vs non-HTTP    β”‚ β”‚
β”‚  β”‚  - proxy_protocol: reads PROXY protocol header (HAProxy format)      β”‚ β”‚
β”‚  β”‚  - original_dst: recovers original destination (iptables redirect)   β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                        β”‚                                  β”‚
β”‚         Filter chain selected based on SNI/port/protocol                 β”‚
β”‚                                        β”‚                                  β”‚
β”‚                                        β–Ό                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚  NETWORK FILTERS  (L4, connection-level)                             β”‚ β”‚
β”‚  β”‚                                                                      β”‚ β”‚
β”‚  β”‚  Operate on raw TCP byte streams. Read/write data on the             β”‚ β”‚
β”‚  β”‚  downstream connection. Can be read, write, or read/write filters.   β”‚ β”‚
β”‚  β”‚                                                                      β”‚ β”‚
β”‚  β”‚  Examples:                                                            β”‚ β”‚
β”‚  β”‚  - tcp_proxy: forwards TCP to upstream cluster (terminal filter)     β”‚ β”‚
β”‚  β”‚  - http_connection_manager (HCM): parses HTTP, runs HTTP filters    β”‚ β”‚
β”‚  β”‚  - mongo_proxy: MongoDB wire protocol aware proxy                    β”‚ β”‚
β”‚  β”‚  - mysql_proxy: MySQL wire protocol aware proxy                      β”‚ β”‚
β”‚  β”‚  - redis_proxy: Redis protocol aware proxy                           β”‚ β”‚
β”‚  β”‚  - rbac: L4 RBAC enforcement (source IP, port)                      β”‚ β”‚
β”‚  β”‚  - ext_authz: L4 external authorization                             β”‚ β”‚
β”‚  β”‚                                                                      β”‚ β”‚
β”‚  β”‚  The last network filter in the chain must be a TERMINAL filter      β”‚ β”‚
β”‚  β”‚  (e.g., tcp_proxy or http_connection_manager).                       β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                        β”‚                                  β”‚
β”‚         (Only if HCM is in the chain)  β”‚                                  β”‚
β”‚                                        β–Ό                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚  HTTP FILTERS  (L7, request/response-level)                          β”‚ β”‚
β”‚  β”‚                                                                      β”‚ β”‚
β”‚  β”‚  Operate on decoded HTTP requests/responses. Each filter has         β”‚ β”‚
β”‚  β”‚  decodeHeaders/decodeData (request path) and                         β”‚ β”‚
β”‚  β”‚  encodeHeaders/encodeData (response path) callbacks.                 β”‚ β”‚
β”‚  β”‚                                                                      β”‚ β”‚
β”‚  β”‚  Request flow (decode):                                               β”‚ β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚ β”‚
β”‚  β”‚  β”‚ CORS   β”œβ”€β–Ίβ”‚ fault  β”œβ”€β–Ίβ”‚ RBAC   β”œβ”€β–Ίβ”‚ext_    β”œβ”€β–Ίβ”‚ router β”‚       β”‚ β”‚
β”‚  β”‚  β”‚        β”‚  β”‚ inject β”‚  β”‚        β”‚  β”‚authz   β”‚  β”‚(terminalβ”‚       β”‚ β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚ β”‚
β”‚  β”‚                                                                      β”‚ β”‚
β”‚  β”‚  Response flow (encode):  ◄── reverse order ──                       β”‚ β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚ β”‚
β”‚  β”‚  β”‚ router β”œβ”€β–Ίβ”‚ext_    β”œβ”€β–Ίβ”‚ RBAC   β”œβ”€β–Ίβ”‚ fault  β”œβ”€β–Ίβ”‚ CORS   β”‚       β”‚ β”‚
β”‚  β”‚  β”‚        β”‚  β”‚authz   β”‚  β”‚        β”‚  β”‚ inject β”‚  β”‚        β”‚       β”‚ β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚ β”‚
β”‚  β”‚                                                                      β”‚ β”‚
β”‚  β”‚  The router filter MUST be the last HTTP filter (terminal).          β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Decode vs Encode execution model: HTTP filters implement two callback paths. During the decode (request) phase, filters execute in the order they appear in the chain. During the encode (response) phase, filters execute in reverse order. Any filter can stop the chain β€” for example, the RBAC filter can return a 403 during decode and skip all downstream filters, including the router. The router filter initiates the upstream connection and is always last in the decode path.

Built-in HTTP Filters Used by Istio

FilterEnvoy NamePurposeIstio CRD Mapping
Routerenvoy.filters.http.routerRoutes request to upstream cluster based on RDS. Terminal filter.VirtualService routes
RBACenvoy.filters.http.rbacEvaluates allow/deny rules based on source, path, headers, JWT claimsAuthorizationPolicy
Fault Injectionenvoy.filters.http.faultInjects delays or aborts (HTTP errors) for chaos testingVirtualService fault
ext_authzenvoy.filters.http.ext_authzDelegates authz decision to external gRPC/HTTP serviceAuthorizationPolicy (CUSTOM action)
CORSenvoy.filters.http.corsHandles CORS preflight and response headersVirtualService corsPolicy
Luaenvoy.filters.http.luaInline Lua scripting for custom request/response manipulationEnvoyFilter
Wasmenvoy.filters.http.wasmRuns WebAssembly plugins for custom logicWasmPlugin CRD
Rate Limitenvoy.filters.http.ratelimitExternal rate limit service integrationEnvoyFilter (or Istio rate limit API)
Compressorenvoy.filters.http.compressorResponse body compression (gzip, brotli, zstd)EnvoyFilter
JWT Authenticationenvoy.filters.http.jwt_authnValidates JWT tokens against JWKS endpointsRequestAuthentication
gRPC Statsenvoy.filters.http.grpc_statsEmits gRPC-specific metrics (request/response message counts)Automatic for gRPC traffic

Connection Pooling

Envoy manages upstream connection pools on a per-cluster, per-worker-thread basis. The pooling behavior differs significantly between HTTP/1.1 and HTTP/2:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              CONNECTION POOL ARCHITECTURE                           β”‚
β”‚                                                                    β”‚
β”‚  Worker Thread 0                  Worker Thread 1                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚  β”‚  Cluster: reviews:8080  β”‚      β”‚  Cluster: reviews:8080  β”‚      β”‚
β”‚  β”‚                          β”‚      β”‚                          β”‚      β”‚
β”‚  β”‚  HTTP/1.1 pool:          β”‚      β”‚  HTTP/1.1 pool:          β”‚      β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”  β”‚      β”‚  β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”         β”‚      β”‚
β”‚  β”‚  β”‚connβ”‚ β”‚connβ”‚ β”‚connβ”‚  β”‚      β”‚  β”‚connβ”‚ β”‚connβ”‚         β”‚      β”‚
β”‚  β”‚  β”‚ 1  β”‚ β”‚ 2  β”‚ β”‚ 3  β”‚  β”‚      β”‚  β”‚ 1  β”‚ β”‚ 2  β”‚         β”‚      β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”˜  β”‚      β”‚  β””β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”˜         β”‚      β”‚
β”‚  β”‚  (1 request per conn)   β”‚      β”‚  (1 request per conn)   β”‚      β”‚
β”‚  β”‚                          β”‚      β”‚                          β”‚      β”‚
β”‚  β”‚  HTTP/2 pool:            β”‚      β”‚  HTTP/2 pool:            β”‚      β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚      β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚      β”‚
β”‚  β”‚  β”‚ conn 1             β”‚ β”‚      β”‚  β”‚ conn 1             β”‚ β”‚      β”‚
β”‚  β”‚  β”‚ β”œβ”€ stream 1        β”‚ β”‚      β”‚  β”‚ β”œβ”€ stream 1        β”‚ β”‚      β”‚
β”‚  β”‚  β”‚ β”œβ”€ stream 2        β”‚ β”‚      β”‚  β”‚ β”œβ”€ stream 2        β”‚ β”‚      β”‚
β”‚  β”‚  β”‚ β”œβ”€ stream 3        β”‚ β”‚      β”‚  β”‚ └─ stream 3        β”‚ β”‚      β”‚
β”‚  β”‚  β”‚ └─ stream 4        β”‚ β”‚      β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚      β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚      β”‚  (many requests over 1  β”‚      β”‚
β”‚  β”‚  (multiplexed streams)  β”‚      β”‚   connection)            β”‚      β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
ProtocolPooling BehaviorConcurrency
HTTP/1.1One request at a time per connection. Envoy opens multiple connections to the same endpoint to achieve parallelism. Connections are kept alive and reused for subsequent requests.Controlled by maxConnectionsPerEndpoint (circuit breaker max_connections)
HTTP/2Multiple concurrent streams (requests) multiplexed over a single TCP connection per worker per endpoint. Envoy typically opens just one connection per worker per upstream host.Controlled by max_concurrent_streams (default 2147483647 β€” practically unlimited) and max_requests circuit breaker

Connection pools are not shared across worker threads. Each worker independently manages its own pools. This means total connections to a single upstream host equals connections_per_worker * num_workers.

Circuit breaker integration: Connection pools are bounded by the circuit breaker thresholds configured via DestinationRule’s connectionPool settings. When thresholds are hit (e.g., maxConnections, maxPendingRequests, maxRequestsPerConnection), Envoy immediately returns a 503 with the flag UO (upstream overflow) rather than queueing the request.


Health Checking

Envoy supports two complementary mechanisms for determining endpoint health:

Active Health Checking

Envoy periodically sends probe requests to each upstream endpoint and marks unhealthy endpoints as unavailable. This is configured per-cluster and operates independently of Kubernetes liveness/readiness probes.

ParameterDescriptionDefault
intervalTime between health check attempts5s (Istio default varies)
timeoutTime to wait for a health check response1s
unhealthy_thresholdConsecutive failures before marking unhealthy2
healthy_thresholdConsecutive successes before marking healthy again1

Health check types: HTTP (send GET to a path, check status code), TCP (attempt connection), gRPC (use grpc.health.v1.Health service).

Note: In Istio, active health checking is not enabled by default for sidecar proxies. Istio relies on Kubernetes readiness probes to remove unready pods from Endpoints, which then propagates to Envoy via EDS. Active health checks can be configured via DestinationRule’s outlierDetection or via EnvoyFilter for advanced cases. The Istio Gateway deployments are more likely to use active health checks.

Passive Health Checking (Outlier Detection)

Outlier detection monitors real traffic responses and ejects endpoints that show signs of failure β€” no extra probe traffic needed. This is configured via DestinationRule.trafficPolicy.outlierDetection and maps directly to Envoy’s outlier_detection cluster config.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   OUTLIER DETECTION FLOW                       β”‚
β”‚                                                               β”‚
β”‚  Request to upstream endpoint                                 β”‚
β”‚         β”‚                                                     β”‚
β”‚         β–Ό                                                     β”‚
β”‚  Response received (or connection error / timeout)            β”‚
β”‚         β”‚                                                     β”‚
β”‚         β–Ό                                                     β”‚
β”‚  Envoy tracks per-endpoint:                                   β”‚
β”‚  - consecutive 5xx count                                      β”‚
β”‚  - consecutive gateway errors (502, 503, 504)                 β”‚
β”‚  - consecutive local-origin failures (connect timeout, reset) β”‚
β”‚  - success rate (over a sliding window)                       β”‚
β”‚         β”‚                                                     β”‚
β”‚         β–Ό                                                     β”‚
β”‚  Threshold exceeded?                                          β”‚
β”‚    β”œβ”€β”€ No  β†’ continue routing to this endpoint                β”‚
β”‚    └── Yes β†’ EJECT endpoint for `baseEjectionTime`            β”‚
β”‚              (each subsequent ejection doubles the duration)   β”‚
β”‚              Ejected endpoint receives no traffic              β”‚
β”‚         β”‚                                                     β”‚
β”‚         β–Ό                                                     β”‚
β”‚  After ejection period: endpoint re-enters the pool           β”‚
β”‚  Next failure β†’ ejected for 2x the base time, etc.           β”‚
β”‚                                                               β”‚
β”‚  Safety valve: maxEjectionPercent (default 10%)               β”‚
β”‚  Never eject more than this % of the cluster at once          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
# DestinationRule with outlier detection
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: reviews-outlier
spec:
  host: reviews.default.svc.cluster.local
  trafficPolicy:
    outlierDetection:
      consecutive5xxErrors: 3        # eject after 3 consecutive 5xx
      interval: 10s                  # check window
      baseEjectionTime: 30s          # first ejection lasts 30s
      maxEjectionPercent: 50         # allow ejecting up to 50% of endpoints

Key difference from Kubernetes probes: Kubernetes liveness/readiness probes determine whether a pod should be restarted or removed from Service endpoints globally. Envoy outlier detection is per-proxy β€” one Envoy might eject an endpoint that other Envoys still consider healthy, because the failure might be path-dependent (e.g., network partition between specific nodes).


Access Logging

Envoy access logs record per-request metadata for debugging and auditing. In Istio, access logging is configured globally via MeshConfig or per-workload via the Telemetry API.

Enabling access logs globally (via MeshConfig):

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  meshConfig:
    accessLogFile: /dev/stdout            # file-based (logs to container stdout)
    accessLogEncoding: JSON               # TEXT or JSON
    accessLogFormat: ""                    # empty = default format

Default access log format (TEXT mode):

[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%"
%RESPONSE_CODE% %RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS% %CONNECTION_TERMINATION_DETAILS%
"%UPSTREAM_TRANSPORT_FAILURE_REASON%" %BYTES_RECEIVED% %BYTES_SENT%
%DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%
"%REQ(X-FORWARDED-FOR)%" "%REQ(USER-AGENT)%"
"%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%"
%UPSTREAM_CLUSTER% %UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS%
%DOWNSTREAM_REMOTE_ADDRESS% %REQUESTED_SERVER_NAME% %ROUTE_NAME%

Key response flags to watch for in logs:

FlagMeaning
UHNo healthy upstream hosts
UFUpstream connection failure
UOUpstream overflow (circuit breaker tripped)
NRNo route configured
URXUpstream retry limit exceeded
DCDownstream connection termination
RLRate limited
UAEXUnauthorized (ext_authz denied)
RLSERate limit service error

gRPC Access Log Service (ALS): Instead of (or in addition to) file-based logging, Envoy can stream access logs to a remote gRPC service. This enables centralized log collection without relying on a sidecar log shipper:

Envoy β†’ gRPC stream β†’ Access Log Service (ALS) β†’ Storage backend
                        (e.g., OpenTelemetry       (Elasticsearch,
                         Collector, custom)          BigQuery, etc.)

Configure via MeshConfig:

meshConfig:
  accessLogFile: ""                     # disable file logging
  defaultConfig:
    envoyAccessLogService:
      address: als-collector.istio-system:9090

See also


Interview Prep

Q: What is the difference between Istio and Envoy?

A: Envoy is a standalone, high-performance L4/L7 proxy written in C++ by Lyft. It handles the actual data plane work: accepting connections, load balancing, applying retries/timeouts, terminating TLS, and collecting metrics. Envoy has no opinion about how it gets configured β€” it exposes the xDS API for dynamic configuration.

Istio is the control plane that manages a fleet of Envoy proxies. It watches Kubernetes resources (Services, Endpoints, VirtualService, DestinationRule, PeerAuthentication), translates them into Envoy-native configuration, and pushes that configuration to every sidecar proxy via xDS. Istio also acts as a Certificate Authority, issuing SPIFFE X.509 certificates to each proxy for mTLS.

The istio-proxy container in each pod contains the Envoy binary plus pilot-agent, a helper process that generates Envoy’s bootstrap config, manages its lifecycle, fetches certificates from istiod, and serves them to Envoy via the local SDS API.


Q: Explain Envoy’s threading model. Why doesn’t it use a thread-per-connection approach?

A: Envoy uses a multi-threaded, non-blocking event-loop architecture:

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  Main Thread   β”‚
                    β”‚  - xDS updates β”‚
                    β”‚  - Admin API   β”‚
                    β”‚  - Stats flush β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚ RCU (thread-local snapshots)
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β–Ό             β–Ό             β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚ Worker 0 β”‚ β”‚ Worker 1 β”‚ β”‚ Worker N β”‚
        β”‚ (event   β”‚ β”‚ (event   β”‚ β”‚ (event   β”‚
        β”‚  loop)   β”‚ β”‚  loop)   β”‚ β”‚  loop)   β”‚
        β”‚          β”‚ β”‚          β”‚ β”‚          β”‚
        β”‚ owns:    β”‚ β”‚ owns:    β”‚ β”‚ owns:    β”‚
        β”‚ - conns  β”‚ β”‚ - conns  β”‚ β”‚ - conns  β”‚
        β”‚ - pools  β”‚ β”‚ - pools  β”‚ β”‚ - pools  β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The main thread handles control plane operations (xDS updates, admin API, stats flushing) but never touches data-plane traffic. Worker threads each run an independent libevent event loop and own their connections for the entire connection lifetime. The kernel distributes incoming connections across workers using SO_REUSEPORT.

A thread-per-connection model would waste memory on idle connections and suffer from context-switching overhead at high connection counts. Envoy’s event-loop model can handle thousands of concurrent connections per worker thread because all I/O is non-blocking. Configuration updates flow from the main thread to workers via a read-copy-update (RCU) mechanism using thread-local storage β€” workers hold read-only config snapshots and never contend on shared mutable state.


Q: How does Envoy’s hot restart work? Why is it needed?

A: Hot restart enables zero-downtime Envoy binary upgrades. The sequence:

  1. pilot-agent starts a new Envoy process with an incremented restart epoch.
  2. The new process connects to the old process via a Unix domain socket.
  3. The old process transfers its listener sockets using SCM_RIGHTS (Unix file descriptor passing).
  4. Both processes share a shared-memory region for stats counters (so counters persist across restarts).
  5. The old process enters drain mode β€” stops accepting new connections but finishes in-flight requests.
  6. After the drain period (default 600s in Istio), the old process exits.

In practice, hot restart is rarely triggered in Istio sidecar mode because xDS delivers config changes dynamically without restarts. It is more relevant for Envoy binary upgrades or crash recovery by pilot-agent.


Q: What are the three types of Envoy filters? In what order do HTTP filters execute on requests vs responses?

A: The three filter tiers:

  1. Listener filters (L3/L4, pre-connection): Run before filter chain selection. Inspect raw connection bytes. Examples: tls_inspector (reads SNI from ClientHello), http_inspector (sniffs for HTTP).
  2. Network filters (L4, connection-level): Operate on TCP byte streams after filter chain selection. Must end with a terminal filter like tcp_proxy or http_connection_manager.
  3. HTTP filters (L7, request/response): Operate on parsed HTTP. Only active when http_connection_manager is the network filter.

HTTP filter execution order:

  Request (decode):   filter_1 β†’ filter_2 β†’ ... β†’ router (terminal)
  Response (encode):  router β†’ ... β†’ filter_2 β†’ filter_1 (REVERSED)

On the decode path, filters execute in chain order. On the encode path, they execute in reverse. Any filter can short-circuit the chain β€” for example, the RBAC filter returning 403 stops decode processing and the response goes back through the encode path.


Q: How does outlier detection (passive health checking) differ from active health checking and Kubernetes readiness probes?

A:

AspectActive Health CheckOutlier DetectionK8s Readiness Probe
MechanismEnvoy sends periodic probe requestsEnvoy monitors real traffic responseskubelet sends periodic probes
ScopePer-proxy decisionPer-proxy decisionGlobal (affects Endpoints for all consumers)
Extra trafficYes (synthetic probes)No (uses real requests)Yes (synthetic probes)
Reaction timeDepends on intervalImmediate (on Nth failure)Depends on interval + failure threshold
RecoveryAutomatic after healthy thresholdAutomatic after ejection time expiresAutomatic after success threshold
GranularityPer-endpointPer-endpointPer-pod

The key difference: Kubernetes readiness probes remove a pod from the global Service Endpoints (affecting all consumers), while outlier detection is local to each Envoy proxy β€” one proxy may eject an endpoint while another still considers it healthy. This can happen when failures are path-dependent (e.g., network issues between specific nodes).

In Istio, active health checking is not enabled by default. The primary health signal comes from Kubernetes readiness probes (propagated via EDS), supplemented by outlier detection configured through DestinationRule.