前四篇讲的是「资源怎么从 CRD 变成 CDS/EDS/RDS/filter
chain/SDS」。Sidecar、Telemetry、EnvoyFilter
这三个资源不生产新的服务或路由语义,而是在生成流水线的不同阶段改写最终配置:Sidecar
裁剪某个代理能看到的输入,Telemetry 往生成好的
Listener 里加可观测配置,EnvoyFilter
直接对生成结果打补丁。三者各自有一套合并/覆盖规则,且都不是「越具体越优先」这么简单的直觉能覆盖的。本文只钉这三层的作用点与排序规则,不讲
Wasm/EnvoyFilter 的插件编程——那是 Envoy
扩展边界 与 Wasm
架构 的范围。
本文是「Istio / 网格控制面内核」系列第 9 篇。→ 系列目录
版本锚定:Istio 1.30.3,源码路径对应
pilot/pkg/model/sidecar.go、pilot/pkg/model/telemetry.go(函数签名以 master 分支验证,1.30.x 语义一致);EnvoyFilter合并规则引用 Istio 1.30 官方参考文档。
一、三者在流水线上的不同位置
flowchart LR
input["Service / VirtualService / DestinationRule"] --> scope["Sidecar 裁剪<br/>(生成前:限制可见输入)"]
scope --> gen["CDS / RDS / LDS 生成"]
gen --> telemetry["Telemetry 注入<br/>(生成中:accessLog/metrics/tracing)"]
telemetry --> patch["EnvoyFilter 打补丁<br/>(生成后:直接改 proto)"]
patch --> final["最终推给该代理的 xDS 资源"]
- Sidecar
作用在生成之前:决定「这个代理的
SidecarScope里能看到哪些 Service/VirtualService/DestinationRule」,看不到的东西根本不会进入 CDS/RDS 的生成输入。 - Telemetry 作用在生成过程中:为 access log、metrics、tracing 提供者往 Listener/HCM 配置里追加字段,与 第 5–8 篇 的翻译逻辑并行,不改变路由/集群语义。
- EnvoyFilter 作用在生成之后:直接对已经生成好的 Listener/RouteConfiguration/Cluster proto 做增删改——它看到的是结果,不是语义模型。
这个顺序解释了为什么三者的排障方式完全不同:Sidecar 出问题表现为「资源缺失」(CDS 里少了某个 Cluster);Telemetry 出问题表现为「日志/指标缺失或重复」;EnvoyFilter 出问题往往表现为「config_dump 里字段对不上任何一份 YAML 的直觉」——因为它是在生成结果上二次加工。
二、Sidecar:裁剪配置,不是防火墙
Sidecar.egress.hosts 用
namespace/dnsName
格式声明这个代理需要「看见」哪些服务;未显式配置时,pilot/pkg/model/sidecar.go
的 initSidecarScopeInternalIndexes
会补一个默认的 */* 监听器,等价于「看见 mesh
里所有对本命名空间可见的服务」。hosts 支持精确
host 与通配符(*.example.com)两种形式,内部按
hostClassification 分开索引——精确 host 走直接
map 查找,通配符走范围匹配;这不只是实现细节,1.30
系列的一次性能优化(PR 记录复杂度从遍历全部可见服务 \(O(M)\) 降到按精确 host 数 \(O(H)\) 索引查找,\(H \ll M\))也印证了「精确 host
声明」在大规模 mesh
下有实打实的生成性能收益,不只是配置卫生。
最容易被误解的一点,官方 Sidecar 参考文档原话:
A common misunderstanding is that restricting the configuration amounts to blocking the traffic. If requests are sent to destinations not included in the scoping, the traffic will be treated as unmatched traffic, which is often still allowed. The sidecar is not able to enforce an outbound traffic restriction.
也就是说:egress 裁剪只影响「CDS/RDS 里有没有这个
Cluster/路由」,不影响「Envoy
能不能把包发出去」。对没有被显式导入的
host,请求会落进 passthrough
类的兜底集群,多数默认配置下仍然放得出去——真正要做流量阻断,需要
Egress Gateway 或额外的网络策略,Sidecar
资源本身不是安全边界。生产上把「配了 egress
只导入白名单服务」当成「已经做了流量隔离」,是一类反复出现的误解。
~/*
是另一个专用值:给「只接流量、不发起任何出站连接」的代理彻底清空出站配置——常见于纯
sink 型 workload。Sidecar 同时还限制哪些
VirtualService/DestinationRule
可见(依赖它们的 exportTo
是否允许该命名空间),第 7
篇 提到「DestinationRule host 拼错会静默不生效」,egress
未导入对应命名空间是另一个会导致同样「静默不生效」表现的独立原因,排障时要分开排查。
三、Telemetry API:三层继承,但「禁用」不能靠省略覆盖
Telemetry 资源分三级作用域:root
配置命名空间(通常 istio-system,mesh 级默认)→
namespace 级 → workload 级(带
selector)。官方文档:字段级覆盖是「整字段替换」,不是逐子字段深合并——这与
第 7
篇 里 DestinationRule subset
覆盖的粒度规则同构,命名不同,行为同源。
pilot/pkg/model/telemetry.go 的
computedTelemetries /
AccessLogging 方法按代理与
ListenerClass(inbound/outbound/gateway)计算最终生效配置,并做缓存(computedLoggingConfig)避免重复计算。
唯一一处不对称、且容易踩坑的规则是「禁用」的继承方向。官方文档明确写出:
If a field is explicitly disabled in a parent resource, it must be explicitly re-enabled in a child resource to override that behavior.
直觉上「子级配置覆盖父级」应该是双向对称的——子级不管开还是关都能覆盖父级。但关闭这件事有额外约束:mesh
级把某个 provider 的 access log 显式
disabled: true 之后,某个 namespace
想重新打开,必须在 namespace 级 Telemetry
里显式写 disabled: false;只是「不写
disabled 字段」不会重新打开——因为未显式设置的
disabled
默认被当作继承父级的显式禁用,而不是「未设置=启用」的独立默认值。这条规则的动机很直接:避免某个中间层「忘了写」而意外重新打开一个被上层刻意关闭的
provider——但对写 namespace/workload 级
Telemetry
的人来说,这是一个必须记住的例外,而不是能从「更具体覆盖更宽泛」这条直觉里推出来的规则。
Telemetry 编译出的配置最终体现在 access log 格式、tracing
采样、envoy.filters.http.* 侧的 stats
配置上——这些机制本身如何在数据面执行(stats 命名空间、access
log 格式化器、tracing 上下文传播)不在本文展开,见 Envoy
可观测数据面篇。
四、EnvoyFilter:直接补丁,规则由创建时间决定
EnvoyFilter
不经过任何类型化的语义模型,直接描述「往哪个 Envoy proto
结构打什么补丁」:
applyTo |
目标 |
|---|---|
LISTENER / FILTER_CHAIN |
Listener 级、filter chain 级配置 |
NETWORK_FILTER /
HTTP_FILTER |
网络层 / HTTP 层过滤器插入点 |
ROUTE_CONFIGURATION /
VIRTUAL_HOST / HTTP_ROUTE |
RDS 结果的三个层级 |
CLUSTER |
CDS 结果 |
EXTENSION_CONFIG |
ECDS 动态扩展配置 |
operation
分绝对操作(ADD、INSERT_FIRST)和相对操作(MERGE、REPLACE、REMOVE、INSERT_BEFORE/INSERT_AFTER,依赖「另一个
filter 已经在那里」)。
4.1 官方排序规则(原文)
Unlike other Istio networking objects, EnvoyFilters are additively applied. … EnvoyFilters in the config root namespace [are applied], followed by … EnvoyFilters in the workload’s namespace. … Patch sets are sorted in the following ascending key order: priority, creation time, fully qualified resource name.
翻成执行顺序:
- root 命名空间(通常
istio-system)的所有EnvoyFilter先处理,workload 命名空间的后处理。 - 同一范围内,按
priority(默认 0,可负可正)升序; priority相同时,按创建时间升序;- 创建时间也相同时,按完整资源名字典序。
4.2 「最后写入者胜出」是个误解,真正的键是创建时间
生产上常把 EnvoyFilter 冲突脑补成「最后
kubectl apply
的那个生效」——这是错的。排序键是资源创建时间戳,不是最近一次修改时间:对一个已存在的
EnvoyFilter 做 kubectl apply
更新内容,不会改变它的
creationTimestamp,因此不会改变它在合并顺序里的位置;只有
delete 再
create(或换一个新资源名)才会把它排到更靠后。「我改了配置应该覆盖别人」和「我的资源在排序键上更靠后」是两件独立的事,混淆二者是这一类脚枪里最常见的误判。
4.3 relative 操作没写 priority:分析器已经在警告
Istio 自带的配置分析器对这个问题有专门规则:
IST0151:EnvoyFilter用了相对操作(MERGE/REMOVE/INSERT_BEFORE/INSERT_AFTER/REPLACE)却没设priority,警告「可能因为排序不确定导致补丁应用失败」,建议改用绝对操作(ADD/INSERT_FIRST)或显式设置priority。IST0155:同样缺priority的相对操作,还叠加了proxyVersion匹配条件——升级 Envoy/Istio 版本后proxyVersion语义变化,补丁排序结果可能与升级前不同,即便配置文件完全没改。
官方文档对整体机制的态度也很直接:「Unlike other Istio
networking objects, EnvoyFilters are additively
applied」,且明确警告「incorrect configurations could
potentially destabilize the entire
mesh」——这是一个故意不做深度语义校验的逃生舱:CDS/RDS/EDS
的生成路径上,Cluster/VirtualService
都是类型化的 Go
struct,有结构性校验;EnvoyFilter 的
patch.value 是一段任意的 Envoy proto
JSON/YAML,Istio 不检查它是否会破坏 filter chain
的整体合法性。
五、三种机制放在一起看
| 机制 | 作用阶段 | 覆盖/合并规则 | 典型脚枪 |
|---|---|---|---|
Sidecar |
生成前裁剪输入 | 精确/通配符 host 索引;~/* 清空 |
把「配置裁剪」误当「流量阻断」 |
Telemetry |
生成中注入 | 三层继承,字段级整体覆盖 | 禁用的继承不对称,子级必须显式重新开启 |
EnvoyFilter |
生成后打补丁 | root→workload,再按 priority/创建时间/资源名排序 | 把创建时间当成「最后 apply 胜出」;relative 操作缺 priority |
三者共同的教训是:「更具体的配置覆盖更宽泛的配置」这条直觉,在 Istio 里从来不是无条件成立的通用规则——第 7 篇 的 subset vs port-level 不对称、本篇 Telemetry 的禁用不对称、EnvoyFilter 的创建时间排序,都是同一类「文档写清楚了,但不查文档就会凑出错误直觉」的设计。排障时应该先查具体资源的官方合并规则条款,而不是套用上一个资源类型学到的合并直觉。
六、谱系、争论与开放问题
谱系:EnvoyFilter
延续的是「给控制面留一个绕过类型化模型的逃生舱」这一常见设计——几乎所有把用户
API 抽象成中间模型再生成底层配置的系统(Kubernetes 的
kubectl patch、Terraform 的
ignore_changes/provider
override)都有类似的补丁机制,代价永远是「失去中间层的语义校验」。Sidecar
的裁剪思路则更接近传统防火墙规则集的「默认拒绝,显式允许」范式,只不过它裁剪的是控制面可见性,不是数据面转发。
争论:EnvoyFilter
该不该有更强的静态冲突检测——现有的
istioctl analyze
规则(IST0151/IST0155)只检查「缺
priority 的相对操作」这一种模式,不检测「两个
EnvoyFilter 实际语义冲突」(例如都往同一个
filter chain 位置 INSERT_BEFORE 同一个
filter)。保持现状的理由是:校验补丁语义等价于重新实现一部分
Envoy
的配置合法性检查,成本和收益不对称;但这也意味着这类冲突只能靠运维经验或事后
config_dump 排查发现。
开放问题:Sidecar egress
裁剪被误当成流量隔离手段,是文档反复强调却仍在生产事故复盘里反复出现的误解——这提示「配置可见性」与「流量控制」在
Istio 的资源模型里刻意分离(前者是
Sidecar,后者要靠 Egress
Gateway/NetworkPolicy),但没有工具层面的强制提醒(例如「检测到窄
egress
但没有对应出站限制」的分析规则),这类跨资源一致性检查目前仍是空白,与
第 8
篇 提到的「auto mTLS
推断与精确策略信息差」是同一类尚未被工具覆盖的边界。
七、参考资料
规范 / 官方文档(A)
- Istio 1.30 文档,Sidecar 参考(egress
hosts语法、「不是流量阻断」原文警告、~/*语义)。 - Istio 1.30 文档,Telemetry 参考与 Telemetry API 任务文档(三层继承、禁用继承不对称原文)。
- Istio 1.30 文档,Envoy Filter 参考(additively applied、root→namespace 顺序、priority/创建时间/资源名排序原文)。
- Istio 1.30
文档,EnvoyFilterUsesRelativeOperation(
IST0151)、EnvoyFilterUsesRelativeOperationWithProxyVersion(IST0155)。
源码(A)
istio/istio:pilot/pkg/model/sidecar.go(SidecarScope、initSidecarScopeInternalIndexes、hostClassification)。istio/istio:pilot/pkg/model/telemetry.go(computedTelemetries、AccessLogging、mergeLogs)。istio/istioPR #60473:Sidecar 精确 host 索引查找的复杂度优化记录(\(O(M)\) 扫描 → \(O(H)\) 索引查找)。
站内
- Envoy / 扩展边界(Wasm/Lua/ext_authz 的机制位置,本文不重复)
- Wasm 架构(沙箱与 ABI 全景,本文不重复)
- Envoy / 可观测数据面
- 第 7 篇:DestinationRule · 第 8 篇:Authn/Authz/SDS
- 系列目录
实验台账
- 本篇不粘贴
config_dump中 EnvoyFilter 补丁前后对比;排序与继承规则直接引用官方文档条款与源码注释,未在本机集群逐字段核对。
八、小结
Sidecar、Telemetry、EnvoyFilter作用在生成流水线的三个不同阶段:生成前裁剪、生成中注入、生成后打补丁。Sidecar的 egress 裁剪只影响控制面可见性,官方文档明确它不是流量阻断机制。Telemetry的字段覆盖是整体替换,但禁用的继承方向不对称:子级必须显式disabled: false才能覆盖父级的显式禁用。EnvoyFilter按「root 命名空间优先 → priority → 创建时间 → 资源名」排序合并;创建时间不等于最近一次 apply 时间,这是最容易被误解的一条规则。- 三者的共同教训:合并/覆盖规则因资源类型而异,不能用「更具体覆盖更宽泛」这一句话统一理解——具体规则以对应官方参考文档为准。
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Istio 控制面】控制面全景:从 CRD 到 xDS 的翻译与推送内核
定位 Istio 控制面内核相对 Envoy 消费侧、Service Mesh 税文与 Gateway API 资源模型的缺口;给出五条坐标系、16 篇地图与本系列明确不写的范围,钉住 istiod 1.30.3 为主线。
【Istio 控制面】istiod 进程模型:discovery、config、CA 与注入的边界
拆解 pilot-discovery 二进制内部的组件化启动顺序:CA 为何先于 Discovery 初始化、注入 Webhook 与配置校验为何是独立 HTTPS 服务、以及哪些组件真正位于 xDS 推送热路径上。
【Istio 控制面】Service / Endpoint / Workload → CDS/EDS:istiod 如何拼出一个 Cluster
拆解 istiod 如何把 K8s Service、EndpointSlice、WorkloadEntry 聚合进内部服务模型,再经 CdsGenerator/EdsGenerator 分别生成 Cluster 与 ClusterLoadAssignment;钉住命名契约与部分推送边界,subset 留给 DestinationRule 篇。
【Istio 控制面】VirtualService / mesh HTTPRoute → RDS:两条编译路径,一份路由表
拆解 istiod 的 RdsGenerator 如何把 VirtualService 与 Gateway API HTTPRoute(含 GAMMA mesh 模式)都编译成同一份内部路由模型,说明为何双轨输入会在同一 host 上产生冲突,以及两套 API 的合并顺序为何一个确定、一个未定义。