Envoy Proxy β Complete Guide
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
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