打开扩展点之后升级变得可怕——EnvoyFilter 与 WasmPlugin
目标
亲自确认 API 服务器如何强制校验 EnvoyFilter 的四个位置(applyTo、match.context、patch.operation、priority),并比较用 WasmPlugin 表达同样的需求会有什么不同。最后制作一个检查脚本,遍历集群中所有的 EnvoyFilter,用代码标出版本升级风险。
为什么重要
网格用得久了,就一定会遇到标准 API 无法表达的需求。这时就要用 EnvoyFilter,但这个 API 不是 Istio 的抽象,而是直接触碰 Envoy 的内部配置结构。 因此升级 Istio 版本后,代理内部的过滤器名称或结构可能发生变化,补丁就会悄悄地不再生效——既不报错,也不触发告警。不过,叫人别用也不现实。取而代之的是养成三个习惯:把范围尽可能缩小(命名空间和 workloadSelector),给相对位置补丁写明优先级,并把在哪里插入了什么,用机器可读的检查清单留下来。这样,版本升级前需要重新确认什么,就不必靠人来记忆。
步骤
- 创建
/root/ist-envoyfilter和命名空间ext-lab。然后把集群中已注册的两个扩展 API,按<복수형 이름>\t<API 그룹>\t<kind>(占位符依次为复数形式名称、API 组和 kind)的格式写两行到/root/ist-envoyfilter/ext-apis.tsv——它们是 EnvoyFilter 和 WasmPlugin。按名称升序排列。 - 在
/root/ist-envoyfilter/ef-broken.yaml中写入ext-lab命名空间的 EnvoyFilterinbound-lua,但要故意写错三处——applyTo写HTTP_FILTERS,match.context写SIDECAR_IN,patch.operation写INSERT_BEFORE_ALL。workloadSelector设为app: checkout。不要应用这个文件,只做服务端试运行,并把拒绝的句子保存到/root/ist-envoyfilter/ef-reject.txt。 - 在
/root/ist-envoyfilter/ef-inbound.yaml中写入修正后的 EnvoyFilterinbound-lua,并实际应用到ext-lab——applyTo为HTTP_FILTER,match.context为SIDECAR_INBOUND,patch.operation为INSERT_BEFORE,match.listener.filterChain.filter.name为envoy.filters.network.http_connection_manager。patch.value中只写name: envoy.filters.http.lua,priority先不要加入。 - 创建命名空间
istio-system,并在其中应用 EnvoyFiltermesh-access-log——不设置workloadSelector,applyTo为NETWORK_FILTER,match.context为ANY,patch.operation为MERGE,并写一个把访问日志接到/dev/stdout的typed_config。清单放在/root/ist-envoyfilter/ef-mesh.yaml中。然后针对集群中当前的每个 EnvoyFilter,按<네임스페이스>/<이름>\t<selector 있음 yes|no>(占位符依次为命名空间、名称和是否带有 selector,取值为 yes 或 no)的格式,排序后写入/root/ist-envoyfilter/ef-scope.tsv。 - 把
istioctl analyze -n ext-lab -o json的输出保存到/root/ist-envoyfilter/analyze-before.json——第 3 步中创建的inbound-lua上应该带有IST0151警告。然后在/root/ist-envoyfilter/ef-inbound-priority.yaml中写一个加入了spec.priority且值为10的版本,重新应用,并把同一条命令的输出保存到/root/ist-envoyfilter/analyze-after.json。后者中不应出现IST0151。 - 在
/root/ist-envoyfilter/wasm.yaml中写入ext-lab的 WasmPluginheader-check并应用——selector.matchLabels为app: checkout,url为oci://registry.lab.internal/plugins/header-check:1.0,phase为AUTHZ,priority为 20,pluginConfig.header为x-lab-tier。然后在/root/ist-envoyfilter/wasm-bad.yaml中把header-check-bad写成phase: PRE_AUTHZ,使其在服务端试运行中被拒绝,并把那句话保存到/root/ist-envoyfilter/wasm-reject.txt。 - 再次运行
istioctl analyze -n ext-lab -o json,把<코드>\t<대상>(占位符依次为告警代码和目标对象)逐行排序后写入/root/ist-envoyfilter/findings.tsv。目标直接使用分析结果中origin的值。然后把用istioctl validate检查/root/ist-envoyfilter/ef-inbound.yaml(第 3 步的文件)的输出保存到/root/ist-envoyfilter/validate.txt——亲眼确认分析器抓到的内容与验证器抓到的内容并不相同。 - 创建命名空间
legacy-lab,并应用 EnvoyFilterlegacy-ratelimit——没有workloadSelector,applyTo为HTTP_FILTER,match.context为SIDECAR_INBOUND,patch.operation为INSERT_AFTER,patch.value中只写name: envoy.filters.http.ratelimit(清单为/root/ist-envoyfilter/ef-legacy.yaml)。接着创建/root/ist-envoyfilter/ef-audit.sh,让它把集群中所有的 EnvoyFilter 按<네임스페이스>/<이름>\t<위험코드들>(占位符依次为命名空间、名称和风险代码)排序后只输出到标准输出。风险代码有no-selector(没有 workloadSelector)、no-priority(是相对位置操作却没有 priority)和name-only(没有 typed_config,只有名称)三种,用逗号连接并按字典序书写,一个都没有时写-。把输出保存到/root/ist-envoyfilter/audit-result.txt。
参考
- applyTo、context、operation、phase 都是枚举,所以 API 服务器会返回允许值的列表。
kubectl apply --dry-run=server不会创建对象,只是询问服务器。- IST0151 指的是没有优先级的相对位置补丁。可以用
istioctl analyze -o json只提取代码。 - 常见错误:在 jq 中写成
.spec.priority // "없음"(韩文,意为“无”)这样,0 也会被当作没有。请用== null来比较。 - 常见错误:把放在根命名空间中的 EnvoyFilter 误认为是命名空间级的。那里是整个网格。
- 参考:https://istio.io/v1.24/docs/reference/config/networking/envoy-filter/
- 参考:https://istio.io/v1.24/docs/reference/config/proxy_extensions/wasm-plugin/
- 参考:https://istio.io/v1.24/docs/reference/config/analysis/
确认两个扩展 API 注册在哪里
创建 /root/ist-envoyfilter 和命名空间 ext-lab。然后把集群中已注册的两个扩展 API,按 <복수형 이름>\t<API 그룹>\t<kind>(占位符依次为复数形式名称、API 组和 kind)的格式写两行到 /root/ist-envoyfilter/ext-apis.tsv——它们是 EnvoyFilter 和 WasmPlugin。按名称升序排列。
kubectl api-resources 会同时显示复数形式名称、API 组和 kind。这两个扩展 API 位于不同的组——一个与流量 API 在同一个组,另一个在专用于扩展的组。
同时写错三个枚举试试
在 /root/ist-envoyfilter/ef-broken.yaml 中写入 ext-lab 命名空间的 EnvoyFilter inbound-lua,但要故意写错三处——applyTo 写 HTTP_FILTERS,match.context 写 SIDECAR_IN,patch.operation 写 INSERT_BEFORE_ALL。workloadSelector 设为 app: checkout。不要应用这个文件,只做服务端试运行,并把拒绝的句子保存到 /root/ist-envoyfilter/ef-reject.txt。
kubectl apply --dry-run=server 只是询问 API 服务器,不会创建任何东西。如果 CRD 具有结构化 schema,服务器还会一并返回允许值的列表——请数一数三处错误是否一次全部给出。
修正三处并实际应用
在 /root/ist-envoyfilter/ef-inbound.yaml 中写入修正后的 EnvoyFilter inbound-lua,并实际应用到 ext-lab——applyTo 为 HTTP_FILTER,match.context 为 SIDECAR_INBOUND,patch.operation 为 INSERT_BEFORE,match.listener.filterChain.filter.name 为 envoy.filters.network.http_connection_manager。patch.value 中只写 name: envoy.filters.http.lua,priority 先不要加入。
要插入 HTTP 过滤器,还必须指明它位于哪个网络过滤器的过滤器链中。所以 match 中会有 listener.filterChain.filter.name。priority 留到下一步再处理。
作用于整个网格的位置与作用于一个工作负载的位置
创建命名空间 istio-system,并在其中应用 EnvoyFilter mesh-access-log——不设置 workloadSelector,applyTo 为 NETWORK_FILTER,match.context 为 ANY,patch.operation 为 MERGE,并写一个把访问日志接到 /dev/stdout 的 typed_config。清单放在 /root/ist-envoyfilter/ef-mesh.yaml 中。然后针对集群中当前的每个 EnvoyFilter,按 <네임스페이스>/<이름>\t<selector 있음 yes|no>(占位符依次为命名空间、名称和是否带有 selector,取值为 yes 或 no)的格式,排序后写入 /root/ist-envoyfilter/ef-scope.tsv。
放在根命名空间(默认值 istio-system)中的 EnvoyFilter 会作用于整个网格。放在其他命名空间中,则只作用于该命名空间;如果再加上 workloadSelector,就只作用于标签匹配的工作负载。范围越小,事故越小。
相对位置补丁没有优先级时,分析器会发出警告
把 istioctl analyze -n ext-lab -o json 的输出保存到 /root/ist-envoyfilter/analyze-before.json——第 3 步中创建的 inbound-lua 上应该带有 IST0151 警告。然后在 /root/ist-envoyfilter/ef-inbound-priority.yaml 中写一个加入了 spec.priority 且值为 10 的版本,重新应用,并把同一条命令的输出保存到 /root/ist-envoyfilter/analyze-after.json。后者中不应出现 IST0151。
IST0151 会在使用 INSERT_BEFORE 这类相对位置操作却没有写 priority 时出现。它的意思是:应用顺序得不到保证,补丁可能根本不会被反映。加上 -o json,代码和目标会以机器可读的形式输出。
用标准扩展 API 来写同样的事,会有什么不同
在 /root/ist-envoyfilter/wasm.yaml 中写入 ext-lab 的 WasmPlugin header-check 并应用——selector.matchLabels 为 app: checkout,url 为 oci://registry.lab.internal/plugins/header-check:1.0,phase 为 AUTHZ,priority 为 20,pluginConfig.header 为 x-lab-tier。然后在 /root/ist-envoyfilter/wasm-bad.yaml 中把 header-check-bad 写成 phase: PRE_AUTHZ,使其在服务端试运行中被拒绝,并把那句话保存到 /root/ist-envoyfilter/wasm-reject.txt。
WasmPlugin 不涉及 Envoy 内部结构,只写明“在哪个阶段插入什么”。阶段名称只有四种,API 服务器会告诉你列表。还请一并确认:没有 url 就无法创建。
把分析器在这个命名空间中抓到的内容固化成表
再次运行 istioctl analyze -n ext-lab -o json,把 <코드>\t<대상>(占位符依次为告警代码和目标对象)逐行排序后写入 /root/ist-envoyfilter/findings.tsv。目标直接使用分析结果中 origin 的值。然后把用 istioctl validate 检查 /root/ist-envoyfilter/ef-inbound.yaml(第 3 步的文件)的输出保存到 /root/ist-envoyfilter/validate.txt——亲眼确认分析器抓到的内容与验证器抓到的内容并不相同。
用 jq -r '.[] | .code + "\t" + .origin' 就能直接得到两列的表。validate 只看一个文件,所以抓不到只有从集群状态才能知道的东西。这两个工具不是替代关系,而是在不同时点进行的检查。
把版本升级前要运行的检查清单做成脚本
创建命名空间 legacy-lab,并应用 EnvoyFilter legacy-ratelimit——没有 workloadSelector,applyTo 为 HTTP_FILTER,match.context 为 SIDECAR_INBOUND,patch.operation 为 INSERT_AFTER,patch.value 中只写 name: envoy.filters.http.ratelimit(清单为 /root/ist-envoyfilter/ef-legacy.yaml)。接着创建 /root/ist-envoyfilter/ef-audit.sh,让它把集群中所有的 EnvoyFilter 按 <네임스페이스>/<이름>\t<위험코드들>(占位符依次为命名空间、名称和风险代码)排序后只输出到标准输出。风险代码有 no-selector(没有 workloadSelector)、no-priority(是相对位置操作却没有 priority)和 name-only(没有 typed_config,只有名称)三种,用逗号连接并按字典序书写,一个都没有时写 -。把输出保存到 /root/ist-envoyfilter/audit-result.txt。
用 jq 遍历 kubectl get envoyfilter -A -o json,就能一次统计完。如果像 .spec.priority // "" 这样使用默认值运算符,0 会被看作没有,所以请用 == null 来比较。相对位置操作有 INSERT_BEFORE、INSERT_AFTER、INSERT_FIRST 三种。