Day 05 / 共 20 天 · 第 1 周收官
PushContext 配置快照
配置和服务读进来了,怎么高效地给每个 proxy 生成 xDS?答案是 PushContext——世界当前状态的不可变快照 + 预建索引。再讲 Sidecar 作用域(决定 proxy 看到哪些配置)。收官第 1 周。
📍 你在控制面 istiod 之旅的位置(第 1 周收官:把工单+通讯录定格成一张高效可查的"快照")
D1 全景·
D2 启动·
D3 配置CRD·
D4 服务注册·
D5 快照·
D6 xDS服务·
D7 生成·
D8 Builder·
D9 推送·
D10 连接
L01
PushContext 是什么
🤔 痛点:上千个 Envoy 同时来要配置,边算边被人改怎么办
istiod 要同时给上千个 Envoy 生成配置。如果每个都现查一遍 ConfigStore/ServiceDiscovery,慢;更糟的是——查到一半配置又被改了,读到"半新半旧"的不一致状态,发出去的配置就是错的。
💡 本质:先拍一张"定格照片",大家都照着它
PushContext 就像新闻发布会前先拍一张全场定格合影:某一刻把所有配置和服务"咔嚓"定住,之后谁来要资料都发这张照片的副本——既快又保证人人拿到的是同一版本。而且照片还附带一套图书馆式索引卡片(按命名空间、host、gateway 分好类),要查某项直接翻卡片,不用把全场再数一遍。照片不可变,所以多数查找无锁。
PushContext(pilot/pkg/model/push_context.go:215)= 世界当前状态的不可变快照,每次推送(通常部分)重新生成(pilot.md:71)。
PushContext = "配置的一张定格照片 + 索引"
istiod 要给成百上千个 Envoy 生成配置。如果每个 Envoy 都现查 ConfigStore/ServiceDiscovery,又慢又容易读到"变化中"的不一致状态。PushContext 的思路:在某个时刻把所有配置和服务"定格"成一张不可变快照,并预先建好各种索引(按命名空间、按 host、按 gateway…)。之后为每个 proxy 生成配置时,都从这张快照的索引里查——快且一致。因为不可变,大多数查找无锁(
pilot.md:71-75)。配置一变,就重新生成一张新快照(下一课)。这是"读多写少 → 快照 + 索引"的经典优化。L02
预建索引
// push_context.go:215 PushContext 的索引字段:
ServiceIndex // :225 服务索引(按 host/ns/port)
virtualServiceIndex // :231 VS 索引(按 gateway/host)
destinationRuleIndex // :234
gatewayIndex // :237
sidecarIndex // :243
envoyFiltersByNamespace // :246
AuthnPolicies/AuthzPolicies/Telemetry // :252+
Mesh / PushVersion / InitDone // :268/:271/:285
读法:每种配置都预建了"按最常用的维度"索引好的结构。比如
virtualServiceIndex 按 gateway 分组(生成某 gateway 的路由时直接取)。这些索引就是"带 Proxy 翻译"(Day 01)能快起来的原因——生成配置时不用遍历全部,查索引即可。L03
InitContext
// push_context.go:1401 createNewContext —— 全量按序初始化所有索引:
initServiceRegistry → initKubernetesGateways → initVirtualServices
→ initDestinationRules → initAuthnPolicies → initAuthorizationPolicies
→ initTelemetry → initProxyConfigs → initWasmPlugins → initEnvoyFilters
→ initGateways → initAmbient → initSidecarScopes // ★ Sidecar 作用域必须最后
读法:
InitContext(:1371)是快照构建入口。createNewContext 严格按依赖顺序初始化各索引。initSidecarScopes 必须最后——因为它依赖前面所有索引(要算"每个 proxy 能看到哪些 service/VS/DR")。L04
全量 vs 增量
// push_context.go:1388 InitContext 分支:
// 首建/强制 → createNewContext(全量重建所有索引)
// 否则 → updateContext(增量:只重算变更的索引,其余从旧快照复制)
// updateContext(:1423):按 ConfigsUpdated 的 Kind 设 xxxChanged 标志
// 服务未变则 ps.ServiceIndex = old.ServiceIndex(直接复用旧的!)
即便"全量推送"也尽量复用旧快照
重建整张 PushContext 很贵(要重新索引所有配置)。优化:如果这次变更只涉及 VirtualService,那 ServiceIndex、DestinationRuleIndex 等没变的索引直接从旧快照复制指针,只重算 virtualServiceIndex。这就是
updateContext 做的——按 ConfigsUpdated 判断哪些变了,只重算变的。这是三级优化(pilot.md:166-181)的第二级:debounce 合并(Day 09)→ PushContext 增量复用(这里)→ proxy 级裁剪(Day 09)。层层减少不必要的计算。从"一堆变更"到"真正要推的那几个 Envoy",三道闸门层层过滤,避免全网无脑重算重推。
L05
Sidecar 作用域
SidecarScope(pilot/pkg/model/sidecar.go:140)= 每个 proxy 预算出的"视图"——它到底能看到哪些 service/VS/DR。
Sidecar 作用域 = "给每个 Envoy 划定的视野"
默认情况下,一个 Envoy 会收到全网格所有服务的配置——集群大了,这是巨量配置(内存爆炸、推送慢)。Sidecar 资源(Day 03 的 CRD)让你限定"这个服务只需要知道哪几个上游"。SidecarScope 就是据此算出的"该 proxy 实际能看到的配置子集"。比如 frontend 只调 backend,就只给它 backend 的配置,不给全网格的。这是大规模网格的关键优化——用 Sidecar 资源收敛配置,能把单个 Envoy 的内存从 GB 级降到 MB 级。默认作用域(无 Sidecar 资源)看到所有 public 服务(
*/*)。📝 举个例子:一条 Sidecar 资源省下的内存
集群有 2000 个服务。
不写 Sidecar → frontend 的 Envoy 收到全部 2000 个服务的 CDS/EDS/RDS(内存 ~GB 级)。
写一条
frontend 其实只调 backend 和 cart。不写 Sidecar → frontend 的 Envoy 收到全部 2000 个服务的 CDS/EDS/RDS(内存 ~GB 级)。
写一条
Sidecar { egress: hosts: ["./backend", "./cart"] } → SidecarScope 算出它只需 2 个上游 → Envoy 配置骤降到 MB 级,推送也更快。💬 常见误解
👶 小白:不写 Sidecar 资源,配置量大点无所谓吧?
👨🏫 老师:小集群无所谓,大集群是灾难。默认每个 Envoy 都拿"全网格视图",服务数一多,内存和推送量呈平方级膨胀(N 个服务 × N 个 Envoy)。生产大网格几乎必配 Sidecar 资源收敛视野——这不是可选优化,是生存必需。
👨🏫 老师:小集群无所谓,大集群是灾难。默认每个 Envoy 都拿"全网格视图",服务数一多,内存和推送量呈平方级膨胀(N 个服务 × N 个 Envoy)。生产大网格几乎必配 Sidecar 资源收敛视野——这不是可选优化,是生存必需。
L06
可见性 exportTo
配置和服务有可见性(pkg/config/visibility/):Private="."(仅本命名空间)、Public="*"(全网格)、None="~"(不可见)。三个 init* 按 exportTo 把配置分类进 public/private/exported。
读法:
exportTo 控制"这条配置/服务给谁看"。默认 public(全网格可见);设成 . 就只有本命名空间能用(隔离);设成具体命名空间就导出给那几个。这和 Sidecar 作用域配合,共同决定"某 proxy 能看到什么"——可见性从"提供方"角度限制,Sidecar 从"消费方"角度限制。L07
依赖跟踪
SidecarScope 有 configDependencies(sidecar.go:182)——记录"这个作用域依赖哪些配置"。DependsOnConfig(:559)判断某配置变更是否需推给使用此作用域的 proxy。
依赖跟踪 = "只通知关心的人"
配置变了,要推给哪些 Envoy?如果推给所有 Envoy,浪费(大部分不关心这个变更)。依赖跟踪:每个 SidecarScope 记下"我依赖哪些配置"。某配置变更时,只推给依赖它的那些 proxy。比如改了 payment 服务的路由,只推给"会调用 payment"的那些 Envoy,其他的不动。这是三级优化的第三级(proxy 级裁剪,Day 09 的
ProxyNeedsPush 用它)——把推送范围精确到"真正受影响的代理"。大规模网格里这个优化省下海量无效推送。L08
第 1 周收官 🎓
🧠 第 1 周(控制面全景)你已掌握
- Day 01 控制面 vs 数据面、xDS 五兄弟、三段式职责、带 Proxy 翻译
- Day 02 istiod 启动:pilot-discovery → NewServer 装配 → Start 监听
- Day 03 配置模型:CRD、config.Config 统一信封、ConfigStore、schema
- Day 04 服务注册:Service/IstioEndpoint 模型、聚合/K8s/SE 注册表
- Day 05 PushContext 快照 + 索引、全量/增量、Sidecar 作用域、可见性、依赖跟踪
你现在理解了 istiod 的"数据基础":配置和服务怎么读进来(摄取)、怎么组织成高效可查的快照。第 2 周进入核心——这张快照怎么翻译成 Envoy xDS 并推送下去。
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/istio
grep -n 'type PushContext struct\|func.*InitContext\|createNewContext\|updateContext' pilot/pkg/model/push_context.go | head
grep -n 'type SidecarScope struct\|configDependencies\|func.*DependsOnConfig' pilot/pkg/model/sidecar.go | head
明天预告 · Day 06(第2周开始):xDS DiscoveryServer——istiod 怎么向 Envoy 提供 ADS gRPC 流?
DiscoveryServer 结构、StreamAggregatedResources、收发分离、ACK/NACK 状态机。这是"发送端"的核心。