Day 08 / 共 20 天 · 第 2 周 xDS 生成

Listener/Route/Cluster Builder

深入 core 层的三大 Builder:怎么把 Istio 配置一步步构造成 Envoy 的 Listener、Cluster、Route 对象。这里是"高层意图变成 Envoy 具体配置"的真正现场。

📍 你在控制面 istiod 之旅的位置(昨天看翻译科分工,今天钻进三个翻译窗口看逐字翻译)
D1 全景· D2 启动· D3 配置CRD· D4 服务注册· D5 快照· D6 xDS服务· D7 生成· D8 Builder· D9 推送· D10 连接
L01

BuildListeners

🤔 痛点:把"90% 到 v1"翻成 Envoy 配置,到底改哪些字段? 昨天知道了有翻译科,但一条 VirtualService/DestinationRule 到底怎么变成 Envoy 的 Listener/Cluster/Route?涉及几十个字段、入站出站、TLS、熔断……不理清"哪句话对应哪个字段",就永远停留在"知道有翻译"却不懂"翻了什么"。
💡 本质:三条流水线,各装一种 Envoy 零件 三大 Builder 像三条装配流水线,各产一种零件:ListenerBuilder 装"门"(监听哪些端口、进出怎么过滤)、ClusterBuilder 装"后端机器池"(负载均衡/连接池/熔断)、Route 翻译装"路牌"(什么请求去哪)。每条线是链式的——一个工位加一部分,最后汇总。DestinationRule 的熔断参数、VirtualService 的路由规则,就是在这些工位上被逐字焊进 Envoy 配置的。
// pilot/pkg/networking/core/listener.go:100 BuildListeners
// 按 node.Type 分派:
//   SidecarProxy → buildSidecarListeners
//   Waypoint     → buildWaypointListeners
//   Router       → buildGatewayListeners
// 最后 builder.patchListeners()(EnvoyFilter 补丁,Day 17)+ getListeners() 汇总
读法:生成 Listener(Envoy 课的监听器)先看 proxy 类型:sidecar(业务 Pod 旁的)、waypoint(ambient 模式)、router(网关)各有不同的 listener 构造逻辑。生成后还要过一遍 EnvoyFilter 补丁(Day 17,Higress 重度使用)。
L02

ListenerBuilder

// pilot/pkg/networking/core/listener_builder.go:58 ListenerBuilder
// 聚合:inboundListeners / outboundListeners / gatewayListeners
//       virtualInbound/OutboundListener / httpProxyListener
// 持有 authnBuilder(mTLS) / authzBuilder(授权) / mseIngressBuilder(Higress)
// 链式流水线:appendSidecarInboundListeners().appendSidecarOutboundListeners()
//             .buildHTTPProxyListener().buildVirtualOutboundListener()
Builder 模式 = "流水线式组装复杂对象" Listener 配置很复杂(入站、出站、TLS、过滤器链…)。ListenerBuilder 用"建造者模式":一个持有中间状态的对象,链式调用一串 appendXxx 方法,每步往里加一部分,最后 getListeners() 汇总输出。好比组装流水线,每个工位加一个部件。注意它持有 authnBuilder(生成 mTLS 配置,Day 15)和 authzBuilder(生成 RBAC,Day 15)——安全策略就是在构造 Listener 时织进去的。Higress 加了 mseIngressBuilder
L03

入站 vs 出站

sidecar 有两个方向的 listener:入站(inbound)——处理"别人访问我"的流量(listener_inbound.go);出站(outbound)——处理"我访问别人"的流量(buildSidecarOutboundListeners)。

为什么 sidecar 要管两个方向? 回想 Day 11 的 iptables 劫持——Pod 的所有进出流量都被劫持到 Envoy。所以 sidecar Envoy 要处理两类:入站(别的服务调用本 Pod,走 15006 端口,Day 11)——这里应用 mTLS 验证、授权(Day 15);出站(本 Pod 调用别的服务,走 15001)——这里应用路由、负载均衡。两个方向的 listener 配置逻辑不同。入站关心"谁能访问我、怎么验证",出站关心"我要去哪、怎么去"。还有 virtualInbound/virtualOutbound 作为兜底 listener。
本 Pod 的 Envoy iptables 劫持进出流量 别的服务调用我 入站:15006 mTLS验证/授权 上游服务我要调用 出站:15001 路由/负载均衡
同一个 sidecar 管两个方向:左边入站(15006)负责"谁能进、验工牌",右边出站(15001)负责"我去哪、怎么去"。
L04

BuildClusters

// pilot/pkg/networking/core/cluster.go:56 BuildClusters
//   选定 service 集合(Gateway 用 GatewayServices,sidecar 用 SidecarScope.Services())
//   → buildClusters(:219)按 proxy.Type 分派
//   outbound clusters + blackhole/passthrough + inbound + SDS cluster
//   → normalizeClusters 去重 → 包成 discovery.Resource
读法:生成 Cluster(Envoy 课的集群)时,用 SidecarScope 限定的服务集合(Day 05——只给这个 proxy 需要的服务建 cluster,不建全网格的)。除了正常 outbound/inbound cluster,还有 blackhole(丢弃未知流量)、passthrough(透传)等特殊 cluster。这直接体现了"配置收敛"的价值。
L05

ClusterBuilder

// pilot/pkg/networking/core/cluster_builder.go:103 ClusterBuilder
// 持有 proxy 全上下文:serviceTargets/clusterID/sidecarScope/
//   supportsIPv4/6/locality/proxyView/cache
// 强调:影响 cluster 的字段都要进缓存 key(Day 07 缓存)
读法:ClusterBuilder 持有生成一个 cluster 所需的全部 proxy 上下文。注释强调"影响结果的字段都要进缓存 key"——因为"带 Proxy 翻译",同一个服务对不同 proxy 可能生成不同 cluster(locality/IP 版本不同),缓存 key 必须区分。
L06

applyDestinationRule

cluster_builder.go:307 applyDestinationRule:把 DestinationRule(Day 03)的策略应用到 cluster——负载均衡(Envoy 课 Day 14)、连接池、离群检测(熔断,Envoy 课 Day 15 的 outlier)、上游 TLS。

这里就是"高层 CRD → Envoy 配置"的翻译现场 你在 DestinationRule 里写"用 LEAST_REQUEST 负载均衡、连接池上限 100、连续 5 次 5xx 熔断"。applyDestinationRule 就把这些高层描述翻译成 Envoy Cluster 里的具体字段——lbPolicycircuitBreakersoutlierDetection(正是你在 Envoy 课 Day 14-15 学的那些!)。subset(版本子集)也在这里展开成多个 Envoy cluster。看,Istio 课和 Envoy 课在这里精确对接:Istio 生成的,正是 Envoy 消费的。你两门课都学了,就能看到完整的"生成→消费"闭环。
📝 举个例子:一句 DR 翻成哪个 Envoy 字段
你在 DestinationRule 写的applyDestinationRule 译成 Envoy Cluster 字段
trafficPolicy.loadBalancer: LEAST_REQUESTcluster.lbPolicy = LEAST_REQUEST
connectionPool.tcp.maxConnections: 100cluster.circuitBreakers.thresholds.maxConnections = 100
outlierDetection.consecutive5xxErrors: 5cluster.outlierDetection.consecutive5xx = 5
subsets: [{name: v2, labels: {version: v2}}]额外生成一个集群 outbound|9080|v2|…
L07

Route 翻译

// pilot/pkg/networking/core/route/route.go:400
// BuildHTTPRoutesForVirtualService(node, vs, port, gatewayNames, opts) []*route.Route
//   逐条 vs.Http 规则翻译成 Envoy route.Route
//   遇 IsCatchAllRoute 提前 break(路由按序匹配,后续不可达)
读法:VirtualService 的每条 http 规则(匹配 + 路由目标 + 重试/超时/故障注入)被翻译成一个 Envoy route.Route遇到"catch-all"(匹配一切)规则就停——因为路由按序匹配,后面的永远匹配不到(呼应 Envoy 课 Day 09 Router)。Higress 加了 per-route filter 支持(BuildHTTPRoutesForVirtualServiceWithHTTPFilters)。
💬 错误驱动:路由顺序踩坑 👶 小白:我在 VirtualService 里先写了一条 prefix: / 的兜底规则,后面写 /api 的精确规则,为什么 /api 永远不生效?
👨‍🏫 老师:因为路由按书写顺序从上往下匹配prefix: / 匹配一切、先命中就返回了,后面的 /api 永远轮不到(IsCatchAllRoute 一命中就 break)。规则:越具体的越往前放,catch-all 永远垫最后。这和 Envoy Router 的匹配语义完全一致。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • BuildListeners 按什么分派?生成后为什么要 patch?
  • ListenerBuilder 用什么模式?为什么持有 authn/authz builder?
  • sidecar 的入站/出站 listener 各处理什么方向?
  • BuildClusters 怎么用 SidecarScope 收敛?
  • applyDestinationRule 翻译出哪些 Envoy 配置?(和 Envoy 课对接)

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/istio
grep -n 'func.*BuildListeners\|func.*buildSidecarListeners' pilot/pkg/networking/core/listener.go
grep -n 'func.*BuildClusters\|applyDestinationRule' pilot/pkg/networking/core/cluster.go pilot/pkg/networking/core/cluster_builder.go
grep -n 'BuildHTTPRoutesForVirtualService' pilot/pkg/networking/core/route/route.go
明天预告 · Day 09Push 与事件模型——配置一变,怎么触发推送?ConfigUpdate → debounce(100ms 合并)→ Push → PushQueue → 各 proxy。这条链是控制面的"神经系统"。
← Day 07 配置生成 Day 09 · Push 与事件模型 →