在 Istio 中使用 EnvoyFilter 配置原始 Envoy 过滤器
对于 Istio 自身 API 未暴露的功能,EnvoyFilter 会直接修补底层的 Envoy 配置 —— 但要谨慎使用。
Istio 的高层资源,如 VirtualService、DestinationRule、AuthorizationPolicy,涵盖了大多数日常流量管理,使用者无需直接了解 Envoy 的配置格式。但 Envoy 拥有的过滤器和选项远多于这些资源所暴露的,为了弥补这个差距,Istio 提供了 EnvoyFilter:一个直接修补 Istio 所生成的 Envoy 配置的资源。它是一个真正的逃生舱口,既强大又同样容易配置出错,本指南介绍它的结构,并给出一个完整的示例。
EnvoyFilter 的用途
如果你需要的功能有专门的 Istio 资源,比如路由规则、重试策略、双向 TLS 设置,就使用那个资源。EnvoyFilter 是为其余一切而存在的:例如 Lua 脚本、请求 tapping、IP 标记,或 Istio 的高层 API 根本没有对应字段的任何其他 Envoy 能力。只有在确认你所需要的东西确实无法通过常规资源表达之后,才去使用它,因为你添加的每个 EnvoyFilter 都是又一个需要在 Istio 自身生成配置随升级演进时保持同步的东西。
每个 EnvoyFilter 都要回答的四个问题
无论配置的是什么,每个 EnvoyFilter 资源都回答同样的四个问题:
- 它应用于哪些代理?
workloadSelector通过标签指定特定的工作负载;省略它则应用于其所在命名空间内的全部工作负载,如果放在 Istio 的根/配置命名空间中,则应用于整个网格。 - 补丁应用在 Envoy 配置的哪个位置?
applyTo字段指明目标:LISTENER、FILTER_CHAIN、HTTP_FILTER、NETWORK_FILTER、CLUSTER、ROUTE_CONFIGURATION,以及其他少数几个。 - 更具体地说,是该事物的哪个实例?
match块进一步缩小范围,例如某个特定的监听器端口、某个特定的过滤器链,或者这是应用于 sidecar 入站流量、sidecar 出站流量还是网关。 - 要执行什么操作,内容是什么?
patch块同时指定操作 ——ADD、REMOVE、MERGE、INSERT_BEFORE、INSERT_AFTER或INSERT_FIRST—— 并且对于任何插入或合并配置的操作,还要把实际的过滤器配置作为其value。
对顺序敏感的变更(在链中的特定位置插入 HTTP 过滤器)使用 INSERT_* 操作,并指向一个具名的相邻项,通常是终端的 envoy.filters.http.router 过滤器,以表达”在它之前”或”在它之后”。
一个完整示例:按 IP 给请求打标签
下面将 ip_tagging HTTP 过滤器添加到 80 端口上的 sidecar 入站流量,插入在终端 router 过滤器之前:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: ip-tagging
namespace: default
spec:
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
portNumber: 80
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
subFilter:
name: envoy.filters.http.router
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.ip_tagging
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ip_tagging.v3.IPTagging
request_type: BOTH
ip_tags:
- ip_tag_name: hello-envoy-conf
ip_list:
- address_prefix: "0.0.0.0"
prefix_len: 0
从上到下解读:没有 workloadSelector,意味着它应用于 default 命名空间中的每个工作负载。applyTo: HTTP_FILTER 表示我们要添加一个 HTTP 过滤器。match 块将范围缩小到 80 端口监听器上的 sidecar 入站流量,具体是在 HTTP Connection Manager 的过滤器链中,相对于 router 过滤器来定位。patch 表示在该 router 之前插入一个新过滤器,并给出它的完整配置:过滤器名称、类型 URL(@type),以及过滤器特有的字段,这里是无论源 IP 为何,都给每个请求打上 X-Envoy-IP-Tags: hello-envoy-conf(0.0.0.0/0 匹配一切)。
应用并确认:
kubectl apply -f ip-tagging-filter.yaml
kubectl exec deploy/httpbin -c istio-proxy -- curl -s -D - http://localhost:80/get -o /dev/null | grep -i x-envoy-ip-tags
查找某个过滤器的名称、类型 URL 和字段
name、typed_config 的 @type,以及其下的字段,都因过滤器而异,它们来自 Envoy 自己的过滤器文档,不过这些文档分散在多个相互链接的页面中,而不是集中在一处:一个过滤器的概览页通常链接到它的 protobuf 参考,后者再定义每个字段,嵌套类型往往还有更多链接。要有大量切换标签页的心理准备。下面几点可以让它更快:
- 一旦拿到类型 URL,就到 GitHub 上搜索它。 对于任何较常见的过滤器,很可能已经有人发布了使用它的可用的
EnvoyFilter或原始 Envoy 配置,这通常比仅凭字段参考自己拼装更快。 - 并非每个 Envoy 过滤器都被编译进了 Istio 的代理构建。 Istio 附带的是特定的 Envoy 构建,envoyproxy.io 上记载的某个过滤器不一定包含在其中。在围绕某个过滤器设计方案之前,请确认它确实在 Istio 的构建中可用。
- 如果觉得 YAML 的缩进在深层嵌套配置中容易出错,可以对
value块使用 JSON 而不是 YAML; Envoy 的配置两者都接受,而嵌套 YAML 块中错位的缩进,是EnvoyFilter悄无声息地没有达到你预期效果的最常见原因之一。
检查实际运行的内容
有两个工具可以帮助你确认补丁是否按预期落地,而不是仅凭行为去猜:
istioctl proxy-config listener POD_NAME -n NAMESPACE
istioctl proxy-config route POD_NAME -n NAMESPACE
istioctl proxy-config cluster POD_NAME -n NAMESPACE
istioctl proxy-config all POD_NAME -n NAMESPACE -o json
istioctl proxy-config 后接 bootstrap、clusters、routes、listeners 或 all,会打印指定 sidecar 经 Istio 和 Envoy 组装后的实际配置,默认是表格,用 -o 可输出 YAML/JSON。这是对实际运行内容的权威依据,而不是你打算修补的内容。对于以可视化方式比较完整的配置转储:完整的 Envoy 配置超出了大多数人愿意在终端中直接阅读的范围;把它渲染成可浏览、带交叉链接结构的配置转储查看器(Envoy 社区维护了几个)通常比滚动浏览原始 JSON 更容易处理。
核心告诫
EnvoyFilter 修补的是 Istio 自己生成、并在每次变更时重新生成的配置,这意味着它天然与 Istio 的内部实现耦合,这一点是高层资源所没有的。针对某个 Istio 版本编写的 EnvoyFilter,在升级改变了 Istio 构造其生成的监听器或过滤器链的方式之后,可能悄无声息地不再匹配任何东西,或者匹配到非预期的内容。请像它的名字所暗示的那样对待它:一个用于特定、已验证的缺口的精确工具,并在每次 Istio 升级之后测试,而不是配置流量的默认方式。