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。
同一个 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 里的具体字段——lbPolicy、circuitBreakers、outlierDetection(正是你在 Envoy 课 Day 14-15 学的那些!)。subset(版本子集)也在这里展开成多个 Envoy cluster。看,Istio 课和 Envoy 课在这里精确对接:Istio 生成的,正是 Envoy 消费的。你两门课都学了,就能看到完整的"生成→消费"闭环。📝 举个例子:一句 DR 翻成哪个 Envoy 字段
| 你在 DestinationRule 写的 | applyDestinationRule 译成 Envoy Cluster 字段 |
|---|---|
trafficPolicy.loadBalancer: LEAST_REQUEST | cluster.lbPolicy = LEAST_REQUEST |
connectionPool.tcp.maxConnections: 100 | cluster.circuitBreakers.thresholds.maxConnections = 100 |
outlierDetection.consecutive5xxErrors: 5 | cluster.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 09:Push 与事件模型——配置一变,怎么触发推送?
ConfigUpdate → debounce(100ms 合并)→ Push → PushQueue → 各 proxy。这条链是控制面的"神经系统"。