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

配置翻译(重头戏)

Higress 怎么把一个 K8s Ingress(+ 一堆注解)翻译成 Istio 的 VirtualService/Gateway?今天读翻译核心 ConvertHTTPRoute,以及一个精妙的"通知/生成解耦"事件模型。昨天(Day 03)看的是"翻译官的心脏 IngressConfig 怎么组装",今天正式看它怎么翻译一条具体的 Ingress——这是全课最"重头"的一天,翻完的成品(VirtualService)明天(Day 05)会被打包喂给 istiod。

📍 你在 Higress 源码课的位置(第1周 全景与控制器 · 第2周 服务发现与配置)
D01 全景 D02 启动 D03 Ingress控制器 D04 配置翻译 D05 xDS-over-MCP D06 多注册中心 D07 McpBridge D08 Nacos D09 ServiceEntry D10 配置+证书
💡 本质:翻译官把"大白话申请单"逐条译成"官方文书" 延续前台/翻译官类比——用户递上来的 Ingress 是大白话申请单("api.foo.com 的 /v1 请求,转给 backend 服务"),加上一堆注解便签("顺便给我开个金丝雀、加个 CORS")。翻译官要把它逐条译成 istiod 认得的官方文书:host→Gateway、path→VirtualService 的匹配规则、backend→转发目标、注解→加权/鉴权。注解 = 让你用便签写需求,不用亲手写复杂的官方文书——这就是 Higress 比 Istio 易用的核心。
L01

聚合入口 List

// pkg/ingress/config/ingress_config.go List(:288)
// 对每种类型:listFromIngressControllers + listFromGatewayControllers
//   中间对每个 config 跑 templateProcessor.ProcessConfig(模板/Secret 引用替换)
// listFromIngressControllers(:324)分发中心:
//   ingressController.List() 收集 Ingress → SortIngressByCreationTime 排序
//   → createWrapperConfigs 包装 → 按类型 switch 到 convertGateways/convertVirtualService/…
读法:istiod 来 List(gvk.VirtualService) 时,触发这条翻译链:收集所有 Ingress → 排序(保证确定性)→ 包装 → 按目标类型翻译。配置是"现场翻译"的(Day 03 只读 ConfigStore)——不预存,istiod 要时才算。
L02

解析注解

createWrapperConfigs:387)把每个 Ingress 的 annotations 用 annotationHandler.Parse 解析成结构化的 annotations.Ingress,打包成 WrapperConfig。注解解析器在 pkg/ingress/kube/annotations/(canary/auth/cors/retry/rewrite/header_control/ip_access_control/local_rate_limit…)。

注解 = Higress 的"简易配置语言" Istio 要配限流/鉴权/重写,得写复杂的 VirtualService/EnvoyFilter。Higress 让你用 K8s Ingress 的注解(annotation)就能配——比如 higress.io/canary-weight: "20" 做金丝雀、higress.io/enable-cors: "true" 开 CORS。每个注解对应 annotations/ 里一个解析器,把注解翻译成内部结构。这就是 Higress"比 Istio 易用"的关键——用注解隐藏了 Istio CRD 的复杂性。解析出的结构后续应用到 VirtualService/DestinationRule。
L03

convertGateways

// ingress_config.go convertGateways(:433)
// 遍历 wrapperConfigs → ingressController.ConvertGateway
// 为每个 host 产出 config.Config{GroupVersionKind: gvk.Gateway, Spec: gateway.Gateway}
// ConvertGateway(ingressv1/controller.go:339):为每条 rule 的 host 构造 Gateway
//   加 Selector(gatewaySelectorKey/Value)、HTTP/HTTPS Server、端口、SSLPassthrough
读法:Ingress 的每个 host 翻译成一个 Istio Gateway(Istio 课 Day 03 的 Gateway CRD)——定义"在哪个端口、什么协议、什么 host、什么 TLS 暴露"。Selector 把 Gateway 绑定到 Higress 网关 Pod。
L04

ConvertHTTPRoute(路由核心)

// pkg/ingress/kube/ingressv1/controller.go ConvertHTTPRoute(:531)
// 逐 rule.host 建 WrapperVirtualService(:590)
// 逐 path 建 WrapperHTTPRoute{HTTPRoute: &networking.HTTPRoute{}}(:605)
// PathType 映射(:612):正则/Exact/Prefix → 内部 PathType
//   generateHttpMatches 生成 HTTPMatchRequest(:637)
// backendToRouteDestination(:664):K8s backend service → Istio HTTPRouteDestination
// 冲突路由检测(definedRules/Route2Ingress,:645-691)
这就是"Ingress → VirtualService"的翻译现场 一条 Ingress 规则("host=api.foo.com, path=/v1 → service=backend:8080")被翻译成 Istio VirtualService 的一条 HTTPRoute:①host 对应 VirtualService;②path + PathType(精确/前缀/正则)翻译成 HTTPMatchRequest(匹配条件);③backend service 翻译成 HTTPRouteDestination(转发目标)。还检测路由冲突(两条 Ingress 定义了同样的路由)。翻译完的 VirtualService 就是 Istio 课 Day 08 里 istiod 拿去生成 Envoy Route 的输入——两站在这里对接:Higress 生成 VirtualService,istiod 把它变成 Envoy 配置。
📝 举个例子:一条 Ingress → 一条 HTTPRoute 输入 Ingress:host: api.foo.compath: /v1(Prefix)backend: order-svc:8080
翻译产物(VirtualService 里的一条 HTTPRoute,示意):
hosts: ["api.foo.com"] → 定位到哪个 VirtualService
match: [{ uri: { prefix: "/v1" } }] ← path+PathType 变成匹配条件
route: [{ destination: { host: order-svc, port: 8080 } }] ← backend 变成转发目标
申请单的三部分 → 官方文书的三个字段 Ingress(申请单) host: api.foo.com path: /v1 (Prefix) backend: order-svc:8080 VirtualService(文书) → hosts → match.uri.prefix → route.destination ConvertHTTPRoute
图注:host / path / backend 三部分一一对应到 VirtualService 的三个字段——这就是翻译的骨架。
L05

金丝雀 / 权重

convertVirtualServiceingress_config.go:486)在翻译后还做:annotationHandler.ApplyRoute 应用注解、applyCanaryIngresses 处理金丝雀、normalizeWeightedCluster 归一化权重到 100、ApplyDefaultBackend 默认后端。

金丝雀发布怎么实现? 你有一个主 Ingress 指向 v1,再建一个带 canary-weight: "20" 注解的 Ingress 指向 v2。applyCanaryIngresses 把它们合并成一条 VirtualService,路由带权重(v1: 80%、v2: 20%)。normalizeWeightedCluster 保证权重加起来是 100。于是 20% 流量走 v2——金丝雀发布。用户只需加个注解,Higress 帮你翻译成 Istio 的加权路由(Istio 课 Day 08 的 route 权重)。这就是注解体系(L02)的威力——复杂能力,简单配置。
L06

Http2Rpc / EnvoyFilter

Higress 的高级能力通过生成 EnvoyFilter 实现:constructHttp2RpcEnvoyFilter:1576,HTTP→Dubbo/gRPC 协议转换)、constructMcpSseStatefulSessionEnvoyFilter:2102,MCP SSE 会话保持)、configmapMgr.ConstructEnvoyFilters(全局 tracing/gzip)。

读法:Istio 高层 CRD 覆盖不到的能力(HTTP 转 Dubbo、SSE 会话保持、全局 tracing),Higress 用 EnvoyFilter(Istio 课 Day 17 的逃生舱)直接 patch Envoy 配置实现。Http2Rpc CRD 让你声明"HTTP 请求怎么映射到 Dubbo 方法参数",翻译成 EnvoyFilter。这呼应 Istio 课 Day 17——EnvoyFilter 是 Higress 加高级功能的主要手段之一。
L07

通知/生成解耦

// ingressv1/controller.go onEvent(:154):
// Ingress 变更时,并不直接生成配置!
// 而是对 dr/vs/ef/gateway 四类构造带 constants.AlwaysPushLabel:"true" 的空 config.Meta
// 调用对应 handler(:219)触发 istiod 重新拉取
"通知"和"生成"分开——一个巧妙设计 Ingress 变了,Higress 不立即翻译生成配置,而是发一个"喂,配置变了,你来重新拉"的通知给 istiod。istiod 收到通知 → 发 xDS/MCP 请求要配置 → Higress 的 Generator 调 List()此时才现场翻译(L01-06)→ 返回给 istiod。为什么这样?因为"生成"是按需的、带 proxy 上下文的(Istio 课 Day 01 的"带 Proxy 翻译")——提前生成没意义。AlwaysPushLabel 强制 istiod 跳过差异比较直接推。这是"事件驱动 + 惰性生成"的解耦:变更只管通知,生成等到被拉取时才做。

👶 小白:为什么不干脆改一次 Ingress 就翻译一次、把结果存起来,等 istiod 来直接给?

👨‍🏫 老师:因为翻译结果和"谁来问、当时的上下文"有关,而且 istiod 什么时候来拉、拉哪几类,是它自己决定的。提前算好存起来,既可能白算(没人来拉),又要额外维护"缓存和输入是否一致"的麻烦。更省心的做法:变更时只喊一嗓子"配置变了,来拉吧",真正翻译推迟到 istiod 发请求、调 List() 那一刻。就像餐厅不会把所有菜提前炒好摆着,而是等你点了才下锅——新鲜、不浪费。

L08

今日小结 + 动手

🧠 今天你应该能回答

  • List 触发的翻译链?为什么是"现场翻译"?
  • 注解体系是什么?举例它怎么隐藏 Istio 复杂性?
  • ConvertHTTPRoute 把 Ingress 的哪三部分翻译成 VirtualService 的什么?
  • 金丝雀发布怎么用注解实现?
  • Http2Rpc 等高级能力用什么实现?通知/生成解耦是什么?

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/higress
grep -n 'func.*ConvertHTTPRoute\|func.*ConvertGateway\|func.*onEvent' pkg/ingress/kube/ingressv1/controller.go
grep -n 'convertVirtualService\|applyCanaryIngresses\|AlwaysPushLabel' pkg/ingress/config/ingress_config.go | head
ls pkg/ingress/kube/annotations/
明天预告 · Day 05(第1周收官)xDS-over-MCP——翻译好的 Istio 配置怎么"喂"给 istiod?MCP Generator 怎么把 config.Config 打包成 MCP Resource,通过 15051 端口的 gRPC 流下发。这是 Higress→istiod 的接口。
← Day 03 控制器核心 Day 05 · xDS-over-MCP →