Skip to content

Envoy Proxy β€” Complete Guide

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you'll learn about Envoy Proxy. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Envoy is a high-performance proxy designed for cloud-native applications, providing advanced routing, observability, and dynamic configuration through the xDS protocol.

What You'll Learn

By the end of this lesson, you will understand Envoy's architecture, configure listeners and clusters, implement circuit breakers and retries, and use Envoy as an API Gateway.

Why It Matters

Envoy is the most popular service proxy in the cloud-native ecosystem, used as the data plane for Istio, Consul Connect, and AWS App Mesh.

Real-World Use

Envoy sits alongside each microservice as a sidecar proxy, handling all incoming traffic with advanced routing, retries, circuit breaking, and distributed tracing.

Envoy Architecture

flowchart LR
    Client --> Listener[Listener :80]
    Listener --> Filter[HTTP Filter Chain]
    Filter --> Router[Router Filter]
    Router --> Cluster[Cluster: Backend]
    Cluster --> EP1[Endpoint 1]
    Cluster --> EP2[Endpoint 2]

Envoy Configuration Generation

# envoy_config.py
import json
from typing import Dict, List, Optional

class EnvoyConfigGenerator:
    def __init__(self):
        self.listeners: List[dict] = []
        self.clusters: List[dict] = []
        self.admin = {"access_log_path": "/dev/null", "address": {"socket_address": {"address": "127.0.0.1", "port_value": 9901}}}

    def add_listener(self, name: str, port: int, routes: List[dict]):
        listener = {
            "name": name,
            "address": {"socket_address": {"address": "0.0.0.0", "port_value": port}},
            "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": f"ingress_{name}",
                        "route_config": {
                            "name": f"routes_{name}",
                            "virtual_hosts": [{
                                "name": "backend",
                                "domains": ["*"],
                                "routes": routes,
                            }],
                        },
                        "http_filters": [
                            {"name": "envoy.filters.http.router",
                             "typed_config": {"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"}},
                        ],
                    },
                }],
            }],
        }
        self.listeners.append(listener)

    def add_cluster(self, name: str, hosts: List[str],
                    circuit_breaker: Optional[dict] = None,
                    timeout: str = "5s"):
        cluster = {
            "name": name,
            "connect_timeout": timeout,
            "type": "STRICT_DNS",
            "lb_policy": "ROUND_ROBIN",
            "load_assignment": {
                "cluster_name": name,
                "endpoints": [{
                    "lb_endpoints": [
                        {"endpoint": {"address": {"socket_address": {"address": h.split(":")[0], "port_value": int(h.split(":")[1])}}}}
                        for h in hosts
                    ],
                }],
            },
        }
        if circuit_breaker:
            cluster["circuit_breakers"] = circuit_breaker
        self.clusters.append(cluster)

    def generate(self) -> dict:
        return {
            "admin": self.admin,
            "static_resources": {
                "listeners": self.listeners,
                "clusters": self.clusters,
            },
        }

envoy = EnvoyConfigGenerator()

envoy.add_listener("ingress", 8080, [
    {"match": {"prefix": "/api/v1/users"}, "route": {"cluster": "user_cluster"}},
    {"match": {"prefix": "/api/v1/orders"}, "route": {"cluster": "order_cluster"}},
    {"match": {"prefix": "/api/v1/products"}, "route": {
        "cluster": "product_cluster",
        "retry_policy": {"num_retries": 3, "retry_on": "5xx"},
    }},
])

envoy.add_cluster("user_cluster", ["10.0.0.1:3000", "10.0.0.2:3000"],
                  circuit_breaker={"thresholds": [{"max_connections": 100}]})
envoy.add_cluster("order_cluster", ["10.0.0.3:4000"])
envoy.add_cluster("product_cluster", ["10.0.0.4:5000"])

config = envoy.generate()
print(f"Listeners: {len(config['static_resources']['listeners'])}")
print(f"Clusters: {len(config['static_resources']['clusters'])}")

for cluster in config['static_resources']['clusters']:
    endpoints = cluster['load_assignment']['endpoints'][0]['lb_endpoints']
    print(f"  Cluster '{cluster['name']}': {len(endpoints)} hosts")
    if 'circuit_breakers' in cluster:
        print(f"    Circuit breakers: {cluster['circuit_breakers']}")

Expected output:

Listeners: 1
Clusters: 3
  Cluster 'user_cluster': 2 hosts
    Circuit breakers: {'thresholds': [{'max_connections': 100}]}
  Cluster 'order_cluster': 1 hosts
  Cluster 'product_cluster': 1 hosts

Envoy Circuit Breaker Simulation

# envoy_circuit.py
import time
from typing import Dict, List, Optional

class EnvoyCircuitBreaker:
    def __init__(self, max_connections: int = 100,
                 max_pending_requests: int = 10,
                 max_retries: int = 3):
        self.max_connections = max_connections
        self.max_pending_requests = max_pending_requests
        self.max_retries = max_retries
        self.active_connections = 0
        self.pending_requests = 0
        self.consecutive_5xx = 0
        self.detection_threshold = 5
        self.tripped = False
        self.trip_time = 0

    def allow(self) -> bool:
        if self.tripped:
            if time.time() - self.trip_time > 30:
                self.tripped = False
                self.consecutive_5xx = 0
            else:
                return False

        return self.active_connections < self.max_connections

    def record_success(self):
        self.consecutive_5xx = 0

    def record_failure(self, status: int):
        if status >= 500:
            self.consecutive_5xx += 1
            if self.consecutive_5xx >= self.detection_threshold:
                self.tripped = True
                self.trip_time = time.time()

    def stats(self) -> dict:
        return {
            "tripped": self.tripped,
            "active_connections": self.active_connections,
            "consecutive_5xx": self.consecutive_5xx,
        }

cb = EnvoyCircuitBreaker(max_connections=5)
for i in range(7):
    allowed = cb.allow()
    if allowed:
        cb.record_failure(503)
    print(f"Request {i+1}: {'Allowed' if allowed else 'Blocked'} | Stats: {cb.stats()}")

Expected output:

Request 1: Allowed | Stats: {'tripped': False, 'active_connections': 0, 'consecutive_5xx': 1}
...
Request 5: Allowed | Stats: {'tripped': True, 'active_connections': 0, 'consecutive_5xx': 5}
Request 6: Blocked | Stats: {'tripped': True, 'active_connections': 0, 'consecutive_5xx': 5}
Request 7: Blocked | Stats: {'tripped': True, 'active_connections': 0, 'consecutive_5xx': 5}

Common Mistakes

1. No xDS Control Plane

Without a control plane, Envoy requires static configuration reloaded on restart. For dynamic config, use Istio, Consul, or a custom xDS server.

2. Ignoring Envoy's Observability

Envoy emits rich metrics but they must be collected. Integrate with Prometheus for metrics and Jaeger for tracing.

3. Misconfigured Timeouts

Default timeouts may be too short for slow backends. Set appropriate connect, request, and idle timeouts per cluster.

4. Overly Permissive RBAC

Envoy supports RBAC filters. Without them, any service can call any other service. Apply least-privilege routing policies.

5. No Health Check Configuration

Envoy supports active health checking. Without it, Envoy may route to unhealthy endpoints until they fail.

Practice Questions

1. What is the xDS protocol?

xDS (Discovery Service) is Envoy's dynamic configuration API. It includes LDS (Listener), CDS (Cluster), RDS (Route), and EDS (Endpoint) discovery.

2. How does Envoy implement circuit breaking?

Envoy tracks pending requests, active connections, and consecutive failures. When thresholds are exceeded, the circuit trips and new requests Fail Fast.

3. What is a sidecar proxy pattern?

A sidecar proxy runs alongside each service instance, handling all network traffic to and from that service, providing observability and resilience.

4. How does Envoy support advanced routing?

Envoy supports prefix, path, header, query parameter, and method-based routing with权重, mirroring, and retry policies.

Challenge

Design an Envoy configuration for a microservice platform with 5 services, circuit breakers on all clusters, retry policies for idempotent endpoints, distributed tracing, and Prometheus metrics export.

FAQ

Is Envoy an API gateway or a sidecar proxy?

Both. Envoy can be deployed as an edge proxy (gateway) or a service mesh sidecar. The same binary handles both use cases.

Does Envoy support WebSockets?

Yes. Envoy supports WebSocket proxying with HTTP/1.1 upgrade and HTTP/2 tunneling.

How does Envoy handle TLS?

Envoy supports TLS termination and origination, mutual TLS, certificate rotation, and integration with SPIFFE for workload identity.

Can Envoy rate limit requests?

Yes. Envoy supports both local and global rate limiting via the rate limit filter and external rate limit service.

What is Envoy's performance profile?

Envoy handles 10K+ req/s per core with sub-millisecond latency overhead, making it suitable for high-performance environments.

Mini Project: Envoy Config Generator

# envoy_gen.py
import json
from typing import Dict, List, Optional

class EnvoyGenerator:
    def __init__(self):
        self.clusters = []
        self.routes = []

    def add_cluster(self, name: str, hosts: List[str]):
        self.clusters.append({
            "name": name,
            "connect_timeout": "5s",
            "type": "STRICT_DNS",
            "lb_policy": "ROUND_ROBIN",
            "load_assignment": {
                "cluster_name": name,
                "endpoints": [{
                    "lb_endpoints": [{"endpoint": {"address": {"socket_address": {"address": h.split(":")[0], "port_value": int(h.split(":")[1])}}}} for h in hosts]
                }],
            },
        })

    def add_route(self, prefix: str, cluster: str, retries: int = 0):
        route = {"match": {"prefix": prefix}, "route": {"cluster": cluster}}
        if retries:
            route["route"]["retry_policy"] = {"num_retries": retries, "retry_on": "5xx"}
        self.routes.append(route)

    def generate(self, port: int = 8080) -> str:
        config = {
            "static_resources": {
                "listeners": [{
                    "name": "listener_0",
                    "address": {"socket_address": {"address": "0.0.0.0", "port_value": port}},
                    "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": "ingress", "route_config": {"name": "local_route", "virtual_hosts": [{"name": "backend", "domains": ["*"], "routes": self.routes}]}, "http_filters": [{"name": "envoy.filters.http.router"}]}}]}],
                }],
                "clusters": self.clusters,
            },
            "admin": {"access_log_path": "/dev/null", "address": {"socket_address": {"address": "127.0.0.1", "port_value": 9901}}},
        }
        return json.dumps(config, indent=2)

eg = EnvoyGenerator()
eg.add_cluster("users", ["10.0.0.1:3000"])
eg.add_cluster("orders", ["10.0.0.2:4000"])
eg.add_route("/api/users", "users", retries=3)
eg.add_route("/api/orders", "orders")

config = json.loads(eg.generate())
print(f"Clusters: {len(config['static_resources']['clusters'])}")
print(f"Routes: {len(config['static_resources']['listeners'][0]['filter_chains'][0]['filters'][0]['typed_config']['route_config']['virtual_hosts'][0]['routes'])}")

Expected output:

Clusters: 2
Routes: 2

What's Next

You understand Envoy. Next, learn about AWS API Gateway, then explore Azure API Management.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro