TT Lab
开始
学习 学习路径 课程

Envoy 内部结构

拆开四个虚拟主机与五种匹配

在 TT Lab 中继续学习

目标

确认 Host 头会先决定虚拟主机,并逐一加上各种匹配类型和目的地类型,观察同一个请求会去往哪里。

为什么重要

路由配置在人看来像一份“规则列表”,实际上却是两步选择。而且这两步遵循不同的规则——前一步看具体程度,后一步看顺序。不了解这个区别,就会在上下移动规则上浪费时间。此外,再亲手确认 weighted_clusters 是概率而不是保证,可以用 direct_response 在不碰应用的情况下收尾,以及头会在三个层次上各自追加,别人写的路由表几分钟就能读懂。

步骤

  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)。
  2. 把 /root/envd-route/route.yaml 复制为 /root/envd-route/route-dup.yaml,然后再创建一个 domains: ["*"] 的虚拟主机(名称不要重复)。用 envoy --mode validate 检查,并把输出和退出码保存到 /root/envd-route/02-onestar.txt(最后一行为 rc=1)。
  3. 在 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。
  4. 再添加两条指向 /api 的路由(放在正则路由之下、/ 路由之上)。一条用 headers 条件,在 x-canary: yes 时返回 m=header,另一条用 query_parameters 条件,在 debug=1 时返回 m=query。在 /root/envd-route/04-cond.txt 中写入 header=、query=、plain=(不带条件,只请求 /api)三行。
  5. 启动两个上游(8082、8083)并创建集群 blue、green 之后,让 /split 路由用 weighted_clusters 按 75 比 25 分流。用 --concurrency 1 启动并请求 40 次,在 /root/envd-route/05-weighted.txt 中写入 blue=、green=、total= 三行(各上游收到的次数及总数)。
  6. 添加两条路由——path: "/healthz" 用 direct_response 返回 200 和 alive,prefix: "/old" 用 redirect 以 301 转到 /new。在 /root/envd-route/06-direct.txt 中写入 health=(响应正文)、redirect_code=(HTTP 状态码)、redirect_url=(Location 头的值)三行。
  7. 在三个层次上分别添加响应头 x-level——路由上为 route,虚拟主机上为 virtualhost,路由表上为 routeconfig。请求 shop.envd.test 的 /zzz,把返回的 x-level 头全部写入 /root/envd-route/07-headers.txt 的一行 levels= 中,不用逗号,用空格连接,并在 count= 行写入个数。
  8. 在 /root/envd-route/08-report.md 中写入 vhost_order=(虚拟主机具体程度的顺序,格式为 exact,suffix,prefix,star)、star_limit=(一个路由表中可以放置的 * 虚拟主机数量)、blue_share=(第 5 步中 blue 收到的比例,百分比整数)、redirect_code=(第 6 步的状态码)四行,并在下面至少写四行学到的内容。

参考

先确定虚拟主机,再看路由表

在 /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 次中出现了几次得出。说明行中要写下像“权重是比例,不是保证”这样将来能帮到自己的句子。