Everything for Envoy in one place — pick a section below. 15 reviewed items across 4 content types.
Quick Answer
Envoy Proxy is an open-source, high-performance L7 proxy and communication bus designed for large microservices architectures. Originally built at Lyft, it provides service discovery, load balancing, TLS termination, HTTP/2 and gRPC proxying, circuit breaking, health checks, and rich observability out of the box.
Detailed Answer
Think of Envoy Proxy as a universal translator and traffic controller that sits between every microservice in your infrastructure. Instead of each service having to handle network concerns like retries, timeouts, load balancing, and observability on its own, Envoy takes over all of these responsibilities transparently, allowing developers to focus purely on business logic.
Envoy was originally created at Lyft in 2016 to solve the challenges of managing network communication across hundreds of microservices. Before Envoy, Lyft's services used a patchwork of language-specific libraries and ad-hoc proxy configurations to handle service-to-service communication. This led to inconsistent behavior, difficult debugging, and fragmented observability. Envoy was designed from the ground up as a self-contained process that runs alongside every service, intercepting all inbound and outbound network traffic. It was donated to the Cloud Native Computing Foundation (CNCF) in 2017 and graduated in 2018, becoming one of the most widely adopted infrastructure components in the cloud-native ecosystem.
At its core, Envoy is a L3/L4 network proxy with an L7 filter architecture. This means it can handle raw TCP connections at the network and transport layers while also understanding and manipulating application-layer protocols like HTTP/1.1, HTTP/2, and gRPC. Envoy is written in C++ for maximum performance, uses an event-driven, non-blocking architecture similar to Nginx, and supports hot restarts that allow configuration updates without dropping any active connections. The proxy is fully dynamic, meaning its entire configuration can be updated at runtime through a set of discovery service APIs called xDS, without requiring restarts or reloads. This dynamic nature is what makes Envoy the data plane of choice for service meshes like Istio, Consul Connect, and AWS App Mesh.
In production environments, Envoy is deployed in several patterns. The most common is the sidecar pattern, where an Envoy instance runs alongside each microservice in the same pod or VM. In this configuration, all traffic to and from the payments-api service passes through its local Envoy sidecar, which handles TLS termination, retries, circuit breaking, rate limiting, and telemetry collection. Envoy is also used as an edge proxy or API gateway, sitting at the ingress point of a cluster and routing external traffic to internal services. Companies like Airbnb, Pinterest, Stripe, and Salesforce use Envoy at massive scale, processing millions of requests per second. The proxy emits detailed statistics for every connection and request, integrates with distributed tracing systems like Zipkin and Jaeger, and provides an admin interface for runtime debugging.
A critical point that differentiates Envoy from traditional proxies is its API-driven configuration model. While Nginx and HAProxy rely on static configuration files that require reloads, Envoy can discover listeners, routes, clusters, and endpoints dynamically through its xDS APIs. This makes Envoy ideal for environments where services scale up, scale down, and change frequently. The combination of high performance, dynamic configuration, protocol awareness, and rich observability is why Envoy has become the foundational building block for modern service mesh architectures and cloud-native networking.
Code Example
# Pull the official Envoy Docker image docker pull envoyproxy/envoy:v1.28-latest # Run Envoy with a custom configuration file docker run -d --name envoy-proxy \ -p 10000:10000 \ -p 9901:9901 \ -v $(pwd)/envoy.yaml:/etc/envoy/envoy.yaml \ envoyproxy/envoy:v1.28-latest # Verify Envoy is running and healthy curl http://localhost:9901/ready # Check Envoy version information docker exec envoy-proxy envoy --version # View Envoy process statistics curl http://localhost:9901/stats # View active clusters and their health curl http://localhost:9901/clusters # Send a test request through Envoy listener on port 10000 curl -v http://localhost:10000/
Interview Tip
A junior engineer typically says 'Envoy is a proxy like Nginx' without explaining what makes it different. Interviewers expect you to highlight the dynamic xDS configuration model, the sidecar deployment pattern, and the rich observability features that set Envoy apart from traditional proxies. Mention that Envoy was built specifically for microservices at Lyft and is now the data plane for Istio, Consul Connect, and AWS App Mesh. If you can reference specific features like hot restart, HTTP/2 native support, and gRPC proxying, you demonstrate deeper understanding. Explain how Envoy solves the problem of inconsistent networking logic scattered across services written in different languages.
◈ Architecture Diagram
┌─────────────────────────────────────────────────┐ │ Microservices with Envoy Proxy │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ payments-api │ │ order-service│ │ │ │ ┌────────┐ │ │ ┌────────┐ │ │ │ │ │ App │ │ │ │ App │ │ │ │ │ └───┬────┘ │ │ └───┬────┘ │ │ │ │ │ │ │ │ │ │ │ │ ┌───┴────┐ │ │ ┌───┴────┐ │ │ │ │ │ Envoy │──┼───────┼──│ Envoy │ │ │ │ │ │Sidecar │ │ │ │Sidecar │ │ │ │ │ └────────┘ │ │ └────────┘ │ │ │ └──────────────┘ └──────────────┘ │ │ │ │ │ │ └───────────┬───────────┘ │ │ ┌──────┴──────┐ │ │ │ Observability│ │ │ │ Metrics/Logs │ │ │ └─────────────┘ │ └─────────────────────────────────────────────────┘
💬 Comments
Quick Answer
Envoy is designed for dynamic, microservices environments with API-driven configuration (xDS), native HTTP/2 and gRPC support, and built-in observability. Nginx and HAProxy excel at static, high-throughput scenarios with file-based configuration. Choose Envoy when you need dynamic service discovery and service mesh integration.
Detailed Answer
Think of Nginx and HAProxy as experienced highway toll operators who are extremely efficient at directing traffic along pre-planned routes. Envoy is more like a GPS navigation system that continuously recalculates routes based on real-time traffic conditions, road closures, and new destinations appearing on the map. Both approaches work, but they excel in different scenarios.
Nginx was originally built as a high-performance web server and reverse proxy, optimized for serving static content and handling large numbers of concurrent connections. HAProxy was purpose-built as a load balancer with exceptional performance for TCP and HTTP traffic. Both tools use static configuration files that require a reload or restart when changes are made. While Nginx Plus and newer HAProxy versions have added some dynamic capabilities, their core architecture is fundamentally file-driven. Envoy, by contrast, was designed from day one with dynamic configuration as a first-class feature. Its entire configuration, including listeners, routes, clusters, and endpoints, can be updated at runtime through the xDS API without any restarts or connection drops.
The protocol support is another major differentiator. Envoy natively supports HTTP/1.1, HTTP/2, and gRPC as first-class protocols, with the ability to translate between them. For example, Envoy can accept an HTTP/1.1 request from a legacy client and forward it as HTTP/2 to the upstream service, or proxy gRPC traffic with full understanding of the protocol semantics. Nginx added HTTP/2 support later, and HAProxy's gRPC support is more limited. Envoy also supports advanced L7 protocols through its extensible filter chain, including MongoDB, MySQL, Redis, and Thrift protocol awareness, allowing it to parse and route application-specific traffic.
In terms of observability, Envoy generates detailed statistics for every upstream cluster, downstream connection, and individual request. It natively integrates with distributed tracing systems by propagating trace context headers and generating spans. It exports metrics in formats compatible with Prometheus, StatsD, and other monitoring backends. Nginx and HAProxy provide access logs and basic metrics, but they require additional modules or external tools to achieve the level of observability Envoy provides out of the box. Envoy's admin interface exposes runtime configuration, cluster health, statistics, and debugging endpoints, making it significantly easier to troubleshoot in production.
Choose Envoy when you are building a microservices architecture with dynamic service discovery, need a service mesh data plane, require native gRPC support, or need rich observability without additional tooling. Choose Nginx when you need a high-performance web server for static content, a well-established reverse proxy with extensive module ecosystem, or when your team has deep Nginx expertise. Choose HAProxy when you need a battle-tested TCP/HTTP load balancer with deterministic performance characteristics and simpler operational requirements. In practice, many organizations use Envoy as a sidecar proxy alongside Nginx or HAProxy at the edge, combining the strengths of each tool in different layers of their architecture.
Code Example
# Basic Envoy configuration (envoy.yaml) routing to order-service
# Static resources section defines listeners and clusters
static_resources:
# Listeners define where Envoy accepts connections
listeners:
# Listen on port 10000 for incoming HTTP traffic
- name: order_listener
# Bind address and port for this listener
address:
socket_address:
address: 0.0.0.0
port_value: 10000
# Filter chains process incoming connections
filter_chains:
- filters:
# HTTP connection manager handles HTTP traffic
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
# Stat prefix for metrics identification
stat_prefix: ingress_http
# Route configuration for this listener
route_config:
name: local_route
virtual_hosts:
# Virtual host matches requests by domain
- name: order_service_host
# Match all domains
domains: ["*"]
routes:
# Route all traffic to order-service cluster
- match:
prefix: "/"
route:
cluster: order-service
# HTTP filters applied to every request
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
# Clusters define upstream services
clusters:
# order-service cluster with two endpoints
- name: order-service
# Connect timeout for upstream connections
connect_timeout: 5s
# Cluster type STRICT_DNS resolves DNS names
type: STRICT_DNS
# Load balancing policy
lb_policy: ROUND_ROBIN
# Upstream endpoints for the cluster
load_assignment:
cluster_name: order-service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: order-service
port_value: 8080Interview Tip
A junior engineer typically lists features in a table without explaining when to choose which tool. Interviewers want to hear your reasoning about trade-offs. Focus on three key differentiators: dynamic configuration via xDS versus static files, native HTTP/2 and gRPC support, and built-in observability. Explain that Envoy excels in dynamic microservices environments while Nginx and HAProxy are better for static, high-throughput edge proxying. Mention that many production architectures use both: HAProxy or Nginx at the edge with Envoy as sidecar proxies inside the mesh. This shows architectural maturity rather than treating it as a binary choice.
◈ Architecture Diagram
┌─────────────────────────────────────────────────┐ │ Proxy Comparison │ │ │ │ Feature Envoy Nginx HAProxy │ │ ─────────────────────────────────────────────── │ │ Config Model xDS API Files Files │ │ HTTP/2 Native ✓ Partial Partial │ │ gRPC Native ✓ Limited Limited │ │ Hot Restart ✓ Reload Reload │ │ Observability Rich Basic Basic │ │ Service Mesh ✓ ✗ ✗ │ │ Static Content ✗ ✓ ✗ │ │ TCP LB ✓ ✓ ✓ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Envoy │ │ Nginx │ │ HAProxy │ │ │ │ Dynamic │ │ Static │ │ Static │ │ │ │ Sidecar │ │ Edge │ │ Edge │ │ │ └──────────┘ └──────────┘ └──────────┘ │ └─────────────────────────────────────────────────┘
💬 Comments
Quick Answer
Envoy's architecture consists of listeners (accept incoming connections), filter chains (process traffic), routes (match requests to destinations), and clusters (groups of upstream endpoints). Traffic flows from a downstream client through a listener, gets processed by filters, matched by routes, and forwarded to a cluster endpoint.
Detailed Answer
Think of Envoy like a large hotel. Listeners are the doors to the hotel, each accepting guests on different entrances. Filter chains are the check-in procedures that inspect and process each guest. Routes are the directory signs that tell guests which floor and room to go to based on their reservation. Clusters are the actual floors with available rooms, and endpoints are the individual rooms on each floor.
The listener is the first component in Envoy's request processing pipeline. A listener binds to a specific IP address and port, accepting incoming connections from downstream clients. Each Envoy instance can have multiple listeners, for example one on port 80 for HTTP traffic and another on port 443 for HTTPS. Listeners are defined with a name, a bind address, and one or more filter chains. When a connection arrives, the listener selects the appropriate filter chain based on connection properties like SNI (Server Name Indication) for TLS or the source IP address. This is the entry point where all traffic first touches Envoy.
Filter chains contain an ordered list of network filters that process the connection. The most important filter for HTTP traffic is the HTTP Connection Manager (HCM), which upgrades a raw TCP connection into HTTP protocol handling. The HCM parses HTTP requests, manages connection pooling, and applies HTTP-level filters like rate limiting, authentication, and CORS. Within the HCM, the route configuration defines how requests are matched and forwarded. Routes match on URL paths, headers, query parameters, and other HTTP attributes, then direct the request to a specific cluster. The route configuration can be defined statically in the config file or dynamically through the Route Discovery Service (RDS).
Clusters represent groups of logically similar upstream hosts that Envoy forwards traffic to. A cluster named payments-api might contain three backend instances running on different IP addresses. Each cluster defines its load balancing policy (round-robin, least-request, random, ring-hash), health checking configuration, circuit breaker thresholds, and connection pool settings. Endpoints within a cluster are the actual network addresses of upstream service instances. Envoy supports multiple service discovery mechanisms for endpoints: static (hardcoded IPs), strict DNS (DNS resolution), logical DNS, EDS (Endpoint Discovery Service for dynamic environments), and original destination (for transparent proxying).
In production, a typical Envoy configuration for a microservices environment defines one listener on port 15001 that intercepts all outbound traffic from the local application. The route table maps destination hostnames to clusters: requests to order-service.default.svc.cluster.local go to the order-service cluster, requests to inventory-service go to the inventory-service cluster, and so on. Each cluster is configured with appropriate timeouts, retry policies, and health checks. The admin listener on port 9901 provides debugging endpoints. Understanding this architecture is essential because every service mesh configuration ultimately translates into these Envoy primitives.
A common misconception is that routes and clusters are the same thing. Routes define the matching logic (which requests go where), while clusters define the upstream behavior (how to connect, which endpoints to use, what load balancing algorithm to apply). Separating these concerns allows you to route different URL paths to different clusters with different policies, or to route the same path to different clusters based on headers for canary deployments.
Code Example
# Complete Envoy config showing listeners, routes, and clusters
# This proxies traffic to payments-api and order-service
static_resources:
# Define listeners that accept incoming connections
listeners:
# Main listener on port 10000
- name: main_listener
address:
socket_address:
# Accept connections on all interfaces
address: 0.0.0.0
# Listen on port 10000
port_value: 10000
filter_chains:
- filters:
# HTTP connection manager filter
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
# Prefix for stats emitted by this listener
stat_prefix: main_ingress
route_config:
name: local_route
virtual_hosts:
# Virtual host for routing decisions
- name: backend_services
# Match all incoming domains
domains: ["*"]
routes:
# Route /payments/* to payments-api cluster
- match:
prefix: "/payments/"
route:
cluster: payments-api
# Route /orders/* to order-service cluster
- match:
prefix: "/orders/"
route:
cluster: order-service
http_filters:
# Router filter forwards matched requests
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
# Define upstream service clusters
clusters:
# Payments API cluster
- name: payments-api
connect_timeout: 5s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: payments-api
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
# Payments service hostname
address: payments-api
port_value: 8080
# Order service cluster
- name: order-service
connect_timeout: 5s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: order-service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
# Order service hostname
address: order-service
port_value: 8080Interview Tip
A junior engineer typically lists components without explaining the data flow. Interviewers want you to trace a request from the moment it hits a listener through the filter chain, route matching, cluster selection, and finally to the upstream endpoint. Draw the flow on a whiteboard if possible. Emphasize the separation between routes (matching logic) and clusters (upstream behavior), as confusing these is a common mistake. Mention that this architecture maps directly to service mesh control plane abstractions: Istio VirtualService maps to Envoy routes, and Istio DestinationRule maps to Envoy clusters. This connection shows you understand the full stack.
◈ Architecture Diagram
┌─────────────────────────────────────────────────┐ │ Envoy Request Flow │ │ │ │ Downstream Envoy Proxy Upstream │ │ Client │ │ │ │ │ │ ┌──────────────────────────┐ │ │ └───→│ Listener (:10000) │ │ │ │ │ │ │ │ │ ↓ │ │ │ │ Filter Chain │ │ │ │ │ │ │ │ │ ↓ │ │ │ │ HTTP Connection Manager │ │ │ │ │ │ │ │ │ ↓ │ │ │ │ Route Match │ │ │ │ /payments/* →─────────┼──→ payments-api │ │ /orders/* →─────────┼──→ order-service │ │ │ │ │ └──────────────────────────┘ │ └─────────────────────────────────────────────────┘
💬 Comments
Quick Answer
The sidecar proxy pattern deploys an Envoy instance alongside each microservice in the same pod or host, intercepting all inbound and outbound traffic. This allows Envoy to handle networking concerns like TLS, retries, load balancing, and observability transparently without modifying application code.
Detailed Answer
Imagine every employee in an office building having a personal assistant who handles all their mail, phone calls, and meeting scheduling. The employee focuses purely on their work while the assistant manages all communication logistics, screens calls, retries failed deliveries, and keeps detailed records of every interaction. The sidecar proxy pattern works exactly this way: Envoy acts as the personal assistant for each microservice.
The sidecar pattern is a deployment architecture where a proxy process runs alongside the main application process, sharing the same network namespace. In Kubernetes, this means Envoy runs as a second container within the same pod as the application container. Because they share a network namespace, Envoy can intercept all network traffic to and from the application using iptables rules or similar mechanisms. The application sends traffic to localhost, Envoy intercepts it, applies policies (TLS encryption, retries, circuit breaking), and forwards it to the destination. Similarly, all incoming traffic first hits Envoy, which applies inbound policies before forwarding to the application on localhost. The application is completely unaware that Envoy exists.
Envoy implements the sidecar pattern through its lightweight, self-contained architecture. A single Envoy binary handles all proxy functionality without external dependencies. In Kubernetes environments, sidecar injection is typically automated: a mutating admission webhook modifies pod specifications to add the Envoy container and an init container that configures iptables rules. The init container sets up traffic redirection so that all TCP traffic leaving the application container is captured by Envoy's outbound listener, and all TCP traffic arriving at the pod is captured by Envoy's inbound listener. Envoy then uses its route table to determine the correct upstream cluster for each request. The service mesh control plane (like Istio's istiod) pushes configuration to each Envoy sidecar through the xDS API, telling it about available services, their endpoints, routing rules, and security policies.
In production, the sidecar pattern provides several critical benefits. First, it achieves language-agnostic networking: whether your payments-api is written in Java, your order-service in Go, and your catalog-service in Python, they all get the same networking capabilities through Envoy without importing any language-specific libraries. Second, it enables consistent observability because every request passes through Envoy, which emits metrics, access logs, and trace spans uniformly. Third, it provides security through automatic mTLS encryption between all sidecars without application changes. Fourth, it enables traffic management features like canary deployments, traffic mirroring, and fault injection at the infrastructure level.
The sidecar pattern does have trade-offs that are important to understand. Each Envoy sidecar consumes approximately 50-100MB of memory and adds 1-3ms of latency per hop. For a cluster with 500 pods, that is 25-50GB of additional memory dedicated to proxies. The iptables-based traffic interception can cause debugging challenges because tools like tcpdump show traffic going to localhost instead of the actual destination. Some teams opt for Envoy's ambient mesh mode or per-node deployment to reduce the overhead, but this sacrifices per-service granularity. Understanding these trade-offs is essential for making informed deployment decisions.
Code Example
# Kubernetes Pod spec with Envoy sidecar container
# This deploys payments-api with an Envoy sidecar
apiVersion: v1
kind: Pod
metadata:
# Pod name for the payments-api service
name: payments-api
labels:
# Label for service discovery
app: payments-api
spec:
# Init container configures iptables for traffic interception
initContainers:
- name: envoy-init
# Init image with iptables tools
image: envoyproxy/envoy:v1.28-latest
# Run iptables rules to redirect traffic through Envoy
command: ['sh', '-c', 'iptables -t nat -A OUTPUT -p tcp --dport 8080 -j REDIRECT --to-port 15001']
securityContext:
# Requires NET_ADMIN for iptables manipulation
capabilities:
add: ["NET_ADMIN"]
containers:
# Main application container
- name: payments-api
# Application Docker image
image: myregistry/payments-api:v2.1
ports:
# Application listens on port 8080
- containerPort: 8080
# Envoy sidecar container
- name: envoy-sidecar
# Envoy proxy image
image: envoyproxy/envoy:v1.28-latest
ports:
# Envoy admin interface
- containerPort: 9901
# Envoy inbound listener
- containerPort: 15001
# Mount Envoy configuration
volumeMounts:
- name: envoy-config
mountPath: /etc/envoy
volumes:
# ConfigMap containing envoy.yaml
- name: envoy-config
configMap:
name: payments-api-envoy-configInterview Tip
A junior engineer typically describes the sidecar pattern as 'running Envoy next to your app' without explaining the traffic interception mechanism. Interviewers want to hear about iptables-based traffic redirection, the init container setup, shared network namespace, and why the application does not need code changes. Explain that the sidecar provides language-agnostic networking, meaning a Java service and a Python service both get identical networking capabilities. Discuss the overhead trade-offs: memory consumption per sidecar and added latency. If you mention alternatives like ambient mesh or per-node deployment, you demonstrate awareness of evolving approaches that address sidecar overhead concerns.
◈ Architecture Diagram
┌─────────────────────────────────────────┐ │ Kubernetes Pod │ │ (shared network namespace) │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ payments-api │ │ Envoy │ │ │ │ Container │ │ Sidecar │ │ │ │ │ │ │ │ │ │ App :8080 │ │ Inbound:15001│ │ │ │ │ │ │ Admin :9901 │ │ │ └──────┼───────┘ └──────┼───────┘ │ │ │ │ │ │ localhost iptables │ │ (plaintext) redirect │ │ │ │ │ │ └────────┬────────┘ │ │ │ │ │ ┌──────┴──────┐ │ │ │ Network │ │ │ │ (mTLS) │ │ │ └─────────────┘ │ └─────────────────────────────────────────┘
💬 Comments
Quick Answer
The Envoy admin interface is a built-in HTTP API exposed on a configurable port (typically 9901) that provides runtime introspection into Envoy's state. It exposes endpoints for viewing statistics, cluster health, configuration dumps, logging levels, and server info, making it essential for debugging proxy issues in production.
Detailed Answer
Think of the Envoy admin interface as the dashboard of a car. While the engine (proxy) handles the actual work of moving traffic, the dashboard gives you real-time information about speed, fuel level, engine temperature, and warning lights. Without the dashboard, you would be driving blind, unable to diagnose problems until something breaks down completely.
The admin interface is an HTTP server built into every Envoy instance, typically bound to localhost on port 9901. It provides a comprehensive set of endpoints for inspecting Envoy's runtime state without affecting traffic processing. The most commonly used endpoints include /stats for viewing all counters, gauges, and histograms that Envoy tracks; /clusters for seeing the health status of every upstream endpoint; /config_dump for viewing the complete active configuration; /listeners for listing all active listeners; /logging for dynamically changing log levels; and /ready for health check readiness. The admin interface should never be exposed to untrusted networks because it provides sensitive information and can modify runtime behavior.
The /stats endpoint is the most powerful debugging tool. Envoy tracks thousands of statistics organized by category: cluster metrics show upstream connection counts, request totals, error rates, and latency histograms. Listener metrics show downstream connection counts and HTTP protocol errors. HTTP metrics show request counts by response code, retry counts, and timeout counts. You can filter stats by prefix to focus on a specific cluster or listener. For example, querying /stats?filter=cluster.payments-api shows only statistics related to the payments-api upstream cluster. The /stats/prometheus endpoint exports all statistics in Prometheus format, which can be scraped directly by a Prometheus server for long-term storage and alerting.
The /clusters endpoint shows the real-time health status of every endpoint in every cluster. For each endpoint, it displays the IP address, health status (healthy, unhealthy, or degraded), the number of active connections, outstanding requests, and the results of recent health checks. This is invaluable when debugging routing issues: if requests to the order-service are failing, the /clusters endpoint immediately shows whether the upstream endpoints are healthy, how many connections are active, and whether circuit breakers have tripped. The /config_dump endpoint outputs the entire active Envoy configuration as JSON, which is essential for verifying that dynamic configuration updates from the control plane have been applied correctly.
In production environments, operators use the admin interface as the first line of investigation when diagnosing issues. If a service is returning 503 errors, the typical debugging workflow is: check /clusters to see if upstream endpoints are healthy, check /stats for circuit breaker trip counts, verify /config_dump to ensure routes are configured correctly, and use /logging?level=debug to temporarily increase log verbosity for deeper investigation. Many teams build monitoring dashboards and alerts on top of the Prometheus-format stats. The admin interface also supports POST endpoints for runtime modifications like draining listeners for graceful shutdown and resetting statistics counters.
Code Example
# Access the Envoy admin interface # View all available admin endpoints curl http://localhost:9901/ # Check if Envoy is ready to accept traffic curl http://localhost:9901/ready # View server information and uptime curl http://localhost:9901/server_info # View all statistics (counters, gauges, histograms) curl http://localhost:9901/stats # Filter stats for a specific cluster curl http://localhost:9901/stats?filter=cluster.payments-api # View stats in Prometheus exposition format curl http://localhost:9901/stats/prometheus # View upstream cluster health and endpoints curl http://localhost:9901/clusters # Dump the complete active configuration as JSON curl http://localhost:9901/config_dump # View all active listeners curl http://localhost:9901/listeners # Dynamically change log level to debug curl -X POST http://localhost:9901/logging?level=debug # Change log level for a specific logger curl -X POST http://localhost:9901/logging?connection=trace # Reset all statistics counters curl -X POST http://localhost:9901/reset_counters # Drain listeners for graceful shutdown curl -X POST http://localhost:9901/drain_listeners
Interview Tip
A junior engineer typically mentions the admin interface exists without knowing the key endpoints. Interviewers want to hear your debugging workflow: check /clusters for endpoint health, /stats for error rates and circuit breaker trips, /config_dump for configuration verification, and /logging for dynamic log level changes. Mention that /stats/prometheus is used for production monitoring integration. Emphasize that the admin port must be restricted to localhost or internal networks because it can modify runtime behavior. If you can describe a real debugging scenario where you used the admin interface to diagnose a routing issue or circuit breaker trip, that demonstrates hands-on operational experience.
◈ Architecture Diagram
┌─────────────────────────────────────────┐ │ Envoy Admin Interface │ │ http://localhost:9901 │ │ │ │ ┌────────────────┬──────────────────┐ │ │ │ Endpoint │ Purpose │ │ │ ├────────────────┼──────────────────┤ │ │ │ /ready │ Health readiness │ │ │ │ /stats │ All metrics │ │ │ │ /clusters │ Upstream health │ │ │ │ /config_dump │ Active config │ │ │ │ /listeners │ Active listeners │ │ │ │ /logging │ Log levels │ │ │ │ /server_info │ Version/uptime │ │ │ └────────────────┴──────────────────┘ │ │ │ │ Debugging Flow: │ │ /clusters → /stats → /config_dump │ │ │ │ │ │ │ ↓ ↓ ↓ │ │ Endpoint Error rates Route │ │ health & breakers verification │ └─────────────────────────────────────────┘
💬 Comments
Quick Answer
In Envoy terminology, downstream refers to the client that connects to Envoy (sends requests), and upstream refers to the backend service that Envoy forwards requests to. Envoy sits between downstream clients and upstream services, processing traffic in both directions through its filter chain.
Detailed Answer
Think of Envoy as a river dam. Downstream is the direction water flows toward, meaning the clients that receive responses. Upstream is where the water comes from, meaning the backend services that generate the responses. Envoy sits in the middle, controlling the flow of water in both directions. This terminology is consistent throughout Envoy's documentation, configuration, and statistics.
In Envoy's architecture, a downstream connection is established when a client connects to one of Envoy's listeners. This client could be an end user's browser, another microservice, or any application making a network request. The downstream side is where Envoy accepts connections and receives requests. An upstream connection is what Envoy creates when it needs to forward a request to a backend service. The upstream side is where Envoy acts as a client, connecting to the endpoints defined in its cluster configuration. This bidirectional terminology is fundamental because Envoy processes traffic differently in each direction and tracks separate statistics for downstream and upstream activity.
The distinction becomes critical when reading Envoy statistics and logs. Downstream metrics include counters like downstream_cx_total (total downstream connections), downstream_rq_total (total downstream requests), and downstream_cx_active (currently active downstream connections). These tell you about the load Envoy is receiving from clients. Upstream metrics include upstream_cx_total, upstream_rq_total, upstream_rq_timeout, and upstream_rq_retry, which tell you about Envoy's communication with backend services. When debugging a 503 error, understanding this distinction is essential: a downstream 503 means Envoy sent a 503 to the client, but the root cause might be an upstream connection failure, an upstream timeout, or a circuit breaker trip on the upstream cluster.
In a service mesh with sidecar proxies, the upstream and downstream terminology applies at each hop. When the payments-api calls the order-service, the request passes through two Envoy proxies. The payments-api's Envoy sidecar treats the payments-api container as the downstream client and the order-service as the upstream. The order-service's Envoy sidecar treats the payments-api's Envoy as the downstream client and the order-service container as the upstream. Understanding this double-proxy hop is essential for interpreting metrics and access logs correctly in a service mesh environment.
A common source of confusion is that the upstream/downstream terminology in Envoy is opposite to how some people intuitively think about data flow. People sometimes assume upstream means the client because it is 'higher up' in the call chain. In networking and proxy terminology, upstream always refers to the server or backend that provides the content, and downstream always refers to the client or consumer that requests the content. This convention is consistent across Envoy, Nginx, HAProxy, and most proxy technologies. Getting this terminology right is essential for clear communication in incident response scenarios.
Code Example
# Envoy config showing upstream and downstream concepts
# Downstream: client connects to listener
# Upstream: Envoy connects to cluster endpoints
static_resources:
listeners:
# Downstream listener accepts client connections
- name: downstream_listener
address:
socket_address:
# Downstream clients connect to this address
address: 0.0.0.0
# Downstream port where Envoy accepts traffic
port_value: 10000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
# Stat prefix tracks downstream metrics
stat_prefix: downstream_ingress
route_config:
name: local_route
virtual_hosts:
- name: services
domains: ["*"]
routes:
# Forward downstream requests to upstream cluster
- match:
prefix: "/"
route:
# Upstream cluster receives forwarded requests
cluster: catalog-service
# Upstream timeout configuration
timeout: 15s
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
# Upstream cluster definition
- name: catalog-service
# Timeout for upstream connections
connect_timeout: 5s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: catalog-service
endpoints:
- lb_endpoints:
# Upstream endpoint addresses
- endpoint:
address:
socket_address:
# Upstream service host
address: catalog-service
# Upstream service port
port_value: 8080
# View downstream stats
# curl http://localhost:9901/stats?filter=downstream
# View upstream stats for catalog-service
# curl http://localhost:9901/stats?filter=cluster.catalog-serviceInterview Tip
A junior engineer typically confuses upstream and downstream or uses the terms interchangeably. Interviewers want to hear the precise definitions: downstream is the client side (connections coming in), upstream is the server side (connections going out to backends). Explain how this maps to Envoy's statistics: downstream_cx_total counts client connections, upstream_rq_timeout counts backend timeouts. In a service mesh, describe the double-hop scenario where one Envoy's upstream is another Envoy's downstream. If you can trace a 503 error through both downstream and upstream metrics to identify whether the issue is on the client connection or the backend connection, that shows real debugging skill.
◈ Architecture Diagram
┌─────────────────────────────────────────────────┐ │ Upstream / Downstream Flow │ │ │ │ DOWNSTREAM ENVOY UPSTREAM │ │ (Client) (Proxy) (Server) │ │ │ │ ┌──────────┐ ┌──────────────┐ ┌──────────┐ │ │ │ Browser │──→│ Listener │ │ catalog │ │ │ │ or │ │ (accepts) │ │ -service │ │ │ │ Service │ │ │ │ │ │ │ │ │ │ │ Filter Chain│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Route Match │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │←──│ Cluster ────┼──→│ :8080 │ │ │ │ │ │ (forwards) │ │ │ │ │ └──────────┘ └──────────────┘ └──────────┘ │ │ │ │ downstream_cx_total upstream_rq_total │ │ downstream_rq_total upstream_rq_timeout │ └─────────────────────────────────────────────────┘
💬 Comments
Quick Answer
Envoy supports two types of logging: application-level debug logs for internal operations and access logs for recording details of every request processed. Access logs can be written to files, stdout, or sent to gRPC access log services, with customizable formats using command operators that extract request, response, and connection metadata.
Detailed Answer
Think of Envoy's logging as two separate record-keeping systems. The application log is like a mechanic's diagnostic journal that records internal engine events, warning lights, and system errors. The access log is like a toll booth receipt printer that records every vehicle that passes through: the license plate, the lane used, the time of entry, the time of exit, and the toll charged. Both are essential but serve completely different purposes.
Envoy's application log controls the verbosity of internal operational messages. It uses a hierarchical logger system where different components (connection, http, router, upstream, admin) can be set to different log levels: trace, debug, info, warning, error, and critical. In development, you might set the log level to debug to see detailed information about connection handling, header processing, and routing decisions. In production, the log level is typically set to warning or error to avoid overwhelming log storage with verbose output. The log level can be changed at runtime through the admin interface endpoint /logging, which is invaluable for temporary debugging without restarting Envoy.
Access logs are the primary mechanism for recording information about every request that Envoy processes. They are configured within the HTTP Connection Manager filter and support multiple output sinks. The most common sink is a file-based access log that writes to a specified file path or stdout. Each log entry can be formatted using Envoy's command operator syntax, where placeholders like %REQ(:METHOD)%, %RESP(:STATUS)%, %DURATION%, %UPSTREAM_HOST%, and %RESPONSE_CODE% are replaced with actual values from the request and response. Envoy provides dozens of command operators covering request headers, response headers, connection properties, upstream metadata, and timing information. You can also use JSON format for access logs, which produces structured log entries that are much easier to parse with log aggregation tools like Elasticsearch, Splunk, or Loki.
In production environments, access log configuration is a balancing act between observability and performance. Writing a log entry for every request in a high-throughput service can generate massive log volumes and consume significant disk I/O and CPU. Teams typically configure access logs with a subset of the most useful fields: request method, path, response code, upstream host, total duration, upstream response time, and a few key headers. Some teams use conditional access logging to only log requests that result in errors (4xx or 5xx) or requests that exceed a latency threshold, dramatically reducing log volume while preserving diagnostic value. The gRPC access log sink sends log entries to an external service, enabling centralized log collection without local file management.
A critical aspect of Envoy access logs is understanding the response flags. Envoy adds response flags like UH (no healthy upstream), UO (upstream overflow due to circuit breaking), UT (upstream timeout), NR (no route configured), and DC (downstream connection termination). These flags appear in the access log and provide immediate insight into why a request failed. For example, seeing a 503 response with the UH flag tells you the upstream cluster had no healthy endpoints, while a 503 with UO tells you the circuit breaker tripped. Learning to read these response flags is one of the most valuable debugging skills for operating Envoy in production.
Code Example
# Envoy configuration with custom access logging
static_resources:
listeners:
- name: main_listener
address:
socket_address:
# Listen on all interfaces
address: 0.0.0.0
# Port for incoming traffic
port_value: 10000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
# Stat prefix for this listener
stat_prefix: ingress_http
# Access log configuration
access_log:
# File-based access log sink
- name: envoy.access_loggers.file
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
# Write access logs to stdout
path: /dev/stdout
# Custom log format with request details
log_format:
# JSON format for structured logging
json_format:
# Timestamp of the request
timestamp: "%START_TIME%"
# HTTP method (GET, POST, etc.)
method: "%REQ(:METHOD)%"
# Request path
path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
# HTTP response status code
response_code: "%RESPONSE_CODE%"
# Envoy response flags (UH, UO, UT, etc.)
response_flags: "%RESPONSE_FLAGS%"
# Total request duration in milliseconds
duration_ms: "%DURATION%"
# Upstream service response time
upstream_response_time: "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%"
# Upstream host that handled the request
upstream_host: "%UPSTREAM_HOST%"
# Upstream cluster name
upstream_cluster: "%UPSTREAM_CLUSTER%"
# Client IP address
client_ip: "%DOWNSTREAM_REMOTE_ADDRESS%"
route_config:
name: local_route
virtual_hosts:
- name: backend
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: user-auth-service
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
# Change log level dynamically via admin interface
# curl -X POST http://localhost:9901/logging?level=debug
# Change specific logger level
# curl -X POST http://localhost:9901/logging?http=traceInterview Tip
A junior engineer typically knows Envoy has logging but cannot explain the difference between application logs and access logs, or how to customize the access log format. Interviewers want to hear about the command operator syntax (e.g., %RESPONSE_CODE%, %UPSTREAM_HOST%), JSON-format structured logging for log aggregation tools, and most importantly, response flags like UH, UO, UT, and NR. If you can explain what each response flag means and how it helps diagnose issues, you demonstrate real operational experience. Mention that access log volume is a production concern and discuss strategies like conditional logging and gRPC log sinks.
◈ Architecture Diagram
┌─────────────────────────────────────────────────┐ │ Envoy Logging Architecture │ │ │ │ ┌──────────────────┐ ┌──────────────────────┐ │ │ │ Application Log │ │ Access Log │ │ │ │ (debug/info/warn)│ │ (per-request records) │ │ │ ├──────────────────┤ ├──────────────────────┤ │ │ │ Internal events │ │ Method, Path, Status │ │ │ │ Connection info │ │ Duration, Upstream │ │ │ │ Route decisions │ │ Response Flags │ │ │ │ Error details │ │ Client IP, Headers │ │ │ └────────┬─────────┘ └─────────┬────────────┘ │ │ │ │ │ │ ↓ ↓ │ │ ┌────────────┐ ┌──────────────────────┐ │ │ │ stderr │ │ stdout / file / │ │ │ │ │ │ gRPC log service │ │ │ └────────────┘ └──────────────────────┘ │ │ │ │ Response Flags: UH=no healthy upstream │ │ UO=circuit break UT=timeout NR=no route │ └─────────────────────────────────────────────────┘
💬 Comments
Quick Answer
Envoy is distributed as an official Docker image (envoyproxy/envoy) that can be run with a custom configuration file mounted as a volume. You start Envoy by mapping listener and admin ports, mounting your envoy.yaml configuration, and optionally setting log levels and concurrency options through command-line flags.
Detailed Answer
Think of running Envoy with Docker as setting up a pre-built security checkpoint at a building entrance. The Docker image provides the checkpoint equipment (Envoy binary), you provide the rules for who can enter and which floor they go to (envoy.yaml configuration), and you connect it to the building's entry points (port mappings). Docker ensures the checkpoint runs consistently regardless of the building's infrastructure.
The official Envoy Docker image is published on Docker Hub as envoyproxy/envoy and is available with various tags corresponding to Envoy versions. The image contains the Envoy binary, default configuration, and minimal system dependencies. To run Envoy with a custom configuration, you mount your envoy.yaml file into the container at the default configuration path /etc/envoy/envoy.yaml. Port mappings expose the listener ports and the admin interface port to the host machine. The most basic run command maps the primary listener port (e.g., 10000) and the admin port (9901), mounts the configuration file, and starts the container.
Envoy's Docker image supports several command-line flags that control runtime behavior. The --concurrency flag sets the number of worker threads, which should typically match the number of CPU cores allocated to the container. The --log-level flag sets the initial application log verbosity. The --component-log-level flag allows setting different log levels for different components, such as --component-log-level upstream:debug,connection:trace. The --config-path flag specifies the configuration file location, and --config-yaml allows passing inline YAML configuration for simple setups. The --service-cluster and --service-node flags set metadata that identifies this Envoy instance to the control plane.
In production Docker deployments, Envoy is typically run with resource constraints to prevent it from consuming excessive CPU or memory. Docker's --cpus and --memory flags limit resource usage. Health checks are configured to use the admin interface's /ready endpoint, ensuring the container orchestrator knows when Envoy is ready to accept traffic. The container should run as a non-root user for security, and the admin interface should only be bound to localhost or an internal network. When running Envoy alongside an application in Docker Compose, both containers share a Docker network, allowing Envoy to reach the application by container name.
A common beginner mistake is running Envoy without validating the configuration first. Envoy provides a --mode validate flag that checks the configuration file for syntax errors and semantic issues without actually starting the proxy. Always validate configuration changes before deploying them. Another gotcha is forgetting to expose the admin port for debugging. While the admin interface should not be publicly accessible, having it available internally is essential for troubleshooting. In Docker Compose environments, define the admin port mapping only for development profiles and exclude it from production configurations. File permission issues with mounted configuration files are another frequent problem: ensure the Envoy user inside the container has read access to the mounted envoy.yaml file.
Code Example
# Pull a specific version of the Envoy image docker pull envoyproxy/envoy:v1.28-latest # Validate envoy.yaml configuration before running docker run --rm \ -v $(pwd)/envoy.yaml:/etc/envoy/envoy.yaml \ envoyproxy/envoy:v1.28-latest \ --mode validate \ --config-path /etc/envoy/envoy.yaml # Run Envoy with custom config and port mappings docker run -d --name envoy-proxy \ -p 10000:10000 \ -p 9901:9901 \ -v $(pwd)/envoy.yaml:/etc/envoy/envoy.yaml \ envoyproxy/envoy:v1.28-latest # Run Envoy with resource limits and log level docker run -d --name envoy-proxy \ --cpus 2 \ --memory 512m \ -p 10000:10000 \ -p 9901:9901 \ -v $(pwd)/envoy.yaml:/etc/envoy/envoy.yaml \ envoyproxy/envoy:v1.28-latest \ --log-level info \ --concurrency 2 # Docker Compose with Envoy fronting payments-api # docker-compose.yml # version: '3.8' # services: # envoy: # image: envoyproxy/envoy:v1.28-latest # Envoy proxy # ports: # - "10000:10000" # Listener port # - "9901:9901" # Admin interface # volumes: # - ./envoy.yaml:/etc/envoy/envoy.yaml # Mount config # depends_on: # - payments-api # Start after backend # payments-api: # image: myregistry/payments-api:v2.1 # Backend service # expose: # - "8080" # Internal port only # View Envoy container logs docker logs -f envoy-proxy # Execute command inside the Envoy container docker exec envoy-proxy curl http://localhost:9901/clusters # Stop and remove the Envoy container docker stop envoy-proxy && docker rm envoy-proxy
Interview Tip
A junior engineer typically shows a basic docker run command without discussing configuration validation, resource limits, or health checks. Interviewers want to see that you validate configuration with --mode validate before deploying, set appropriate resource limits with --cpus and --memory, configure health checks using the /ready endpoint, and understand the --concurrency flag for worker thread tuning. Mention running as non-root for security and explain why the admin port should be restricted. If you describe a Docker Compose setup with Envoy fronting a backend service, you demonstrate practical experience with containerized proxy deployments.
◈ Architecture Diagram
┌─────────────────────────────────────────────────┐ │ Docker Envoy Deployment │ │ │ │ Host Machine │ │ ┌─────────────────────────────────────────┐ │ │ │ Docker Container: envoy-proxy │ │ │ │ │ │ │ │ ┌───────────────────────────────┐ │ │ │ │ │ Envoy Binary │ │ │ │ │ │ │ │ │ │ │ │ Listener :10000 ←── Port Map │←── :10000 │ │ │ │ Admin :9901 ←── Port Map │←── :9901 │ │ │ │ │ │ │ │ │ │ /etc/envoy/envoy.yaml │ │ │ │ │ │ ↑ Volume Mount │ │ │ │ │ └───────┼───────────────────────┘ │ │ │ └──────────┼──────────────────────────────┘ │ │ │ │ │ $(pwd)/envoy.yaml │ │ (Host config file) │ └─────────────────────────────────────────────────┘
💬 Comments
Quick Answer
Envoy supports multiple service discovery mechanisms through its cluster type configuration: STATIC (hardcoded endpoints), STRICT_DNS (DNS resolution with all IPs used), LOGICAL_DNS (DNS with single IP used), EDS (Endpoint Discovery Service for dynamic environments), and ORIGINAL_DST (transparent proxying using original destination addresses).
Detailed Answer
Think of service discovery as the way Envoy builds its phone book of available backend services. Just like you can have a printed phone book (static), look up numbers through directory assistance (DNS), or use a constantly updated contact app that syncs across devices (EDS), Envoy provides multiple methods for discovering where upstream services are located.
The cluster type configuration in Envoy determines how it discovers and maintains the list of endpoints (IP addresses and ports) for each upstream service. The simplest type is STATIC, where you hardcode the endpoint addresses directly in the configuration file. This is straightforward and works well for infrastructure services with fixed addresses, like a database server or an external API endpoint. However, static configuration does not adapt to dynamic environments where services scale up and down.
STRICT_DNS resolves a DNS hostname and uses all returned IP addresses as endpoints. Envoy continuously re-resolves the DNS name at a configurable interval and updates the endpoint list when the DNS response changes. If the DNS query for order-service.default.svc.cluster.local returns three IP addresses, Envoy creates three endpoints and load-balances across all of them. When a pod is removed and the DNS response changes to two IP addresses, Envoy automatically removes the stale endpoint. LOGICAL_DNS is similar but only uses the first IP address from the DNS response, creating a single logical connection. This is useful for services behind a load balancer where you want DNS-based resolution but do not want Envoy to track individual backend instances.
EDS (Endpoint Discovery Service) is the most powerful and dynamic service discovery mechanism. Instead of relying on DNS, Envoy communicates with an external management server that provides endpoint information through a gRPC or REST API. The management server pushes endpoint updates to Envoy in real-time as services scale, deploy, or fail health checks. This is the mechanism used by service mesh control planes like Istio's istiod, Consul Connect, and AWS App Mesh. EDS provides richer information than DNS, including endpoint metadata, health status, load balancing weights, and locality information for zone-aware routing. ORIGINAL_DST is used for transparent proxying, where Envoy uses the original destination address from the connection metadata (set by iptables redirection) to determine the upstream endpoint.
In production Kubernetes environments, EDS is the standard discovery mechanism because it integrates with the Kubernetes API to track pod lifecycle events in real-time. When a new payments-api pod starts and passes its readiness check, the control plane pushes an EDS update to all Envoy sidecars, immediately making the new endpoint available for load balancing. When a pod is terminating, the endpoint is removed before the pod shuts down, preventing requests from being sent to a dying instance. This real-time synchronization is far more responsive than DNS-based discovery, which has inherent TTL delays.
A common mistake is choosing STRICT_DNS in Kubernetes and assuming it will work like EDS. While DNS-based discovery works, it has limitations: DNS TTLs introduce propagation delays, DNS responses may be truncated for large clusters, and DNS does not provide health status or weighting information. For any environment with more than a handful of services, EDS is the recommended approach because it provides real-time updates, richer metadata, and better integration with the overall service mesh architecture.
Code Example
# STATIC cluster - hardcoded endpoints
# Use for fixed infrastructure like databases
clusters:
- name: inventory-service
# Timeout for establishing upstream connections
connect_timeout: 5s
# Static type uses hardcoded addresses
type: STATIC
# Round-robin load balancing across endpoints
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: inventory-service
endpoints:
- lb_endpoints:
# First static endpoint
- endpoint:
address:
socket_address:
address: 10.0.1.10
port_value: 8080
# Second static endpoint
- endpoint:
address:
socket_address:
address: 10.0.1.11
port_value: 8080
# STRICT_DNS cluster - resolves all IPs from DNS
# Use when DNS returns multiple A records
- name: catalog-service
connect_timeout: 5s
# Strict DNS resolves and uses all returned IPs
type: STRICT_DNS
# DNS refresh rate for re-resolution
dns_refresh_rate: 5s
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: catalog-service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
# DNS hostname to resolve
address: catalog-service.default.svc.cluster.local
port_value: 8080
# EDS cluster - dynamic endpoint discovery
# Use in service mesh environments
- name: payments-api
connect_timeout: 5s
# EDS type discovers endpoints from control plane
type: EDS
# EDS configuration pointing to management server
eds_cluster_config:
# EDS API source configuration
eds_config:
# Use ADS (Aggregated Discovery Service)
ads: {}
# Resource API version
resource_api_version: V3
lb_policy: ROUND_ROBINInterview Tip
A junior engineer typically mentions DNS-based discovery without discussing the EDS mechanism that powers service meshes. Interviewers want you to explain all five cluster types and when to use each one. Emphasize that EDS is the production standard in Kubernetes environments because it provides real-time updates, health status, and locality information that DNS cannot offer. Explain the limitations of DNS-based discovery: TTL delays, truncated responses, and lack of metadata. If you can describe how a pod lifecycle event (creation, readiness, termination) propagates through EDS to all Envoy sidecars, you demonstrate end-to-end understanding of service mesh data plane mechanics.
◈ Architecture Diagram
┌─────────────────────────────────────────────────┐ │ Envoy Service Discovery Types │ │ │ │ STATIC STRICT_DNS EDS │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Hardcoded│ │ DNS │ │ Control │ │ │ │ IPs in │ │ Resolver │ │ Plane │ │ │ │ Config │ │ │ │ (xDS API)│ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ │ │ │ ↓ ↓ ↓ │ │ ┌──────────────────────────────────────────┐ │ │ │ Envoy Cluster Endpoints │ │ │ │ ● 10.0.1.10:8080 (healthy) │ │ │ │ ● 10.0.1.11:8080 (healthy) │ │ │ │ ● 10.0.1.12:8080 (unhealthy) │ │ │ └──────────────────────────────────────────┘ │ │ │ │ STATIC: Fixed, simple, no auto-update │ │ DNS: Periodic refresh, TTL delays │ │ EDS: Real-time, metadata-rich, recommended │ └─────────────────────────────────────────────────┘
💬 Comments
Context
A company whose edge was archaeology: hardware LBs fronting HAProxy fronting per-team NGINX instances — three hops, three config languages, three change processes, and TLS versions negotiated differently at each layer.
Problem
A routing change traversed three teams and a change-board week; observability stopped at each hop boundary (correlating a user request across the layers was manual log archaeology); and retry/timeout policies stacked multiplicatively across layers, producing the occasional 6-retry storm during backend brownouts.
Solution
Consolidated to an Envoy edge fleet driven by a control plane (xDS): listeners/routes/clusters defined as code in a Git repo, rendered and served dynamically — config changes deploy in seconds without restarts or connection drops. One place owns TLS termination (uniform policy, automated cert rotation via SDS), retries/timeouts/circuit breaking declared once per route with budgets instead of stacking, and access logs plus per-route metrics emit uniformly to the observability stack with request IDs propagated end-to-end. Migration ran host-by-host behind weighted DNS with the old chain as fallback.
Commands
xds repo: routes/checkout.yaml -> CI validate (envoy --mode validate) -> control plane serves LDS/RDS/CDS
retry policy: {retry_on: '5xx,reset', num_retries: 2, retry_budget: {budget_percent: 20}}SDS: cert-manager -> sds secrets, zero-downtime rotation
Outcome
Three layers became one; routing changes went from a week to a reviewed PR deploying in minutes; edge p50 latency dropped 11ms (two fewer hops); the retry-storm class ended with budgets; and one incident's cross-layer log archaeology became a single request-ID query.
Lessons Learned
The control-plane investment (not Envoy itself) was most of the project — static configs would have recreated the change-process bottleneck. Retry budgets per route are the single highest-value setting most teams don't know exists.
💬 Comments
Context
A services stack where one datastore's degradation historically cascaded: the service in front of it slowed, callers piled on connections and retries, thread pools exhausted upward through four layers, and a database brownout became a full-site incident.
Problem
Each service's client-side timeouts and pools were configured independently (and optimistically); during brownouts, queued connections and naive retries multiplied load exactly when the dependency needed shedding; the last such cascade took the whole API surface down for 40 minutes from a 90-second database stall.
Solution
Pushed resilience into the Envoy layer fronting each service: per-upstream circuit breakers (max connections/pending/requests tuned from measured steady-state plus headroom), outlier detection ejecting hosts on consecutive 5xx/local-origin failures with bounded ejection percentage, retry policies rewritten with budgets (20% of active requests) and only on idempotent routes, and priority-based local rate limiting protecting each service's health-check and admin routes even under load shedding. Load-tested the failure mode deliberately: a chaos test stalls the database replica and verifies the blast radius stays one service deep.
Commands
circuit_breakers: {max_connections: 400, max_pending_requests: 200, max_requests: 800}outlier_detection: {consecutive_5xx: 5, base_ejection_time: 30s, max_ejection_percent: 50}chaos drill: tc qdisc add ... delay 2000ms on db replica -> assert cascade contained
Outcome
The next real database brownout (14 minutes) stayed a one-service degradation: callers received fast 503s with Retry-After instead of hanging, upstream pools never exhausted, and the site stayed up. Chaos drills run quarterly and have caught two config regressions since.
Lessons Learned
Fast failure is the feature: the cultural fight was convincing teams that immediate 503s during dependency failure beat 30-second hangs that 'might succeed'. Outlier detection's max_ejection_percent guard matters — without it, a config bug can eject an entire healthy cluster.
💬 Comments
Symptom
A 30-second backend hiccup (a deploy's connection drain) triggers a traffic tsunami: the service's request rate spikes to ~9x baseline, saturates its pools, and converts a blip into a 10-minute brownout. Dashboards show the incoming edge traffic flat — the multiplication is internal.
Error Message
No single error. Per-hop metrics show upstream_rq_retry counters exploding simultaneously at the edge Envoy (2 retries), the mesh sidecar (2 retries), and the application's HTTP client (2 retries) — 3 layers x 3 attempts each = up to 27 requests per original.
Root Cause
Retry policies configured independently at three layers, each reasonable alone, multiplicative together. During the drain window every attempt failed, so every layer exercised its full retry allowance. No retry budgets bounded the aggregate, and the retried route included non-idempotent POSTs (double-processing was avoided only by downstream idempotency keys — luck, partially).
Diagnosis Steps
Solution
Established a single-owner retry policy: retries live at exactly one layer (the mesh sidecar), edge and application-client retries disabled for mesh-internal routes; the surviving policy got a retry budget (20% of active requests) and retry_on restricted to idempotent methods/routes, with per-try timeouts shorter than the overall route timeout. A load test now replays the drain scenario in staging on every gateway config release.
Commands
envoy stats: cluster.svc.upstream_rq_retry, upstream_rq_retry_overflow (budget hits, post-fix)
route config audit: grep -r 'num_retries' edge/ mesh/ app-clients/
fix: retry_budget {budget_percent: 20, min_retry_concurrency: 3}; retry_on: 'connect-failure,refused-stream' onlyPrevention
Retries are a topology property, not a per-layer setting: document which layer owns them per path, disable elsewhere, and always use budgets — unbudgeted retries are amplifiers wired to fire during failures. Include retry_on method/route constraints so non-idempotent operations never auto-retry.
💬 Comments
Symptom
Edge Envoy instances OOM every ~9 days like clockwork, rolling through the fleet (staggered by deploy time). Memory grows linearly; connection counts and traffic are flat. Restarts fully reset the curve.
Error Message
OOMKilled at the container limit. /stats endpoint dump is enormous — millions of unique stat names, dominated by per-route and per-cluster stats for names containing high-cardinality path segments.
Root Cause
A routing refactor introduced dynamically-generated route/cluster names embedding tenant identifiers (route.tenant_<id>_api...), and Envoy allocates stats per named object — stats memory grew with every new tenant/path variant ever seen and never shrank. The linear-with-uptime memory curve was the stats registry, not a leak in the traditional sense: working exactly as configured, unboundedly.
Diagnosis Steps
Solution
Collapsed the naming: routes/clusters renamed to bounded families with the tenant carried in headers/metadata instead of object names; stats_config with stats matchers (inclusion list) dropped the remaining per-variant stats Envoy still wanted to create; tag extraction rules turned meaningful variance into bounded tags. Memory flattened immediately; the 9-day OOM cycle ended. Added a stats-cardinality check (stat name count) to the config CI so route-generation changes can't reintroduce it.
Commands
curl -s localhost:9901/stats | wc -l # trend this
curl -s localhost:9901/memory # allocated vs heap
stats_config: {stats_matcher: {inclusion_list: {patterns: [...bounded families...]}}}Prevention
Object names are cardinality commitments in Envoy: never embed unbounded identifiers (tenants, users, raw paths) in route/cluster/listener names. Use stats matchers as a guardrail, monitor stat-name count as a metric, and gate config-generation PRs on a cardinality budget.
💬 Comments