This episode covers advanced routing: weighted clusters for canaries, mirror traffic and shadowing, header-based routing with path rewrites, and fault injection for chaos engineering.

Episode 4 gave you basic routing; episode 18 takes routing to traffic engineering. Advanced routing and traffic shaping lets you send a portion of requests to a new version (canary), duplicate traffic for testing (mirroring), route based on headers, and deliberately inject failures. These capabilities answer the question that always comes up in production: how do you test changes with minimal risk.
Weighted clusters split requests across several clusters by percentage:
virtual_hosts:
- name: api_vh
domains:
- api.example.com
routes:
- match:
prefix: "/v1/orders"
route:
weighted_clusters:
clusters:
- name: orders_v1
weight:
value: 95
- name: orders_v2
weight:
value: 5With weighted_clusters, 95 percent of /v1/orders requests go to orders_v1 and 5 percent to orders_v2. The total weight doesn't have to be 100; Envoy normalizes proportionally. This is the most basic and most widely used canary mechanism.
A common canary strategy: start at 1 percent, observe metrics and logs, then raise to 10, 50, up to 100 percent. Because config can be changed via xDS without restarts, the shift can be done in minutes.
Weights can be defined in endpoint metadata so the control plane determines proportions dynamically. For the lab, just change weight and reload the config.
Mirroring sends a copy of a request to another cluster without changing the client's response:
routes:
- match:
prefix: "/v1/orders"
route:
cluster: orders_v1
request_mirror_policies:
- cluster: orders_v2
runtime_fraction:
default_value:
numerator: 10
denominator: HUNDRED
trace_sampled: trueThe request_mirror_policies block also sends 10 percent of requests to orders_v2. The client still receives the response from orders_v1; orders_v2's result is discarded — only observability sees it. This is how you test a new version with real traffic without risk.
Shadowing answers the question "will the new version behave the same?" without routing real users. Compare the shadow response with the real response in telemetry, then decide based on data. trace_sampled: true ensures mirror requests are also recorded in tracing.
A few things to watch out for:
runtime_fraction to change the mirror percentage dynamically.Routing can be triggered by specific header values:
routes:
- match:
prefix: "/api/"
headers:
- name: X-Canary
string_match:
exact: "true"
route:
cluster: api_canary
- match:
prefix: "/api/"
route:
cluster: api_stableThe first route catches every request with the header X-Canary: true and sends it to api_canary. Everything else goes to api_stable. This pattern is used for internal testing: QA teams send a special header, and normal traffic is unaffected.
Matches can combine path, header, and query parameter at the same time to map a very specific traffic subset — for example the prefix /api/, the header X-Tenant: tenant-a, and the query beta=1.
Combine header-based routing with the rewrites you learned in episode 4:
routes:
- match:
prefix: "/api/v2"
headers:
- name: X-Experimental
string_match:
prefix: "1"
route:
cluster: experimental_backend
prefix_rewrite: "/v2/internal"prefix_rewrite: "/v2/internal" changes the path before reaching the experimental backend. With this combination, one listener can be a gateway for many routing strategies at once.
The fault injection from episode 10 is now used as a precise chaos engineering tool — combined with routing:
routes:
- match:
prefix: "/v1/orders"
headers:
- name: X-Chaos
string_match:
exact: "1"
route:
cluster: orders_v1
typed_per_filter_config:
envoy.filters.http.fault:
"@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.Fault
abort:
http_status: 500
percentage:
numerator: 100
denominator: HUNDREDThe fault configuration makes every request with the header X-Chaos: 1 fail with a 500 status. By triggering this header on a subset of requests, you can test whether retry and circuit breaking work when the new version misbehaves.
The recommended testing sequence:
Never inject faults into all traffic at once. Start with a small fraction and increase gradually. Envoy's fault injection has a runtime_fraction that can be changed dynamically, giving you full control while an experiment runs.
curl -s -o /dev/null -w "%{http_code}\n" \
-H "Host: api.example.com" -H "X-Chaos: 1" http://localhost:10000/v1/orders
curl -s localhost:9901/stats | grep "upstream_rq_5xx"A request with X-Chaos: 1 should produce a 500, then the upstream_rq_5xx metric increases. That's a quick way to prove fault injection is active and measurable.
Episode 18 gave you full control over traffic shape: weighted clusters for canaries, mirroring for shadowing, header-based routing, and fault injection for precise chaos engineering.
Key takeaways:
weighted_clusters splits traffic proportionally for canaries.request_mirror_policies sends request copies without affecting clients.In the next episode, episode 19, we'll discuss Envoy in Kubernetes and service mesh ecosystems — Envoy sidecars in Istio, the difference between running Envoy directly versus as a mesh data plane, and deployment patterns for Kubernetes workloads.