Skip to content

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
  • grpcurl installed (brew install grpcurl)
  • kubectl and kind (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:

watch -n1 'curl -s http://localhost:9901/clusters'
It gives you real-time visibility into cluster health and statistics.

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
  1. 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.
  2. listeners — a list of listener objects. Each listener binds to an address + port and contains one or more filter chains.
  3. clusters — a list of cluster objects. Each cluster is a named pool of upstream endpoints. Routes inside listeners refer to clusters by name.
  4. 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.
  5. 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

docker run --rm -it \
  -p 10000:10000 \
  -p 9901:9901 \
  -v $(pwd)/envoy.yaml:/etc/envoy/envoy.yaml \
  envoyproxy/envoy:v1.31-latest \
  envoy -c /etc/envoy/envoy.yaml
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