Envoy Proxy Tutorial¶
A hands-on, 5-step guide to mastering Envoy Proxy — from your first container to production-grade gRPC load balancing with automatic failover on Kubernetes.
What you will build¶
By the end of this tutorial you will have a fully working setup where:
- Envoy load-balances gRPC requests across multiple backend replicas
- Unhealthy backends are automatically detected and removed from rotation
- If all primary backends fail, traffic automatically fails over to a backup cluster
- The whole thing runs on Kubernetes with zero-downtime scaling
Prerequisites¶
Before starting, make sure you have:
- Docker and Docker Compose installed
-
grpcurlinstalled (brew install grpcurl) -
kubectlandkind(for Step 5) - Basic familiarity with HTTP and containers
Tutorial structure¶
Each step builds directly on the previous one and takes approximately 30 minutes.
| Step | Topic | Key concepts |
|---|---|---|
| Step 1 | Your first Envoy proxy | Listeners, clusters, admin API |
| Step 2 | Load balancing HTTP | Multiple endpoints, lb policies, health checks |
| Step 3 | gRPC proxy | HTTP/2, grpc_health_check, grpc_stats |
| Step 4 | Failover & circuit breaking | Priority groups, outlier detection, retries |
| Step 5 | Kubernetes with xDS | Gateway API, EDS, dynamic config |
Core Envoy concepts¶
Before you start, here is a mental model of how Envoy processes a request:
graph LR
C[Client] --> L[Listener\nport 10000]
L --> FC[Filter Chain\nHTTP Connection Manager]
FC --> R[Route]
R --> CL[Cluster\nload balancer]
CL --> U1[Upstream 1]
CL --> U2[Upstream 2]
CL --> U3[Upstream 3]
- Listener — where Envoy accepts connections (address + port)
- Filter chain — pipeline of filters that process the connection (e.g. parse HTTP, add headers)
- Route — rules that map a request (by path, header, etc.) to a cluster
- Cluster — a named group of upstream endpoints with a load balancing policy
Admin API
Every step uses Envoy's built-in admin API on port 9901. Keep a terminal open with:
Config file anatomy¶
Every Envoy config file has the same top-level structure. Understanding this skeleton helps you navigate any config, no matter how large:
static_resources: # (1) hardcoded listeners and clusters
listeners: # (2) where Envoy accepts connections
- ...
clusters: # (3) named groups of upstream backends
- ...
dynamic_resources: # (4) optional: use xDS control plane instead
ads_config: ...
admin: # (5) built-in admin HTTP server
address:
socket_address:
address: 0.0.0.0
port_value: 9901
static_resources— all listeners and clusters are defined inline in the file. No external discovery service needed. This is what all steps 1–4 use.listeners— a list of listener objects. Each listener binds to an address + port and contains one or more filter chains.clusters— a list of cluster objects. Each cluster is a named pool of upstream endpoints. Routes inside listeners refer to clusters by name.dynamic_resources— used when a control plane (e.g. Envoy Gateway in Step 5) manages configuration. Listeners and clusters are delivered via xDS gRPC streams and absent from the file.admin— Envoy's built-in management API. Used in every step for observability.
Two kinds of filters¶
Envoy has two layers of filters that are easy to confuse:
| Layer | Config key | Example | Purpose |
|---|---|---|---|
| Network filter | filters inside filter_chains |
http_connection_manager |
Handles raw bytes on the TCP connection — parses the protocol |
| HTTP filter | http_filters inside HCM's typed_config |
router, grpc_stats |
Processes individual HTTP requests/responses after parsing |
The HTTP Connection Manager (HCM) is itself a network filter. It parses HTTP/1.1 and HTTP/2, then passes each request to its own inner pipeline of HTTP filters. The router HTTP filter is always last — it forwards the request to the upstream cluster.
TCP connection
└─ network filter: http_connection_manager
├─ http_filter: grpc_stats ← runs first
└─ http_filter: router ← must be last, sends to cluster
The typed_config pattern¶
Envoy's configuration uses Protocol Buffers for type safety. Every extensible field uses an @type URL so Envoy knows which proto message to deserialize:
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
# ... fields from HttpConnectionManager proto ...
The URL format is always type.googleapis.com/<package>.<MessageName>. The v3 in the path indicates Envoy's v3 xDS API, which is the current stable version. You will see this pattern throughout the tutorial.
Quick reference¶
| URL | What it shows |
|---|---|
http://localhost:9901/ |
Admin home |
http://localhost:9901/clusters |
Cluster health + stats |
http://localhost:9901/stats |
All metrics |
http://localhost:9901/config_dump |
Full running config |
http://localhost:9901/listeners |
Active listeners |
# List services (requires reflection)
grpcurl -plaintext localhost:10000 list
# Call a method
grpcurl -plaintext \
-d '{"name": "World"}' \
localhost:10000 helloworld.Greeter/SayHello
# Add metadata (gRPC headers)
grpcurl -plaintext \
-H 'x-request-id: abc123' \
-d '{"name": "World"}' \
localhost:10000 helloworld.Greeter/SayHello