第 05 期 / 共 10 期

IngressConfig 翻译核心

深入 pkg/ingress/config/ingress_config.go(2085 行)——Higress 控制面最复杂的文件。读完这期你能解释一条 Ingress 是怎么变成 VirtualService + DR + EnvoyFilter 的。

L01

文件定位与全景

pkg/ingress/config/ingress_config.go,2085 行。它的角色是实现 Istio model.ConfigStoreController 接口,把 Pilot 想要的 VirtualService / DR / EnvoyFilter 等资源动态合成出来。

grep '^func ' pkg/ingress/config/ingress_config.go | head -40
# 30+ 个方法,主要分四组:构造、ConfigStore 接口、convert*、apply*
思考:为什么这种"合成型 ConfigStore"模式比"提前写 VirtualService 到 etcd"更好?提示:用户体验 + 实时性。
L02

IngressConfig 字段速读

type IngressConfig struct {
    remoteIngressControllers map[string]common.IngressController
    mcpbridgeController      mcpbridge.McpBridgeController
    wasmPluginController     wasmplugin.WasmPluginController
    http2rpcController       http2rpc.Http2RpcController
    annotationHandler        annotations.AnnotationHandler
    eventHandlers            map[config.GroupVersionKind]istiomodel.EventHandler
    cachedConfig             map[config.GroupVersionKind][]config.Config
    ...
}

字段两类:① 各 controller 引用(拿原始数据),② 缓存与回调(喂给 Pilot)。

思考:cachedConfig 与 Pilot 自带 PushContext 的关系?答:互补,IngressConfig 缓存合成结果,PushContext 是 Pilot 全局快照。
L03

NewIngressConfig 构造

第 187 行的 NewIngressConfig 是入口:

  1. 创建主集群的 IngressController(v1 / v1beta1 / gateway / kingress);
  2. 初始化 McpBridge / WasmPlugin / Http2Rpc Controller;
  3. 构造 AnnotationHandler;
  4. 把所有 controller 注册回调,事件经 normalize 后调用 refreshAndPushXds
思考:构造函数是否要等所有 informer cache synced?答:不需要,由调用方 waitForCacheSync 控制。
L04

实现 ConfigStore 伪装

Pilot 看不到 IngressConfig 是合成的,因为它实现了 model.ConfigStoreController

func (m *IngressConfig) Schemas() collection.Schemas { return m.schemas }
func (m *IngressConfig) Get(gvk, name, ns) *config.Config { ... }
func (m *IngressConfig) List(gvk, ns) []config.Config { ... }
func (m *IngressConfig) Run(stop <-chan struct{}) { ... }
func (m *IngressConfig) HasSynced() bool { ... }

对 Pilot 而言它就是一个普通 store,只是不允许 Create/Update/Delete。

思考:Schemas() 返回哪些 GVK?提示:VirtualService / DestinationRule / Gateway / ServiceEntry / EnvoyFilter / extensions.WasmPlugin。
L05

RegisterEventHandler

238 行的 RegisterEventHandler 把 Pilot 注册的回调存到 eventHandlers。当内部 informer 触发变更,IngressConfig 调对应回调通知 Pilot:

if h, ok := m.eventHandlers[gvk.VirtualService]; ok {
    h(prev, curr, model.EventUpdate)
}
思考:通知前要不要先比较 prev/curr 来去重?答:尽量比对,避免无意义推送。
L06

List 主分发(286 行)

func (m *IngressConfig) List(typ config.GroupVersionKind, ns string) []config.Config {
    switch typ {
    case gvk.VirtualService:     return m.virtualServices(ns)
    case gvk.DestinationRule:    return m.destinationRules(ns)
    case gvk.Gateway:            return m.gateways(ns)
    case gvk.ServiceEntry:       return m.serviceEntries(ns)
    case gvk.EnvoyFilter:        return m.envoyFilters(ns)
    case gvk.WasmPlugin:         return m.wasmPlugins(ns)
    }
    return nil
}

这是整个文件的中央枢纽——按资源类型分发到对应 convert* 函数。

思考:每次 List 都重算开销大吗?提示:有 cachedConfig,仅在事件触发时重算。
L07

listFromIngressControllers

322 行。聚合多集群 IngressController 的原始 Ingress,过滤掉错误的、不属于 Higress 的,按 namespace 切分返回。这是 convert 系列的"数据来源"统一入口。

思考:listFromGatewayControllers 与它的区别?答:source CRD 不同(Gateway API),合成逻辑可复用大部分。
L08

createWrapperConfigs

385 行。把 Ingress 列表逐条调用 annotationHandler.Parse,得到带注解解析结果的 WrapperConfig。这是"原始 K8s 对象 → 内部 IR"的转换点,下游所有 convert* 操作的是 WrapperConfig 不再是原生 Ingress。

思考:把"解析"与"应用"两阶段分开有什么好处?答:清晰 + 错误隔离。
L09

convertGateways

431 行。每条 Ingress 的 spec.tls + 默认 80/443 监听被合并为 Istio Gateway 资源,按 host 分组。这是 LDS 生成的源头:Gateway 字段直接影响 listener。

思考:多条 Ingress 共享同一 host + TLS 时怎么合并?
L10

convertVirtualService(483 行)

整个文件的明星方法。约 150 行:

  1. 把每条 rule + path 转 HTTPRoute;
  2. 调用 annotationHandler.ApplyRoute 把注解填入 HTTPRoute;
  3. 处理 canary(applyCanaryIngresses);
  4. 处理 AppRoot / InternalActiveRedirect;
  5. 同 host 多 VS 合并去重。

RDS 的 99% 路由细节都来自这里。

思考:HTTPRoute 数组顺序有意义吗?答:有,Envoy 按顺序匹配,第一条命中即止。
L11

convertDestinationRule(773 行)

DR 生成关注点:

  • 从注解读 lb / timeout / retry / mirror。
  • 处理 upstreamtls(mTLS、SNI)。
  • 处理 subset(灰度版本切流)。

DR 决定 CDS 与 cluster.transport_socket 的细节。

思考:同名 cluster 的 DR 合并冲突时怎么处理?
L12

convertServiceEntry(738 行)

当 backend 指向集群外(如 nacos://my-svc)时,要写一份 ServiceEntry 让 Pilot 知道这个外部服务。registry 拉来的实例最终也会以 ServiceEntry 形式注入。

思考:ServiceEntry 与 K8s Service 优先级谁高?
L13

convertEnvoyFilter(628 行)

这是 Higress 的"逃生通道":注解里 Higress 还没原生支持的能力,全靠合成 EnvoyFilter patch listener / route / cluster 的 typed_config。比如 BasicAuth、Http2Rpc、Header 透传等。

思考:EnvoyFilter 写多了会拖慢 Pilot 吗?答:会,因为 Pilot 每次 push 都要 merge patch。
L14

convertWasmPlugin(714 行)

把 Higress WasmPlugin CRD 转成 Istio extensions.WasmPlugin。两者 group 不同但内容兼容:Higress 增加了 matchRules(按 host/path 覆盖配置)、imagePullPolicy 等字段,convertIstioWasmPlugin 负责降级处理。

思考:matchRules 如何落到 Envoy?答:转成多份 EnvoyFilter patch。
L15

applyCanaryIngresses(1432 行)

K8s 社区的 Ingress canary 语义(同 host 多 Ingress + canary 注解)在这里实现。流程:

  1. 找到 canary 候选 Ingress;
  2. 按 weight / header / cookie 匹配规则合并到主 Ingress 的 HTTPRoute;
  3. normalizeWeightedCluster 归一化权重。
思考:weight + header 同时指定时优先级?答:header 优先匹配,未命中再走 weight。
L16

applyAppRoot / Redirect

位于 912 / 940 行:

  • applyAppRoot:注解 app-root 指定子路径时,把 / 重定向到 /sub。
  • applyInternalActiveRedirect:Higress 扩展的"网关内部重定向",比 302 更高效。
思考:内部重定向相比 302 的区别?答:客户端无感知,性能更好。
L17

Http2Rpc 合成(1452 行)

constructHttp2RpcEnvoyFilter:把 Http2Rpc CRD 翻译成 EnvoyFilter,注入 Dubbo 协议转换器。constructHttp2RpcMethods 生成方法白名单的 google.protobuf.Struct。

思考:Dubbo 接口反射调用代价高吗?答:是,通常前端用 Higress 做协议转换 + Triple/Dubbo3。
L18

BasicAuth EnvoyFilter(1648 行)

constructBasicAuthEnvoyFilter 把 basic-auth 注解里的用户名密码渲染成 envoy.filters.http.basic_auth typed_config。注意密码不能写明文,需要 Secret 引用 + base64。

思考:为什么不用 ext-authz?答:basic auth 简单场景,本地匹配性能更好。
L19

ProxyEnvoyFilters(1731 行)

constructProxyEnvoyFilters 处理"全局 proxy 设置":每个上游服务可能有自己的 connection pool / outlier detection / tracing 覆写。Higress 把这些合成成一组 EnvoyFilter 一次性推下去。

思考:服务级 vs 路由级 EnvoyFilter 该选哪种?
L20

缓存与 HasSynced(1877+ 行)

Run/HasSynced/GetIngressRoutes/GetIngressDomains 等方法是给 Pilot 与控制面其它组件用的辅助 API。HasSynced 返回所有底层 controller 都 HasSynced 的 AND 结果——只有都准备好了 Pilot 才开始 push。

本期收尾:你已经能在 2085 行里"按需跳读"。下期我们把 annotations/ 30+ 文件展开来看。