Why the Route You Wrote Never Matches
In one line
A request chooses twice before it finds its destination. First it picks a virtual host by the Host header, and then it scans that virtual host's route table from the top and uses the first match. The first choice is decided by specificity and the second by order — that these two differ is the whole point of this article.
Why this was needed
Half of the reports that say "I definitely wrote the route but it does not match" are not route problems. The virtual host was chosen wrongly.
One route table (route_config) holds several virtual hosts. Each virtual host has a domains list, and the Host header of the request (:authority in HTTP/2) must match one of them before that virtual host's route table is looked at. The rule for matching here is different from that for routes. Routes are scanned from the top and the first match is used, but virtual hosts use the most specific one.
1. 정확한 이름 www.foo.com
2. 접미 와일드카드 *.foo.com (더 긴 와일드카드가 먼저)
3. 접두 와일드카드 foo.*
4. 특별한 * 아무 이름에나
So even if you put the *.example.com virtual host at the very top of the file, the virtual host that wrote www.example.com exactly wins. To avoid spending a day shuffling the order, you need to know this difference first.
Only exactly one * can exist in a whole route table. With two, there is no way to decide which is more specific, so Envoy rejects the configuration when it reads it instead of leaving you confused at run time.
How it works
Once the virtual host is decided, it looks at the routes inside it from the top. It stops at the first match. What can be used for matching is not only the path.
| Condition | What |
|---|---|
prefix |
If the beginning of the path is the same |
path |
The path must be exactly the same |
safe_regex |
If it matches the regular expression |
headers |
Header value conditions (exact, present, prefix, regex) |
query_parameters |
Query string conditions |
All the conditions inside one match must be satisfied. So a requirement like "only internal testers get the new version" is built by writing prefix and headers together. If a condition does not match, that route is skipped and it keeps going down. It is not a 404; the next route accepts it — that is why you put the narrow routes with conditions on top and the broad prefix: "/" at the very bottom.
The destination is not just one cluster either.
route.cluster— to one clusterroute.weighted_clusters— split by ratio across several clustersdirect_response— Envoy responds directly without going to an upstreamredirect— moves the client to another address (the default is 302, and for a permanent move,MOVED_PERMANENTLY)
There are three places to add headers — the route, the virtual host and the route table. They are not overwritten; each one is added. So several response headers with the same name can remain.
What it looks like in the field
A report that the weights do not come out in proportion. weighted_clusters is a probability, not an allocation table. On top of that, Envoy's worker threads each hold their own state, so with the default concurrency (the number of cores), if you count forty requests, it comes out different every time. When you verify a canary ratio, you have to make the sample large enough or reduce the workers to one. In production, instead of "the ratio does not match", look at the statistics.
Finishing things without touching the app through direct_response. Things the application does not need to know about, such as a health check path, a maintenance notice or a bot-blocking response, are better finished at the proxy. You can fix them without a deployment, and it answers even if the app is dead.
Getting slow from overusing regular expressions. prefix and path are string comparisons, but safe_regex goes through the regex engine. If you fill the front of a table with hundreds of routes entirely with regular expressions, every request runs all of them. Use regular expressions only where nothing else works.
Official documentation: HTTP routing · HTTP route components
What you will do in the next lab
You set up four virtual hosts and check the specificity order yourself, and then see that putting two * makes the configuration rejected. Next you split traffic by path, safe_regex, headers and query_parameters, count a 75 to 25 weighting over forty requests, attach direct_response and redirect, and confirm that headers are added separately in three layers.