Day 13 / 共 20 天 · 第 3 周 HTTP 与外部调用
Cluster 集群抽象
Day 12 学的 HttpContext 遥控器里有个 RouteCall,能调后端;明天(Day 14)要正式在插件里调外部服务,第一个问题是"调哪个后端"。wasm-go 用 8 种 Cluster 类型表达不同来源的后端(当前路由、K8s 服务、Nacos、固定 IP、域名…),今天把它们讲清——它是明天 HttpCall 的入参。
📍 你在整门课的位置(wasm-go 20 天 · 第 3 周 HTTP 与外部调用)
HttpContext D12→
Cluster 8 种 D13→
HttpCall 异步 D14→
Redis D15→
高级特性 W4
L01
为什么要 Cluster
🤔 痛点:Envoy 的"后端地址"格式反人类
你想让插件调一个 K8s 里的鉴权服务。Envoy 认的不是
http://auth-svc:8080 这么简单的地址,而是一串 outbound|8080||auth-svc.default.svc.cluster.local——竖线、端口、版本、FQDN 的固定拼法,Nacos 来源的格式又完全不同。让你手写、还得记住每种来源的拼法,极易写错。💡 本质:Cluster = 填表式的"地址翻译器"
Cluster 就像寄快递时的电子面单:你只填"收件人、公司、楼层"这些人话字段,系统自动生成快递公司内部认的那串编码地址。你填
ServiceName/Namespace/Port,K8sCluster 帮你拼成 Envoy 认的 outbound|...。不同来源(K8s/Nacos/域名/IP)= 不同的"面单模板",但填法都一样简单。"集群"= Envoy 眼里的一组后端
回想 Envoy/Istio 课:Envoy 把后端服务组织成"Cluster"(集群),每个集群有个名字(如
outbound|8080||foo.default.svc.cluster.local),里面是一组实例。插件要调后端,得告诉 Envoy"调哪个集群"——但集群名格式很复杂、不同来源(K8s/Nacos/固定IP)格式还不一样。wasm-go 的 Cluster 类型就是"帮你拼对集群名"的工具:你填服务名/端口,它生成 Envoy 认识的集群名。L02
Cluster 接口
cluster_wrapper.go:24-27:极简接口,只有两个方法:
type Cluster interface {
ClusterName() string // Envoy 集群名(调用时用)
HostName() string // 请求的 :authority 头
}
读法:8 种 Cluster 都实现这个接口,各自用不同规则生成
ClusterName 和 HostName。下游的 HttpCall(Day 14)只依赖这个接口——不管你用哪种 Cluster,调用方式一样。这是"面向接口编程"的典范:新增一种后端来源,加个实现即可。图注:一个 Cluster 接口,8 种实现"填空"出各自的集群名——面向接口编程,新增后端来源只需加一个实现类。
L03
RouteCluster:当前路由后端
cluster_wrapper.go:29-46:
type RouteCluster struct { Host string }
func (c RouteCluster) ClusterName() string {
routeName, _ := proxywasm.GetProperty([]string{"cluster_name"}) // 用当前请求的集群
return string(routeName)
}
func (c RouteCluster) HostName() string {
if c.Host != "" { return c.Host }
return GetRequestHost() // 默认用当前请求的 Host
}
最省事的一种
RouteCluster = "调当前请求本来要去的后端"。它直接读 Envoy 属性
cluster_name(当前请求匹配到的集群)。典型用途:插件想"预调一次当前后端"(比如先探一下),或改写后重新发。你什么都不用填,它自动用当前路由的目标。(对应 Day 12 HttpContext 的 RouteCall。)L04
K8sCluster:K8s 服务
cluster_wrapper.go:61-83:
type K8sCluster struct { ServiceName, Namespace string; Port int64; Version, Host string }
func (c K8sCluster) ClusterName() string {
namespace := "default"; if c.Namespace != "" { namespace = c.Namespace }
return fmt.Sprintf("outbound|%d|%s|%s.%s.svc.cluster.local",
c.Port, c.Version, c.ServiceName, namespace)
}
📝 举个例子:填字段 → 自动生成集群名
你写
换成 FQDN 外部服务:
wrapper.K8sCluster{ServiceName:"auth", Namespace:"prod", Port:8080}ClusterName() 自动生成 → outbound|8080||auth.prod.svc.cluster.local换成 FQDN 外部服务:
wrapper.FQDNCluster{FQDN:"api.example.com", Port:443} → outbound|443||api.example.com。你从头到尾没碰过那串竖线格式。这个集群名格式眼熟吗?
outbound|端口|版本|服务.命名空间.svc.cluster.local——这正是 Istio 的集群命名约定!(回想 Istio 课)你只填服务名/命名空间/端口,K8sCluster 帮你拼成这个格式。用途:插件要调集群内另一个 K8s 服务(比如调个鉴权微服务)。不用记那套复杂格式,填字段即可。L05
NacosCluster:Nacos 服务
cluster_wrapper.go:85-116:字段更多——ServiceName / Group(默认 DEFAULT-GROUP)/ NamespaceID / Port / IsExtRegistry / Version / Host。集群名按 Nacos 的命名规则拼(含 group、namespace)。
读法:回想上一站 Higress/Console:服务来源支持 Nacos(Day 14)。如果你的后端是从 Nacos 注册中心发现的,就用 NacosCluster——它按 Nacos 的服务标识规则拼集群名(group 里的
_ 会替换成 -)。IsExtRegistry 区分 edas/sae 等外部注册。L06
FQDN / Static / Dns / Consul
| Cluster 类型 | 用途 |
|---|---|
| FQDNCluster | 用完整域名 + 端口(:168,最常用于调外部 HTTP 服务,昨天 http-call 例子就用它) |
| StaticIpCluster | 固定 IP 后端(:117) |
| DnsCluster | DNS 解析的后端(:134) |
| ConsulCluster | Consul 注册中心的服务(:148) |
| TargetCluster | 直接指定集群名 + Host(:48,最底层,你自己拼好集群名) |
对应上一站的服务来源类型
还记得上一站 ServiceSource 的 8 种类型吗(nacos/consul/static/dns…)?这里的 Cluster 类型几乎一一对应——因为它们都在表达"后端从哪来"。控制面(Console)配服务来源、数据面(wasm-go 插件)用对应 Cluster 调用,两端概念统一。http-call 例子(Day 14)用的 FQDNCluster 是调外部 API 最常用的。
L07
集群名的含义(ClusterName vs HostName)
两个方法的区别很关键:
- ClusterName:Envoy 内部的集群标识——告诉 Envoy"把请求发到这组后端"。
- HostName:请求的
:authority头(Host)——后端服务看到的域名,影响后端的虚拟主机路由。
读法:调用时两者都要:ClusterName 决定"物理上发给谁",HostName 决定"HTTP 语义上访问哪个域名"。比如调一个 K8s 服务:ClusterName 是那串
outbound|...,HostName 是 svc.ns.svc.cluster.local。Day 14 的 HttpCall 会同时用到这两个值。⚠️ 常见误解:小白常以为"填了 ClusterName 就够了,HostName 可有可无"。其实少了 HostName,后端拿到的
:authority 头是空/错的,很多后端会按虚拟主机路由拒绝或返回错误页——两个都得对。类比:ClusterName 是"把包裹送到哪栋楼",HostName 是"包裹上写给哪家公司收",缺一不可。L08
今日小结 + 动手
🧠 今天你应该能回答
- 为什么要 Cluster 抽象?它帮你解决什么?
- Cluster 接口的两个方法各是什么?
- K8sCluster 的集群名格式和谁的约定一致?
- 8 种 Cluster 和上一站的服务来源类型什么关系?
🎯 记忆口诀
"调外部域名用 FQDN,调集群内服务用 K8s,注册中心用 Nacos/Consul,就地转发用 Route——填字段,别记那串竖线。"
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/wasm-go
grep -n "type.*Cluster struct" pkg/wrapper/cluster_wrapper.go
sed -n '61,116p' pkg/wrapper/cluster_wrapper.go
grep -n "FQDNCluster" examples/http-call/main.go
明天预告 · Day 14:HTTP 外部调用——用 Cluster +
HttpCall 异步调后端,看 ClusterClient/DispatchHttpCall、回调式编程、以及 ActionPause → Resume 的完整节奏(含 http-call 例子)。