不改代码也能造出故障
目标
在 HTTP 过滤器链中加入故障注入和速率限制,亲自确认顺序、条件和比例如何改变结果。
为什么重要
过滤器是在不触碰应用的情况下,在它前面做点什么的位置。因此,像“支付变慢了我们会怎样”这样的问题,可以用实测而不是推理来回答。只是这个工具用错了,本身就会变成故障——没有设置条件就开启的故障注入、漏掉代理台数的速率限制限额、顺序颠倒的链,全都是真实发生过事故的样子。亲手做过一次之后,仅凭配置就能认出这三种情况。
步骤
- 在
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=两行。 - 创建
/root/envd-filter/f-abort.yaml——在router前面放置fault过滤器,用abort返回 418,条件为请求头x-fault: yes。启动后,分别发送带头和不带头的请求,在/root/envd-filter/02-abort.txt中写入with=和without=两行(各自的 HTTP 状态码)。 - 创建
/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为两个值之差)。 - 创建
/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_tokens3、tokens_per_fill3、fill_interval300s,启用和强制执行均为 100%。启动后请求 6 次,在/root/envd-filter/05-ratelimit.txt中写入ok=、limited=、codes=三行(codes为收到的状态码,用空格分隔)。 - 创建
/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=四行。 - 从当前运行的 Envoy 的
/stats中提取与速率限制相关的统计,在/root/envd-filter/07-stats.txt中写入tight_rate_limited=、loose_rate_limited=、enabled_total=三行(前两个是各个stat_prefix的rate_limited值,最后一个是两个stat_prefix的enabled值之和)。 - 在
/root/envd-filter/08-report.md中写入router_last=(终端过滤器必须在最后则为 yes)、abort_status=(第 2 步收到的状态码)、delay_ms=(第 3 步的gap_ms)、tight_allowed=(第 6 步中/tight放行的次数)四行,并在下面至少写四行学到的内容。
参考
- 启动 Envoy 时使用
setsid --fork nohup envoy -c <파일> --log-level warn --concurrency 1 > <로그> 2>&1 </dev/null(占位符依次为配置文件与日志文件),重新启动前请用pkill -x envoy清理。 - 等待启动时,不要使用固定的
sleep,而要使用循环,直到/ready返回 LIVE 为止。 - 只获取状态码用
curl -s -o /dev/null -w '%{http_code}',耗时用-w '%{time_total}'。时间是以秒为单位的小数,要换算成毫秒需要做乘法。 - 上游用
python3 /opt/lab/envoy/upstream.py <포트> ok(占位符为端口)启动。 - 常见错误——把
router放在链的最前面或中间。它后面的过滤器永远不会被执行,所以 Envoy 会把整个配置拒绝。 - 常见错误——把
fill_interval设得很短。实验期间令牌会重新补满,导致结果不稳定。
链的末尾是固定的
在 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 放行的次数)四行,并在下面至少写四行学到的内容。
说明行中要写下每种机制什么时候该用、什么时候不能用。例如,故障注入要设置条件后使用,本地速率限制的限额会随代理台数而成倍增加,等等。值请取自前面步骤的文件。