Envoy doesn’t actually "balance" traffic in the way you might think of a traditional load balancer; it’s more about intelligently directing requests based on a rich set of criteria.

Let’s see Envoy in action. Imagine you have a microservice architecture. You want to route traffic to different versions of your product-catalog service based on the x-user-segment header.

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 8080
    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_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: product_catalog_service
              domains: ["product-catalog.example.com"]
              routes:
              - match:
                  prefix: "/"
                  headers:
                  - name: "x-user-segment"
                    exact_match: "premium"
                route:
                  cluster: product_catalog_premium
              - match:
                  prefix: "/"
                  headers:
                  - name: "x-user-segment"
                    exact_match: "beta"
                route:
                  cluster: product_catalog_beta
              - match:
                  prefix: "/"
                route:
                  cluster: product_catalog_default
          http_filters:
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
  clusters:
  - name: product_catalog_default
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    lb_policy: ROUND_ROBIN
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: product_catalog_default
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 10.0.0.1
                port_value: 8001
  - name: product_catalog_premium
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    lb_policy: ROUND_ROBIN
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: product_catalog_premium
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 10.0.0.2
                port_value: 8002
  - name: product_catalog_beta
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    lb_policy: ROUND_ROBIN
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: product_catalog_beta
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 10.0.0.3
                port_value: 8003

In this configuration, Envoy is acting as a sophisticated router. The listener_0 listens on port 8080. The http_connection_manager filter intercepts incoming HTTP requests. Within its route_config, we define a virtual_host for product-catalog.example.com.

The routes array is where the magic happens. Envoy evaluates these routes in order.

  1. If a request has the header x-user-segment with the exact value premium, it’s routed to the product_catalog_premium cluster.
  2. If the first match fails, Envoy checks the next route. If the x-user-segment header is beta, it goes to product_catalog_beta.
  3. If neither of the preceding conditions is met, the final, catch-all route directs traffic to product_catalog_default.

Each cluster (product_catalog_default, product_catalog_premium, product_catalog_beta) defines a group of upstream service instances. LOGICAL_DNS means Envoy will resolve the provided hostname (though here we’ve used IP addresses directly for clarity, which is less common for dynamic environments) and ROUND_ROBIN is the load balancing policy for distributing requests among the endpoints within that cluster.

The key is that Envoy’s routing rules are evaluated sequentially. The first match that satisfies the incoming request determines the route. This allows for complex traffic shaping: you can send a small percentage of traffic to a new version using weighted routing, route based on request headers, paths, methods, source IPs, and even runtime configuration.

What most people don’t realize is that Envoy’s routing rules are not just about matching headers; they can also involve regular expressions for more flexible string matching on paths or headers, and you can combine multiple match criteria within a single route. For example, you could route requests for /api/v2/users from premium users to a specific cluster, while all other requests to /api/v2/users go elsewhere.

This advanced routing capability is what enables blue-green deployments, canary releases, A/B testing, and fine-grained traffic control in modern microservice architectures.

The next step is often exploring how to dynamically update these routing rules without restarting Envoy.

Want structured learning?

Take the full Load-balancing course →