扩展点给你什么,又要你付出什么
一句话总结
EnvoyFilter 不是 Istio 的抽象,而是直接触碰 Envoy 内部结构的逃生舱,因此不耐版本升级;WasmPlugin 用较少依赖版本的形式来表达同样的需求,但并不能取代所有场景。
为什么需要逃生舱
Istio 的流量 API 是一套经过精心挑选的抽象。发往哪里、等待多久、重试几次——大部分需求都可以用这套语言来表达。但网格用得久了,就一定会遇到这套语言里没有的需求。比如“给所有响应加上公司内部的追踪头”“只在这个工作负载前面做限速”“在访问日志中多加一个业务字段”。
代理本身(Envoy)完全可以做这些事,欠缺的是 Istio 的语言。EnvoyFilter 就是为弥补这一空缺而留出的一扇门——直接写明在 Istio 生成的 Envoy 配置上要叠加的位置和内容。
工作原理
一个 EnvoyFilter 是一组补丁列表,每个补丁要确定四项内容。
| 位置 | 决定什么 | 取值示例 |
|---|---|---|
applyTo |
触碰哪一类 Envoy 配置 | HTTP_FILTER、NETWORK_FILTER、CLUSTER、LISTENER |
match.context |
是哪个方向的配置 | SIDECAR_INBOUND、SIDECAR_OUTBOUND、GATEWAY、ANY |
patch.operation |
如何叠加 | MERGE、ADD、REMOVE、INSERT_BEFORE、INSERT_FIRST |
priority |
多个补丁的应用顺序 | 整数。越小越先 |
这四项的值都是枚举,由 API 服务器强制检查。填入错误的值,对象不会被创建,允许值的列表会原样返回——这是好消息。坏消息是:即使所有值都正确,也不能保证配置一定会生效。match 是在 Envoy 配置中找出目标的条件,找不到的话就什么都不会发生。既没有错误,也没有事件。版本升级使过滤器名称或链结构发生变化时,就会变成这种状态。
这里还有一层。像 INSERT_BEFORE 这样以其他补丁为基准的操作,只有先确定顺序才有意义,而如果不写 priority,顺序就得不到保证。istioctl analyze 会用 IST0151 捕捉这种情况。同一个工具还会指出没有 typed_config 而只写了过滤器名称的情况——只写名称的方式是旧写法,将来会被改掉。
范围同样重要。EnvoyFilter 只作用于它所在的命名空间,但如果放在根命名空间(默认值 istio-system)中,就会作用于整个网格。再加上 workloadSelector,就会缩小到标签匹配的工作负载。事故的大小,就由这一个选择决定。
WasmPlugin 用另一种方式表达同样的需求。它不涉及 Envoy 内部结构,只说明在哪个阶段插入什么——phase 只有 AUTHN、AUTHZ、STATS、UNSPECIFIED_PHASE 四种,代码则另外以 OCI 镜像分发。因为不引用内部结构,所以更耐版本升级。但也有做不到的事——修改集群配置或监听器本身,仍然属于 EnvoyFilter 的范围。
在现场相遇的样子
最常见的事故是“去年放进去的 EnvoyFilter 不知从什么时候起不生效了”。没有人删除它,对象也原样还在。只是在版本升级的某个时间点,匹配条件开始落空而已。提前发现这一点只有两种办法——要么在版本升级前准备一份遍历清单的检查清单,要么在版本升级后建立获取实际代理配置并确认的流程。两者都没有的话,下次发生故障时,它会在原因候选列表的最底部被发现。
第二种是范围定得太宽造成的事故。曾有一个团队为给自己的服务加一个头而创建的 EnvoyFilter,被放进了根命名空间,作用到了整个网格的代理上。评审时之所以不显眼,是因为清单又短又看起来没什么问题。命名空间这一行,就是爆炸半径。
本实验环境的局限
实验 Pod 中既没有 istiod,也没有真正的边车。因此看不到 EnvoyFilter 反映到真实 Envoy 配置中的样子——用 istioctl proxy-config 比较补丁前后,在这个环境中做不到。不过这里有真正的 API 服务器,所以 schema 的强制校验可以实际确认,istioctl analyze 也会读取集群中的对象,给出相同的判定。“声明是否被接受”和“声明是否真正生效”是不同的问题,本实验涉及前一个问题以及在此之前的风险检查。
下一项实验要做什么
同时写错三个枚举,看 API 服务器返回什么,再修正并实际应用。并排创建作用于整个网格的位置和作用于某一个工作负载的位置,比较范围;看没有优先级的相对位置补丁如何被分析器捕捉。用 WasmPlugin 重写同样的需求,最后制作一个检查脚本,遍历集群中所有的 EnvoyFilter,用代码标出版本升级风险。