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 调
若 istiod 问一个不在这 6 种里的类型(比如
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 Ingress、Gateway API、以及 Higress 自己的三个 CRD(McpBridge/WasmPlugin/Http2Rpc)、还有一个全局 ConfigMap。每种来源一个子控制器,各自 watch 对应资源、转成 Istio 配置。IngressConfig 把它们聚合起来,统一通过
List 暴露给 istiod。这是"多源聚合"——用一个中枢屏蔽多种输入的差异(和 Istio 的聚合注册表、OpenClaw 的渠道注册表同思想)。图注:左边杂乱的六种输入,被中枢收拢成右边统一、只读的 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 类型。onEvent(model.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 逐行 + "通知/生成解耦"的事件模型。