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

Envoy 内部结构

不改代码也能造出故障

在 TT Lab 中继续学习

目标

在 HTTP 过滤器链中加入故障注入和速率限制,亲自确认顺序、条件和比例如何改变结果。

为什么重要

过滤器是在不触碰应用的情况下,在它前面做点什么的位置。因此,像“支付变慢了我们会怎样”这样的问题,可以用实测而不是推理来回答。只是这个工具用错了,本身就会变成故障——没有设置条件就开启的故障注入、漏掉代理台数的速率限制限额、顺序颠倒的链,全都是真实发生过事故的样子。亲手做过一次之后,仅凭配置就能认出这三种情况。

步骤

  1. 在 8095 上以 ok 启动上游。在 /root/envd-filter/f-ok.yaml 中放置过滤器只有 router 一个的配置(管理端口 9976,监听器 127.0.0.1:10076,集群 origin),在 /root/envd-filter/f-badorder.yaml 中放置在 router 后面放了 fault 过滤器的配置。分别用 envoy --mode validate 检查这两个文件,在 /root/envd-filter/01-order.txt 中写入 ok_rc= 和 bad_rc= 两行。
  2. 创建 /root/envd-filter/f-abort.yaml——在 router 前面放置 fault 过滤器,用 abort 返回 418,条件为请求头 x-fault: yes。启动后,分别发送带头和不带头的请求,在 /root/envd-filter/02-abort.txt 中写入 with= 和 without= 两行(各自的 HTTP 状态码)。
  3. 创建 /root/envd-filter/f-delay.yaml——把 fault 过滤器的 delay.fixed_delay 设为 2 秒,条件为头 x-slow: yes。启动后,测量带头和不带头的请求的耗时,在 /root/envd-filter/03-delay.txt 中写入 slow_ms=、fast_ms=、gap_ms= 三行(毫秒整数,gap_ms 为两个值之差)。
  4. 创建 /root/envd-filter/f-half.yaml——不设条件,用 abort 返回 503,并把 percentage 设为 50%。启动后请求 20 次,在 /root/envd-filter/04-percentage.txt 中写入 total=20、aborted=(收到 503 的次数)、passed=(收到 200 的次数)三行。
  5. 创建 /root/envd-filter/f-lrl.yaml——不用 fault,而把 local_ratelimit 过滤器放在 router 前面,stat_prefix 为 lrl,令牌为 max_tokens 3、tokens_per_fill 3、fill_interval 300s,启用和强制执行均为 100%。启动后请求 6 次,在 /root/envd-filter/05-ratelimit.txt 中写入 ok=、limited=、codes= 三行(codes 为收到的状态码,用空格分隔)。
  6. 创建 /root/envd-filter/f-perroute.yaml——把全局 local_ratelimit 的 filter_enabled 设为 0% 使其关闭,为路由 /tight 通过 typed_per_filter_config 设置 1 个令牌(stat_prefix: tight),为 /loose 设置 5 个令牌(stat_prefix: loose)。启动后,向 /tight 请求 3 次,向 /loose 请求 3 次,在 /root/envd-filter/06-perroute.txt 中写入 tight_ok=、tight_limited=、loose_ok=、loose_limited= 四行。
  7. 从当前运行的 Envoy 的 /stats 中提取与速率限制相关的统计,在 /root/envd-filter/07-stats.txt 中写入 tight_rate_limited=、loose_rate_limited=、enabled_total= 三行(前两个是各个 stat_prefix 的 rate_limited 值,最后一个是两个 stat_prefix 的 enabled 值之和)。
  8. 在 /root/envd-filter/08-report.md 中写入 router_last=(终端过滤器必须在最后则为 yes)、abort_status=(第 2 步收到的状态码)、delay_ms=(第 3 步的 gap_ms)、tight_allowed=(第 6 步中 /tight 放行的次数)四行,并在下面至少写四行学到的内容。

参考

链的末尾是固定的

在 8095 上以 ok 启动上游。在 /root/envd-filter/f-ok.yaml 中放置过滤器只有 router 一个的配置(管理端口 9976,监听器 127.0.0.1:10076,集群 origin),在 /root/envd-filter/f-badorder.yaml 中放置在 router 后面放了 fault 过滤器的配置。分别用 envoy --mode validate 检查这两个文件,在 /root/envd-filter/01-order.txt 中写入 ok_rc= 和 bad_rc= 两行。

过滤器链的最后一个过滤器承担把请求真正发往上游的职责。它就是 router,这样的过滤器称为终端过滤器。在终端过滤器后面放任何东西,该过滤器都永远不会被执行,所以 Envoy 不会放任这种状态在运行时存在,而是在读取配置时就拒绝。拒绝信息中会直接给出原因,请读一读。

只中断符合条件的请求

创建 /root/envd-filter/f-abort.yaml——在 router 前面放置 fault 过滤器,用 abort 返回 418,条件为请求头 x-fault: yes。启动后,分别发送带头和不带头的请求,在 /root/envd-filter/02-abort.txt 中写入 with= 和 without= 两行(各自的 HTTP 状态码)。

故障注入是在生产运行中确认“这个服务挂了,我们的服务会怎样”的工具。所以设置条件是关键——不设条件就启用,所有用户都会成为对象。使用头条件,就可以只让测试工具附加该头,使它只让自己的请求遇到故障。418 是实际服务中不会使用的状态码,所以用于实验时很醒目。

不中断,而是让它变慢

创建 /root/envd-filter/f-delay.yaml——把 fault 过滤器的 delay.fixed_delay 设为 2 秒,条件为头 x-slow: yes。启动后,测量带头和不带头的请求的耗时,在 /root/envd-filter/03-delay.txt 中写入 slow_ms=、fast_ms=、gap_ms= 三行(毫秒整数,gap_ms 为两个值之差)。

变慢比中断更难处理。中断会立刻发现,而变慢则是连接堆积、线程被占用之后才变成故障。所以要确认超时和断路器是否设置得当,需要的是让请求变慢的实验,而不是中断的实验。耗时用 curl -w '%{time_total}' 测量——它是以秒为单位的小数,要换算成毫秒需要做乘法。

只挑出一部分来中断

创建 /root/envd-filter/f-half.yaml——不设条件,用 abort 返回 503,并把 percentage 设为 50%。启动后请求 20 次,在 /root/envd-filter/04-percentage.txt 中写入 total=20、aborted=(收到 503 的次数)、passed=(收到 200 的次数)三行。

比例是对每个请求独立抽取的概率,并不是“二十次中恰好十次”。所以用同样的配置再统计一次,数字就会不同。这与金丝雀验证中出现“比例对不上”报告的原因相同。在生产环境中使用故障注入时,一开始就把比例设得很低,也是出于这个性质——即使只有百分之一,请求多了也足以构成足够的样本。

在代理内部统计令牌

创建 /root/envd-filter/f-lrl.yaml——不用 fault,而把 local_ratelimit 过滤器放在 router 前面,stat_prefix 为 lrl,令牌为 max_tokens 3、tokens_per_fill 3、fill_interval 300s,启用和强制执行均为 100%。启动后请求 6 次,在 /root/envd-filter/05-ratelimit.txt 中写入 ok=、limited=、codes= 三行(codes 为收到的状态码,用空格分隔)。

本地速率限制只在该代理内部统计令牌。如果有十台代理,限额实际上就是十倍,所以要遵守整体限额,就必须按台数除开来设置,或者使用外部速率限制服务。作为交换,它不向外部查询,所以没有延迟,该服务挂掉也不受影响。超出限额的请求会收到 429。把 fill_interval 设得长一些,实验期间令牌就不会重新补满,结果会更稳定。

为每条路由设置不同的过滤器配置

创建 /root/envd-filter/f-perroute.yaml——把全局 local_ratelimit 的 filter_enabled 设为 0% 使其关闭,为路由 /tight 通过 typed_per_filter_config 设置 1 个令牌(stat_prefix: tight),为 /loose 设置 5 个令牌(stat_prefix: loose)。启动后,向 /tight 请求 3 次,向 /loose 请求 3 次,在 /root/envd-filter/06-perroute.txt 中写入 tight_ok=、tight_limited=、loose_ok=、loose_limited= 四行。

过滤器以监听器为单位挂载,但配置可以按路由或虚拟主机为单位覆盖——这就是 typed_per_filter_config。只对登录路径严格、对查询路径宽松,这样的需求就是这样解决的。把全局配置关闭、只在路由上开启,也是常见的做法。键是过滤器的名称,值里要把该过滤器的配置完整地重新写一遍。为每条路由设置不同的 stat_prefix,在统计中也能分开查看。

读取过滤器留下的数字

从当前运行的 Envoy 的 /stats 中提取与速率限制相关的统计,在 /root/envd-filter/07-stats.txt 中写入 tight_rate_limited=、loose_rate_limited=、enabled_total= 三行(前两个是各个 stat_prefix 的 rate_limited 值,最后一个是两个 stat_prefix 的 enabled 值之和)。

过滤器会留下以自己名称命名的统计。本地速率限制的名称形如 <그 자리의 stat_prefix>.http_local_rate_limit.<항목>(占位符依次为该位置的 stat_prefix 与项目名称),项目有 enabled(成为对象的请求数)、rate_limited(真正被拦截的数量)、ok(放行的数量)。在生产环境中重要的不是被拦截的数量本身,而是被拦截的数量与成为对象的数量的比例——如果这个比例持续偏高,就说明限额与现实不符。如果不确定名称,请先用 curl -s localhost:<admin>/stats | grep rate_limit 找一找。

留下过滤器设计备忘

在 /root/envd-filter/08-report.md 中写入 router_last=(终端过滤器必须在最后则为 yes)、abort_status=(第 2 步收到的状态码)、delay_ms=(第 3 步的 gap_ms)、tight_allowed=(第 6 步中 /tight 放行的次数)四行,并在下面至少写四行学到的内容。

说明行中要写下每种机制什么时候该用、什么时候不能用。例如,故障注入要设置条件后使用,本地速率限制的限额会随代理台数而成倍增加,等等。值请取自前面步骤的文件。