第 04 期 / 共 10 期

K8s 资源监听与 Controller

Higress 控制面本质是一组 K8s Controller。本期把所有 controller 的统一范式与各自差异讲透。

L01

Informer 三件套

Informer = SharedIndexInformer + Lister + EventHandler,K8s 的"List-Watch + 本地缓存"标准范式。Higress 所有 controller 都是这套范式的实例化。

informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
    AddFunc:    func(obj interface{}) { queue.Add(key(obj)) },
    UpdateFunc: func(old, new interface{}) { queue.Add(key(new)) },
    DeleteFunc: func(obj interface{}) { queue.Add(key(obj)) },
})
思考:为什么不直接处理事件而要走 queue?答:去重、串行化、限速、重试。
L02

pkg/kube/client.go

Higress 在 Istio 的 istiokube.Client 之上再包了一层 higresskube.Client,把自定义 CRD 客户端塞进来,对外暴露统一的 multi-typed client。所有 controller 都接收同一个 client 实例。

思考:单 client 多 informer 怎么避免相互争抢 watch?提示:每个 GVR 一个 informer,由 SharedInformerFactory 管理。
L03

client/ codegen 产物

client/ 目录由 k8s.io/code-generator 自动生成 typed clientset / lister / informer。Higress 写 CRD types.go + 跑 make gen,剩下都是生成代码。不需要读这些文件,只要会调用即可:

cs, _ := versioned.NewForConfig(cfg)
list, _ := cs.NetworkingV1().McpBridges("ns").List(ctx, ...)
思考:CRD 改字段后忘 make gen 会怎样?
L04

common.IngressController 接口

pkg/ingress/kube/common/controller.go 定义了 Higress 自己的 controller 抽象:

type IngressController interface {
    Run(stop <-chan struct{})
    HasSynced() bool
    ListAll() []common.WrapperConfig
    GetIngress(ns, name string) *networkingv1.Ingress
    AddEventHandler(handler IngressHandler)
    ...
}

这是 IngressConfig 与各版本 controller 解耦的关键。

思考:WrapperConfig 是什么?答:把 Ingress + 集群标识 + 注解解析结果包一层的中间表示。
L05

ingressv1 控制器

位于 pkg/ingress/kube/ingressv1/。监听 networking.k8s.io/v1 Ingress 资源(K8s 1.19+ 主流形态)。它过滤 spec.ingressClassName 是否匹配 Higress 的 ingress class。

思考:默认 ingress class 名是什么?答:higress,可通过 ConfigMap 配置。
L06

ingress (v1beta1)

pkg/ingress/kube/ingress/ 是 Old API 兼容。extensions/v1beta1.Ingress 在 K8s 1.22 已删除,但很多老集群仍在跑。Higress 保留这条线,让升级路径平滑。

思考:v1 与 v1beta1 字段差异主要在哪?提示:pathType、backend.service.name 等。
L07

kingress Knative

pkg/ingress/kube/kingress/ 监听 Knative 的 networking.internal.knative.dev/v1alpha1.Ingress。这让 Higress 可以替换 Knative 默认的 Kourier/Istio 网关。

思考:Knative Ingress 与原生 Ingress 的字段差异是什么?答:含 tag、percent、splits 等灰度概念。
L08

gateway (Gateway API)

pkg/ingress/kube/gateway/ 处理 gateway.networking.k8s.io 下的 Gateway / HTTPRoute / TCPRoute / TLSRoute。这是未来主流路径,Ingress 注解的"标准化继任者"。

思考:Gateway API 比 Ingress + 注解强在哪?答:原生支持 listener / route 分离、跨 namespace 引用。
L09

McpBridgeController

pkg/ingress/kube/mcpbridge/controller.go 只有 49 行——它就是个薄壳:监听 McpBridge CRD,把事件回调给 IngressConfig 的 AddOrUpdateMcpBridge / DeleteMcpBridge。真正的"协调多注册中心"逻辑在 registry/reconcile

思考:把 controller 做薄、reconcile 做厚带来什么好处?
L10

WasmPluginController

pkg/ingress/kube/wasmplugin/controller.go 同样很薄:监听 Higress 自定义的 extensions.higress.io/v1alpha1.WasmPlugin,把事件回给 IngressConfig 的 AddOrUpdateWasmPlugin。最终由 IngressConfig 把它合并到 Istio extensions.WasmPlugin 与 EnvoyFilter。

思考:Higress 与 Istio WasmPlugin CRD 名一样但 group 不同,为何这么设计?提示:扩展字段需要。
L11

Http2RpcController

Http2Rpc 是 Higress 的"HTTP→Dubbo 转换"CRD,配合 http2rpc 注解和 EnvoyFilter 可以让前端 HTTP 直接调 Dubbo 接口。Controller 同样薄壳,关键转换在 IngressConfig 的 constructHttp2RpcEnvoyFilter

思考:Dubbo 方法元数据怎么传给 Envoy?答:通过 EnvoyFilter.patch 把 method map 注入 typed_config。
L12

McpServerController

负责监听 McpServer CRD —— 描述外部 MCP Server 的接入信息。事件转发到 IngressConfig 与 pkg/ingress/mcp(注意:这里 "mcp" 是 Mesh Config Protocol,不是 Model Context Protocol,名字撞车)。

思考:怎么区分代码中两个 mcp?提示:上下文 + 引入路径。
L13

Secret Controller

pkg/ingress/config/secret_config_mgr.go 实现 Secret 监听:TLS 证书变了要触发对应 Gateway/Listener 的 push。Higress 还监听特殊 Secret(例如 ai-proxy 用的 API key Secret)。

思考:Secret 变更后 Envoy 多久能看到新证书?提示:SDS 推送+热加载,毫秒级。
L14

ConfigMap 全局配置

pkg/ingress/kube/configmap/ 监听全局 ConfigMap(默认 higress-config),里面塞 gzip / tracing / global mcpServer 等设置。Controller 把变化广播给依赖它的子模块。

思考:为什么不全用 CRD?提示:一些全局开关历史上就是 ConfigMap,迁移成本与运维习惯。
L15

workqueue 与重试

所有 controller 都用 workqueue.NewRateLimitingQueue:失败 requeue 时按指数退避。这是 K8s controller 写法的事实标准,Higress 没有偏离。

if err := c.reconcile(key); err != nil {
    c.queue.AddRateLimited(key); return
}
c.queue.Forget(key)
思考:永远失败的 key 会被无限重试吗?怎么避免?
L16

EventHandler 注册

IngressConfig 提供 RegisterEventHandler(kind, fn):让 Pilot 注册回调,当 controller 检测变更时 Higress 调 fn 通知 Pilot 重推。这是控制面与 controller 的"反向调用"通道。

思考:Pilot 收到通知后会立刻推送吗?答:进 debounce 队列。
L17

AggregateController

Higress 支持多集群:每个集群一个 IngressController 实例,外层用 common.AggregateController 聚合,对 IngressConfig 暴露统一接口。多集群下 WrapperConfig 里会带 clusterId 区分。

思考:多个集群有同名 Ingress 时谁优先?
L18

多集群 clusterId

clusterId 是 Istio MultiCluster 抽象的关键。Higress 沿用此设计:每个 client 携带 clusterId,Service / Endpoint / Config 命名时都带 clusterId 前缀避免冲突。

思考:单集群默认 clusterId 是什么?答:Kubernetes 或环境变量指定。
L19

资源版本与 ResyncPeriod

Higress informer 配置 ResyncPeriod=0(不周期 resync),完全靠事件驱动。这避免 large cluster 下的 list-watch 风暴。一旦丢事件会通过 K8s watch reconnect 自动追回。

思考:ResyncPeriod 设大或设 0 各有什么风险?
L20

写一个最小 controller

以 McpBridge 为模板,新增一个 controller 的最小步骤:

  1. api/networking/v1/ 定义 CRD types。
  2. make gen 生成 client/lister/informer。
  3. 新建 pkg/ingress/kube/myfeat/controller.go,模仿 mcpbridge controller。
  4. 在 IngressConfig 的 NewIngressConfig 里启动它,并注册回调。
  5. 在 List 或 convert* 里把数据消费成 Istio 配置。
本期收尾:你掌握了 Higress 所有 controller 的统一范式。下一期我们解剖最复杂的那个文件——ingress_config.go