Day 03 / 共 20 天 · 第 1 周 全景与控制器

Ingress 控制器核心

Higress 自己写的心脏是 IngressConfig——它实现了 Istio 的 ConfigStore 接口(只读),聚合 6 个子控制器(Ingress/Gateway/McpBridge/WasmPlugin/Http2Rpc/ConfigMap)。今天看这个中枢怎么组装。昨天(Day 02)看的是开机装配清单,今天钻进清单里最重要那一件——翻译官的"心脏",为 Day 04 拆它到底怎么翻译一条 Ingress 铺路。

📍 你在 Higress 源码课的位置(第1周 全景与控制器 · 第2周 服务发现与配置)
D01 全景 D02 启动 D03 Ingress控制器 D04 配置翻译 D05 xDS-over-MCP D06 多注册中心 D07 McpBridge D08 Nacos D09 ServiceEntry D10 配置+证书
🤔 痛点:istiod 只认 Istio 官方文书,可用户递上来的是各种"申请单" 用户可能用 K8s Ingress、Gateway API、或 Higress 自己的三种 CRD,还有一个全局 ConfigMap——五花八门。istiod 却只想按标准接口 List 拿到统一的 6 种 Istio 配置。中间必须有个"中枢"把这些杂乱来源收拢、翻译成统一格式。这个中枢就是 IngressConfig
💡 本质:ConfigStore 是"计算属性",不是"仓库" 延续前台类比——IngressConfig 是前台的总台账,但它不存东西:istiod 每次来查(List),它都现场把 Ingress/CRD 翻译一遍再交出去。就像餐厅的"今日菜单"不是写死存起来的,而是按今天进的菜现算——你没法直接改菜单(只读),只能改进货(改 Ingress),菜单自动跟着变。
L01

只读 ConfigStore

Higress 的 ConfigStore 是"只读"的 回想 Istio 课 Day 03:ConfigStore 是"配置的 CRUD 抽象"。Higress 实现了这个接口,但它的 Create/Update/Delete 全部返回 ErrUnsupportedOp——它是只读的!为什么?因为 Higress 的配置不是"存进去"的,而是从 K8s Ingress/CRD/注册中心实时"算"出来的——istiod 来 List 时现场翻译生成,不能被 istiod 反写。好比一个"计算属性"——你不能直接改它,只能改它的输入(Ingress),它自动算出新值。这是关键心智:Higress 的 ConfigStore 是"输入的投影",不是"存储"。

👶 小白:只读那怎么"改配置"?我改了路由,它不接收吗?

👨‍🏫 老师:你改的不是这个 ConfigStore,而是它的输入——K8s 里的 Ingress/CRD。子控制器 watch 到输入变了,就触发重新翻译,下次 istiod 来 List 拿到的自然是新结果。Create/Update/Delete 之所以直接返回 ErrUnsupportedOp,是为了堵死"istiod 反过来写 Higress"这条路——配置的唯一真相源永远是 K8s 里的 Ingress,不允许别人绕过它偷改。

L02

IngressTranslation 门面

// pkg/ingress/translation/translation.go:33 接口断言
var _ istiomodel.ConfigStoreController = ...  // 实现 Istio 的 ConfigStore
var _ istiomodel.IngressStore = ...
// 结构(:38):内含 ingressConfig(K8s Ingress)+ kingressConfig(Knative Ingress)
// List(:163):只接受 6 种类型,聚合 ingress + kingress
// Create/Update/Delete(:194):全返回 ErrUnsupportedOp(只读)
读法:IngressTranslation 是门面层——对 istiod 表现为一个标准 ConfigStore,内部聚合 K8s Ingress 和 Knative Ingress 两套。List 只返回 6 种 Istio 类型(Gateway/VirtualService/DestinationRule/EnvoyFilter/ServiceEntry/WasmPlugin)。
📝 举个例子:istiod 来问一句,门面答什么 istiod 调 List(gvk.VirtualService, "")("把所有 VirtualService 给我")→ 门面把 K8s Ingress 和 Knative Ingress 各自翻出的 VirtualService 合并成一个列表返回。
若 istiod 问一个不在这 6 种里的类型(比如 Sidecar)→ 门面返回空,因为 Higress 根本不生产那种配置。
L03

IngressConfig 中枢

// pkg/ingress/config/ingress_config.go:104 IngressConfig(心脏)
type IngressConfig struct {
  mcpbridgeController   ...   // McpBridge CRD(注册中心,Day 07)
  wasmPluginController  ...   // WasmPlugin CRD(Day 14)
  http2rpcController    ...   // Http2Rpc CRD(HTTP→Dubbo/gRPC)
  configmapMgr          ...   // higress-config ConfigMap(全局配置)
  RegistryReconciler    ...   // 注册中心对账(Day 07)
  xxxHandlers []EventHandler  // istiod 传来的回调
  XDSUpdater            ...   // 触发推送
}
读法:IngressConfig 是 Higress 独有代码的核心——它持有所有子控制器 + 各类型的 handler + XDSUpdater(配置变了通知 istiod)。NewIngressConfig:189)逐个 new 子控制器并 AddEventHandler 绑回调。
L04

六大子控制器

  • Ingress 控制器:watch K8s Ingress(Day 04 翻译)。
  • Gateway 控制器:watch Gateway API(可选)。
  • McpBridge 控制器:215):watch McpBridge CRD → 注册中心(Day 07)。
  • WasmPlugin 控制器:220):watch WasmPlugin CRD(Day 14)。
  • Http2Rpc 控制器:225):watch Http2Rpc CRD(HTTP→RPC 转换)。
  • ConfigMap 管理器:230):watch higress-config(全局 tracing/gzip 等 → EnvoyFilter)。
六个控制器 = 六种配置来源 Higress 的配置来自多处:标准 K8s IngressGateway API、以及 Higress 自己的三个 CRD(McpBridge/WasmPlugin/Http2Rpc)、还有一个全局 ConfigMap每种来源一个子控制器,各自 watch 对应资源、转成 Istio 配置。IngressConfig 把它们聚合起来,统一通过 List 暴露给 istiod。这是"多源聚合"——用一个中枢屏蔽多种输入的差异(和 Istio 的聚合注册表、OpenClaw 的渠道注册表同思想)。
六种来源 → 一个中枢 → 统一的只读 ConfigStore K8s IngressGateway APIMcpBridge CRDWasmPlugin CRDHttp2Rpc CRDhigress-config ConfigMap IngressConfig 中枢·现场翻译 (计算属性) 只读 ConfigStore List() 返回 6 种 Istio 配置类型 istiod 来 List 时才现场翻译
图注:左边杂乱的六种输入,被中枢收拢成右边统一、只读的 6 类 Istio 配置——istiod 只跟右边打交道。
L05

挂进 Environment

// pkg/bootstrap/server.go initConfigController(:220)
ingressConfig := translation.NewIngressTranslation(s.kubeClient, s.xdsServer, ns, options)
s.configStores = append(s.configStores, ingressConfig)
aggregateConfigController, _ := configaggregate.MakeCache(s.configStores)  // Istio 的聚合缓存
s.environment.ConfigStore = aggregateConfigController  // ★ 挂进 Istio Environment
这一行是 Higress 和 istiod 的"接线" s.environment.ConfigStore = aggregateConfigController(内含 IngressConfig)——这一行把 Higress 的配置源挂进了 Istio 的 Environment还记得 Istio 课 Day 05 吗?istiod 的 Generator 生成配置时调 c.Environment.List(gvk.X, ...)——现在这个 List 就落到 Higress 的 IngressConfig 上了!所以 istiod 要 VirtualService 时,实际是问 Higress 的 IngressConfig 要,IngressConfig 现场从 Ingress 翻译出来给它。这就是"Higress 成为 istiod 配置源"的代码落点。
L06

通用控制器泛型

三个 CRD 控制器(McpBridge/WasmPlugin/Http2Rpc)复用一个泛型基类 CommonController[lister]pkg/ingress/kube/controller/model.go:46)——基于 client-go informer + Istio 的工作队列(最多重试 5 次)。

读法:三个 CRD 控制器逻辑几乎一样(watch → 变更入队 → 调 handler),用 Go 泛型 CommonController[T] 复用,各自只传自己的 lister 类型。onEventmodel.go:97)从 lister 取对象、NotFound 走删除、否则走更新。又是"泛型消除重复"(Istio 课 Day 03 的 SubscriptionBase 同思路)。
L07

Run / HasSynced

// ingress_config.go Run(:2001):goroutine 并发启动全部子控制器
// HasSynced(:2016):所有子控制器都 synced 才 ready
读法:Run 并发启动 6 个子控制器(各自 watch)。HasSynced 要全部同步好才算就绪——对应 Day 02 的 waitForCacheSync只有所有配置源都加载完,Higress 才对 istiod 提供服务(否则喂不完整配置,和 istiod 的 ready 门禁一个道理)。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 为什么 Higress 的 ConfigStore 是只读的?
  • IngressTranslation 门面聚合了什么?List 返回哪 6 种类型?
  • IngressConfig 中枢持有什么?
  • 六大子控制器对应哪六种配置来源?
  • environment.ConfigStore = ... 这一行为什么关键?

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/higress
grep -n 'ErrUnsupportedOp\|ConfigStoreController' pkg/ingress/translation/translation.go
grep -n 'type IngressConfig struct\|func NewIngressConfig\|func.*Run\|func.*HasSynced' pkg/ingress/config/ingress_config.go
sed -n '220,262p' pkg/bootstrap/server.go
明天预告 · Day 04配置翻译——重头戏!Higress 怎么把一个 K8s Ingress(+ 注解)翻译成 Istio 的 VirtualService/Gateway?ConvertHTTPRoute 逐行 + "通知/生成解耦"的事件模型。
← Day 02 启动流程 Day 04 · 配置翻译 →