Split Four Virtual Hosts and Five Match Kinds
Goal
Confirm that the virtual host is decided first by the Host header, and add the kinds of matches and the kinds of destinations one at a time to see where the same request goes.
Why it matters
To a human reader, routing configuration looks like a "list of rules", but it is really two stages of selection. And the two stages run on different rules — the first by specificity, the second by order. If you do not know this difference, you waste time moving rules up and down. On top of that, if you confirm by hand that weighted_clusters is a probability, not a guarantee, that you can finish things with direct_response without touching the app, and that headers are added separately in three layers, you can read someone else's route table in a few minutes.
Steps
- In
/root/envd-route/route.yaml, put four virtual hosts —exact(shop.envd.test),suffix(*.envd.test),prefixw(shop.*) andanyhost(*). Each returnsvh=exact,vh=suffix,vh=prefixwandvh=anyrespectively at/(admin 9931, listener127.0.0.1:10031). After you start it, request/zzzwith four different Host headers and write the results to/root/envd-route/01-vhosts.txtas four lines,exact=,suffix=,prefixw=andany=(the Host values to request are, in order,shop.envd.test,www.envd.test,shop.other.testandnowhere.example). - Copy
/root/envd-route/route.yamlto/root/envd-route/route-dup.yaml, then create one more virtual host withdomains: ["*"](give it a name that does not collide). Check it withenvoy --mode validateand save the output and exit code to/root/envd-route/02-onestar.txt(the last line isrc=1). - Add two routes above the
/route of theexactvirtual host —path: "/exact"returnsm=path, andsafe_regexwith^/id/[0-9]+$returnsm=regex. In/root/envd-route/03-match.txt, write three lines:path=(the/exactrequest),regex=(the/id/42request) andregex_miss=(the/id/abcrequest). The Host is alwaysshop.envd.test. - Add two more routes for
/api(below the regex routes, above the/route). One uses aheaderscondition and returnsm=headerwhenx-canary: yes, and the other uses aquery_parameterscondition and returnsm=querywhendebug=1. In/root/envd-route/04-cond.txt, write three lines:header=,query=andplain=(just/api, without conditions). - Start two upstreams (
8082,8083), create clustersblueandgreen, and make the/splitroute send traffic split 75 to 25 throughweighted_clusters. Start it with--concurrency 1, request 40 times, and in/root/envd-route/05-weighted.txtwrite three lines,blue=,green=andtotal=(the number each upstream received, and the sum). - Add two routes —
path: "/healthz"sends 200 andalivethroughdirect_response, andprefix: "/old"sends a 301 to/newthroughredirect. In/root/envd-route/06-direct.txt, write three lines:health=(the response body),redirect_code=(the HTTP code) andredirect_url=(the Location header value). - Add the response header
x-levelin each of the three layers —routeon the route,virtualhoston the virtual host, androuteconfigon the route table. Request/zzzonshop.envd.test, write all thex-levelheaders that come back on one line,levels=, in/root/envd-route/07-headers.txt, joined by spaces with no commas, and write the count on thecount=line. - In
/root/envd-route/08-report.md, write four lines —vhost_order=(the virtual host specificity order in the formatexact,suffix,prefix,star),star_limit=(the number of*virtual hosts you can put in one route table),blue_share=(the share blue received in step 5, as an integer percentage) andredirect_code=(the code from step 6) — and below them write what you learned in at least four lines.
Notes
- When you start Envoy, use
setsid --fork nohup envoy -c <파일> --log-level warn --concurrency 1 > <로그> 2>&1 </dev/null(the placeholders are the file and the log), and before you start it again, clean up withpkill -x envoy. - Wait for startup not with a fixed
sleepbut with a loop that runs until/readyreturns LIVE. - To attach a name to a request, use
curl -H "Host: 이름"(the placeholder is the name). An address that contains a query string must be wrapped in quotes so the shell does not swallow the?. - The server for imitating an upstream is
python3 /opt/lab/envoy/upstream.py <포트> ok(the placeholder is the port). The response body contains the port, so you can count which side received it. - Common mistake — if you put a route with conditions below a broad
prefix: "/", it never matches. - Common mistake — if you leave out
--concurrency 1and count the distribution, the numbers differ every time.
The virtual host is decided before the route table
In /root/envd-route/route.yaml, put four virtual hosts — exact (shop.envd.test), suffix (*.envd.test), prefixw (shop.*) and anyhost (*). Each returns vh=exact, vh=suffix, vh=prefixw and vh=any respectively at / (admin 9931, listener 127.0.0.1:10031). After you start it, request /zzz with four different Host headers and write the results to /root/envd-route/01-vhosts.txt as four lines, exact=, suffix=, prefixw= and any= (the Host values to request are, in order, shop.envd.test, www.envd.test, shop.other.test and nowhere.example).
Before it looks at the route table, the virtual host is decided first by the Host (or :authority) header. So half of "I definitely wrote the route but it does not match" is choosing the virtual host wrongly. There are two kinds of wildcards, suffix (*.foo.com) and prefix (foo.*), and neither matches an empty string. Send the request with curl -H "Host: 이름" http://127.0.0.1:포트/zzz (the placeholders are the name and the port).
Only one * can exist in a whole route table
Copy /root/envd-route/route.yaml to /root/envd-route/route-dup.yaml, then create one more virtual host with domains: ["*"] (give it a name that does not collide). Check it with envoy --mode validate and save the output and exit code to /root/envd-route/02-onestar.txt (the last line is rc=1).
Virtual host selection runs on "the most specific one wins", and with two * there is no way to decide which is more specific. So Envoy does not leave this state confusing at run time; it rejects it when it reads the configuration. The rejection message includes the name of the route table, so copy that line as it is.
Match by exact path and by regular expression
Add two routes above the / route of the exact virtual host — path: "/exact" returns m=path, and safe_regex with ^/id/[0-9]+$ returns m=regex. In /root/envd-route/03-match.txt, write three lines: path= (the /exact request), regex= (the /id/42 request) and regex_miss= (the /id/abc request). The Host is always shop.envd.test.
There are three kinds of matches — prefix (if the beginning is the same), path (must be exactly the same) and safe_regex (regular expression). The first two are fast, so use regular expressions only when you really need them. When the regular expression does not match, that route is skipped and it keeps going down — the important point is that it is not a 404; the next route accepts it. Write safe_regex in the form { regex: "..." }.
Split the same path by header and by query string
Add two more routes for /api (below the regex routes, above the / route). One uses a headers condition and returns m=header when x-canary: yes, and the other uses a query_parameters condition and returns m=query when debug=1. In /root/envd-route/04-cond.txt, write three lines: header=, query= and plain= (just /api, without conditions).
There are many requirements in practice that cannot be split by path alone — only internal testers get the new version, only requests with a debug query go to a different backend. So a match can carry headers and query_parameters conditions as well as the path, and all the conditions inside one match must be satisfied. If two routes use the same prefix, the one on top is judged first, so put the one with conditions on top.
One route splits traffic across two clusters
Start two upstreams (8082, 8083), create clusters blue and green, and make the /split route send traffic split 75 to 25 through weighted_clusters. Start it with --concurrency 1, request 40 times, and in /root/envd-route/05-weighted.txt write three lines, blue=, green= and total= (the number each upstream received, and the sum).
A weight is a ratio, not a guarantee. And because the state is separate for each worker thread, if you count with the default concurrency (the number of cores), the numbers differ every time — that is why this step must be started with one worker. Start the upstreams with python3 /opt/lab/envoy/upstream.py <포트> ok (the placeholder is the port), and since the response body contains the port, you can count with sort | uniq -c.
Respond without an upstream, and move an old path
Add two routes — path: "/healthz" sends 200 and alive through direct_response, and prefix: "/old" sends a 301 to /new through redirect. In /root/envd-route/06-direct.txt, write three lines: health= (the response body), redirect_code= (the HTTP code) and redirect_url= (the Location header value).
direct_response answers directly from Envoy without going to an upstream — you can finish a health check path or a maintenance notice page at the proxy without putting it in the app. redirect defaults to 302, so for a permanent move you must write response_code: MOVED_PERMANENTLY. You can get both values at once with curl -o /dev/null -w '%{http_code} %{redirect_url}'.
There are three places to attach headers
Add the response header x-level in each of the three layers — route on the route, virtualhost on the virtual host, and routeconfig on the route table. Request /zzz on shop.envd.test, write all the x-level headers that come back on one line, levels=, in /root/envd-route/07-headers.txt, joined by spaces with no commas, and write the count on the count= line.
Header manipulation can be used in three places: the route, the virtual host and the route table. They are not overwritten; each one is added — so several headers with the same name can remain, and a client may see them as one joined by commas. They are applied from the inside (the route) to the outside (the route table). Get only the headers with curl -sI and grep -i x-level.
Summarize it as rules for reading a routing table
In /root/envd-route/08-report.md, write four lines — vhost_order= (the virtual host specificity order in the format exact,suffix,prefix,star), star_limit= (the number of * virtual hosts you can put in one route table), blue_share= (the share blue received in step 5, as an integer percentage) and redirect_code= (the code from step 6) — and below them write what you learned in at least four lines.
Calculate the ratio yourself — it comes from how many of the 40 requests it was. In the explanation lines, write sentences that will rescue you next time, such as "a weight is a ratio, not a guarantee".