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 分好类),要查某项直接翻卡片,不用把全场再数一遍。照片不可变,所以多数查找无锁

PushContextpilot/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)。层层减少不必要的计算。
三级"少干活"优化:漏斗层层收窄 ① Debounce:短时间多次变更合并成一次(Day 09) ② PushContext 增量:只重算变了的索引,其余复用旧快照 ③ proxy 级裁剪:只推给依赖它的 Envoy
从"一堆变更"到"真正要推的那几个 Envoy",三道闸门层层过滤,避免全网无脑重算重推。
L05

Sidecar 作用域

SidecarScopepilot/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 个服务。frontend 其实只调 backendcart
不写 Sidecar → frontend 的 Envoy 收到全部 2000 个服务的 CDS/EDS/RDS(内存 ~GB 级)。
写一条 Sidecar { egress: hosts: ["./backend", "./cart"] } → SidecarScope 算出它只需 2 个上游 → Envoy 配置骤降到 MB 级,推送也更快。
💬 常见误解 👶 小白:不写 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

依赖跟踪

SidecarScopeconfigDependenciessidecar.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 状态机。这是"发送端"的核心。
← Day 04 服务注册 Day 06 · xDS DiscoveryServer →