EnvoyFilter 是补丁,而不是翻译
一句话总结
EnvoyFilter 不是转换,而是补丁。istiod 先用其他资源生成完整的 Envoy 配置,然后通过 applyTo、context、match 选定结果中的某一处,再用 operation 插入或修改一段内容。插入的值就是 Envoy 配置本身,所以会出现 istioctl 放行的补丁,却被 Envoy 拒绝的情况。
为什么需要它
VirtualService、DestinationRule、AuthorizationPolicy 只暴露了 Envoy 能力中的一部分。有时会需要这些之外的功能,比如本地请求限制、处理特定请求头的 Lua,或者网格还没有用 API 封装的新过滤器。Istio 不可能为 Envoy 的每一项功能都新建 API,所以保留了一个直接修改生成配置的应急出口,这就是 EnvoyFilter。
应急出口是有代价的。其他资源由 istiod 理解其含义并负责转换,而 EnvoyFilter 的值对 istiod 来说是无法理解的一整块内容。istiod 的转换结果(过滤器名称、顺序、形态)可能随版本变化,而补丁正是依赖这个结果的。这就是官方文档在开头就警告“使用不当可能会使整个网格变得不稳定”的原因。实际上,即使是正确的 EnvoyFilter,istioctl 1.24.2 也会附上“EnvoyFilter exposes internal implementation details that may change at any time”这条警告。
工作原理
一个补丁是“在哪里”和“做什么”的组合。
| 项 | 选择的内容 | 示例 |
|---|---|---|
applyTo |
作用的 Envoy 对象种类 | LISTENER · FILTER_CHAIN · NETWORK_FILTER · HTTP_FILTER · CLUSTER · ROUTE_CONFIGURATION |
match.context |
哪个代理、哪个方向 | SIDECAR_INBOUND · SIDECAR_OUTBOUND · GATEWAY · ANY |
match.listener |
该种类中的具体哪一个 | 端口、filterChain.filter.name(网络过滤器)、subFilter.name(HTTP 过滤器) |
match.proxy.proxyVersion |
哪个版本的代理 | ^1\.24.* 之类的正则表达式 |
patch.operation |
如何操作 | INSERT_BEFORE · INSERT_AFTER · INSERT_FIRST · MERGE · REPLACE · REMOVE · ADD |
如果给 HTTP_FILTER 指定 subFilter: envoy.filters.http.router 和 INSERT_BEFORE,那么在入站一侧的 HTTP 连接管理器的过滤器列表中,router 的正前方会原样插入一个条目,其内容就是 value。所以 value 不是 Istio 的语法,而是 Envoy 的语法。如果照抄 VirtualService 的 percentage: { value: 100 },Envoy 的 fault 过滤器(使用分子和分母的 FractionalPercent)是无法理解的。
顺序也有规则。根命名空间(istio-system)的 EnvoyFilter 先应用,工作负载命名空间的后应用,同一工作负载上有多个时,则按创建时间排序。如果相互冲突,结果是不确定的。如果找不到名称匹配的对象,REPLACE 什么也不做——不会报错,悄悄地就被漏掉了。作用范围由 workloadSelector 决定。有它则作用于标签匹配的工作负载,没有则作用于该命名空间的全部,放在根命名空间则作用于整个网格。
还需要了解检查能做到什么程度。istioctl 会检查 EnvoyFilter 的外层结构,并尝试把值解析为 Envoy 类型。如果完全没有 @type 名称,会以错误(rc=1)拦截,但该类型中不存在的字段只会给出警告,rc 仍为 0。而且,对于“router 必须是列表中的最后一个”之类的 Envoy 组装规则,它一无所知。最终的判定由 Envoy 来做。
在现场相遇的样子
加入了 EnvoyFilter,却毫无变化。在生产环境中,如果 Envoy 拒绝了打过补丁的监听器,Pod 仍会正常运行,旧配置原样保留(xDS NACK)。在 istioctl proxy-status 中,只有那个代理的配置不一致,istiod 日志中会打印出拒绝的原因。典型情况就是把过滤器插在 router 之后的补丁。istioctl analyze 对没有优先级的相对位置操作给出 IST0151 警告,也是出于同样的原因——如果基准过滤器还不存在,补丁就不会生效。
CI 是绿的,部署之后却坏了。如果流水线只看 istioctl validate 的退出码,值中错误的字段就会作为警告被放过去。唯独对 EnvoyFilter,最好把警告当作失败处理,或者用 envoy --mode validate 把放入同样值的 Envoy 配置再筛查一遍。
升级当天,一半的网格变得异常。如果过滤器名称变了,或者 istiod 生成的形态变了,旧补丁就会附着在错误的位置,或者被拒绝。如果用 proxyVersion 限定了版本,补丁就不会附加到新版本的代理上,所以不是崩溃,而是在缺少该功能的状态下启动。正确的做法是另外制作适用于新版本的补丁,一起放着再进行切换。
官方文档:EnvoyFilter · Envoy HTTP filters
下一项实验要做什么
编写把 fault 过滤器插入 router 之前的 EnvoyFilter,亲手建立应用补丁之后的结果,并得到 418。再分别做出把同一个过滤器插入 router 之后的版本,以及照搬 VirtualService 风格百分比的版本,两次观察 istioctl 放行而 Envoy 拒绝的空隙。最后,把限定版本的正则表达式与实际的代理版本对照,并用不带选择器的请求限制过滤器确认作用范围。