拆开四个虚拟主机与五种匹配
目标
确认 Host 头会先决定虚拟主机,并逐一加上各种匹配类型和目的地类型,观察同一个请求会去往哪里。
为什么重要
路由配置在人看来像一份“规则列表”,实际上却是两步选择。而且这两步遵循不同的规则——前一步看具体程度,后一步看顺序。不了解这个区别,就会在上下移动规则上浪费时间。此外,再亲手确认 weighted_clusters 是概率而不是保证,可以用 direct_response 在不碰应用的情况下收尾,以及头会在三个层次上各自追加,别人写的路由表几分钟就能读懂。
步骤
- 在
/root/envd-route/route.yaml中放置四个虚拟主机——exact(shop.envd.test)、suffix(*.envd.test)、prefixw(shop.*)、anyhost(*)。它们在/上分别返回vh=exact、vh=suffix、vh=prefixw、vh=any(管理端口 9931,监听器127.0.0.1:10031)。启动后,用四种 Host 头请求/zzz,把结果以exact=、suffix=、prefixw=、any=四行写入/root/envd-route/01-vhosts.txt(请求的 Host 依次为shop.envd.test、www.envd.test、shop.other.test、nowhere.example)。 - 把
/root/envd-route/route.yaml复制为/root/envd-route/route-dup.yaml,然后再创建一个domains: ["*"]的虚拟主机(名称不要重复)。用envoy --mode validate检查,并把输出和退出码保存到/root/envd-route/02-onestar.txt(最后一行为rc=1)。 - 在
exact虚拟主机的/路由上面添加两条路由——path: "/exact"返回m=path,用safe_regex匹配^/id/[0-9]+$的返回m=regex。在/root/envd-route/03-match.txt中写入path=(请求/exact)、regex=(请求/id/42)、regex_miss=(请求/id/abc)三行。Host 均为shop.envd.test。 - 再添加两条指向
/api的路由(放在正则路由之下、/路由之上)。一条用headers条件,在x-canary: yes时返回m=header,另一条用query_parameters条件,在debug=1时返回m=query。在/root/envd-route/04-cond.txt中写入header=、query=、plain=(不带条件,只请求/api)三行。 - 启动两个上游(
8082、8083)并创建集群blue、green之后,让/split路由用weighted_clusters按 75 比 25 分流。用--concurrency 1启动并请求 40 次,在/root/envd-route/05-weighted.txt中写入blue=、green=、total=三行(各上游收到的次数及总数)。 - 添加两条路由——
path: "/healthz"用direct_response返回 200 和alive,prefix: "/old"用redirect以 301 转到/new。在/root/envd-route/06-direct.txt中写入health=(响应正文)、redirect_code=(HTTP 状态码)、redirect_url=(Location 头的值)三行。 - 在三个层次上分别添加响应头
x-level——路由上为route,虚拟主机上为virtualhost,路由表上为routeconfig。请求shop.envd.test的/zzz,把返回的x-level头全部写入/root/envd-route/07-headers.txt的一行levels=中,不用逗号,用空格连接,并在count=行写入个数。 - 在
/root/envd-route/08-report.md中写入vhost_order=(虚拟主机具体程度的顺序,格式为exact,suffix,prefix,star)、star_limit=(一个路由表中可以放置的*虚拟主机数量)、blue_share=(第 5 步中 blue 收到的比例,百分比整数)、redirect_code=(第 6 步的状态码)四行,并在下面至少写四行学到的内容。
参考
- 启动 Envoy 时使用
setsid --fork nohup envoy -c <파일> --log-level warn --concurrency 1 > <로그> 2>&1 </dev/null(占位符依次为配置文件与日志文件),重新启动前请用pkill -x envoy清理。 - 等待启动时,不要使用固定的
sleep,而要使用循环,直到/ready返回 LIVE 为止。 - 要给请求指定名称,使用
curl -H "Host: 이름"(占位符为域名)。带查询字符串的地址必须用引号括起来,shell 才不会吞掉?。 - 用来模拟上游的服务器是
python3 /opt/lab/envoy/upstream.py <포트> ok(占位符为端口)。响应正文中包含端口,所以可以统计是哪一个收到的。 - 常见错误——把带条件的路由放在宽泛的
prefix: "/"之下,它就永远不会命中。 - 常见错误——不加
--concurrency 1来统计分配情况,数字每次都会不同。
先确定虚拟主机,再看路由表
在 /root/envd-route/route.yaml 中放置四个虚拟主机——exact(shop.envd.test)、suffix(*.envd.test)、prefixw(shop.*)、anyhost(*)。它们在 / 上分别返回 vh=exact、vh=suffix、vh=prefixw、vh=any(管理端口 9931,监听器 127.0.0.1:10031)。启动后,用四种 Host 头请求 /zzz,把结果以 exact=、suffix=、prefixw=、any= 四行写入 /root/envd-route/01-vhosts.txt(请求的 Host 依次为 shop.envd.test、www.envd.test、shop.other.test、nowhere.example)。
在查看路由表之前,会先由 Host(或 :authority)头确定虚拟主机。所以,“路由明明写了,却没有命中”,有一半是选错了虚拟主机。通配符有后缀(*.foo.com)和前缀(foo.*)两种,二者都不匹配空字符串。请求用 curl -H "Host: 이름" http://127.0.0.1:포트/zzz(占位符依次为域名与端口)发送。
一个路由表中只能放一个 *
把 /root/envd-route/route.yaml 复制为 /root/envd-route/route-dup.yaml,然后再创建一个 domains: ["*"] 的虚拟主机(名称不要重复)。用 envoy --mode validate 检查,并把输出和退出码保存到 /root/envd-route/02-onestar.txt(最后一行为 rc=1)。
虚拟主机的选择遵循“最具体的获胜”,而有两个 * 时,就无法确定谁更具体。所以 Envoy 不会放任这种状态在运行时造成混乱,而是在读取配置时就拒绝。拒绝信息中会同时给出路由表的名称,请把那一行原样记录下来。
用精确路径和正则表达式来匹配
在 exact 虚拟主机的 / 路由上面添加两条路由——path: "/exact" 返回 m=path,用 safe_regex 匹配 ^/id/[0-9]+$ 的返回 m=regex。在 /root/envd-route/03-match.txt 中写入 path=(请求 /exact)、regex=(请求 /id/42)、regex_miss=(请求 /id/abc)三行。Host 均为 shop.envd.test。
匹配类型有三种——prefix(开头相同)、path(必须完全相同)、safe_regex(正则表达式)。前两种更快,所以只在确有必要时才使用正则。正则不匹配时,会跳过该路由并继续向下扫描——重要的是,这不会返回 404,而是由下一条路由接收。safe_regex 写成 { regex: "..." } 的形式。
用头和查询字符串区分同一个路径
再添加两条指向 /api 的路由(放在正则路由之下、/ 路由之上)。一条用 headers 条件,在 x-canary: yes 时返回 m=header,另一条用 query_parameters 条件,在 debug=1 时返回 m=query。在 /root/envd-route/04-cond.txt 中写入 header=、query=、plain=(不带条件,只请求 /api)三行。
实际工作中,有很多需求只靠路径无法区分——只让内部测试人员使用新版本,只把带调试查询的请求发往另一个后端。因此匹配中除了路径,还可以同时设置 headers 和 query_parameters 条件,并且同一个匹配中的条件必须全部满足。两条路由使用相同的 prefix 时,上面的先判定,所以要把带条件的放在上面。
一条路由把流量分给两个集群
启动两个上游(8082、8083)并创建集群 blue、green 之后,让 /split 路由用 weighted_clusters 按 75 比 25 分流。用 --concurrency 1 启动并请求 40 次,在 /root/envd-route/05-weighted.txt 中写入 blue=、green=、total= 三行(各上游收到的次数及总数)。
权重是比例,不是保证。而且每个工作线程的状态各自独立,所以用默认的 concurrency(核心数)统计时,数字每次都会不同——因此这一步必须只用一个工作线程启动。上游用 python3 /opt/lab/envoy/upstream.py <포트> ok(占位符为端口)启动,响应正文中包含端口,用 sort | uniq -c 统计即可。
不经上游直接响应,并为旧路径做跳转
添加两条路由——path: "/healthz" 用 direct_response 返回 200 和 alive,prefix: "/old" 用 redirect 以 301 转到 /new。在 /root/envd-route/06-direct.txt 中写入 health=(响应正文)、redirect_code=(HTTP 状态码)、redirect_url=(Location 头的值)三行。
direct_response 不去上游,而是由 Envoy 直接应答——可以在代理处收尾,而不必把健康检查路径或维护提示页面放进应用。redirect 默认是 302,所以永久迁移需要写 response_code: MOVED_PERMANENTLY。用 curl -o /dev/null -w '%{http_code} %{redirect_url}' 可以一次得到这两个值。
添加头的位置有三处
在三个层次上分别添加响应头 x-level——路由上为 route,虚拟主机上为 virtualhost,路由表上为 routeconfig。请求 shop.envd.test 的 /zzz,把返回的 x-level 头全部写入 /root/envd-route/07-headers.txt 的一行 levels= 中,不用逗号,用空格连接,并在 count= 行写入个数。
头的操作可以用在路由、虚拟主机、路由表三处。它们不是覆盖,而是各自追加——所以同名的头可能会留下多个,客户端有时会把它们看成用逗号连接的一个。应用顺序是从内层(路由)到外层(路由表)。用 curl -sI 只获取头,再 grep -i x-level。
整理成阅读路由表的规则
在 /root/envd-route/08-report.md 中写入 vhost_order=(虚拟主机具体程度的顺序,格式为 exact,suffix,prefix,star)、star_limit=(一个路由表中可以放置的 * 虚拟主机数量)、blue_share=(第 5 步中 blue 收到的比例,百分比整数)、redirect_code=(第 6 步的状态码)四行,并在下面至少写四行学到的内容。
比例请自己计算——由 40 次中出现了几次得出。说明行中要写下像“权重是比例,不是保证”这样将来能帮到自己的句子。