Wasm 插件系统概览
第 3 周讲 Higress 的插件生态——它最活跃、最"AI"的部分。昨天(Day 10)讲完配置与证书这些控制面收尾;今天转到数据面的扩展能力,建立全景:Wasm 插件 vs golang-filter 两套机制、和 Envoy proxy-wasm 的关系、SDK 已迁到独立 module。这是后面 Day 12-15 一切"写插件"的地基。
两套扩展机制
| Wasm 插件 | Golang Filter | |
|---|---|---|
| 目录 | plugins/wasm-go(+rust/cpp) | plugins/golang-filter |
| 运行 | 编译成 .wasm,跑 proxy-wasm 沙箱 | 编译成 .so,Envoy Go filter 动态加载 |
| 隔离 | 沙箱隔离、跨语言、热更 | 原生 Go、无沙箱、性能高、可用完整 Go 生态 |
| 用途 | 绝大多数用户插件、AI 插件 | mcp-server/mcp-session(需常驻 goroutine 的 SSE) |
SDK 已独立
plugins/wasm-go/pkg/ 现在只剩 MCP Server SDK。核心插件框架(wrapper/log/matcher)已抽成独立 module github.com/higress-group/wasm-go,通过 go.mod 依赖引入。所以看 SDK 源码要去 module 缓存:
~/go/pkg/mod/github.com/higress-group/wasm-go@<版本>/pkg/,插件 import 路径是 github.com/higress-group/wasm-go/pkg/wrapper(不是 仓库内路径)。go env GOPATH/pkg/mod 下找,仓库 plugins/wasm-go/pkg 只剩 MCP SDK。每个插件 go.mod 锁不同 SDK 版本。👶 小白:为什么要把核心 SDK 从主仓库搬出去、单独做一个 module?留在仓库里不是更好找吗?
👨🏫 老师:因为 SDK 和主仓库的发布节奏不一样。SDK 是"写插件的人"依赖的稳定 API,需要独立打版本号、独立升级;主仓库改得频繁。搬出去后,你的插件 go.mod 能锁定"我就用 wasm-go v1.x.x",主仓库怎么改都不影响你。坑就在这:你在仓库里翻 plugins/wasm-go/pkg 找 wrapper 会扑空——它已经在 ~/go/pkg/mod/github.com/higress-group/wasm-go@版本/ 里了。
proxy-wasm 绑定
底层是 github.com/higress-group/proxy-wasm-go-sdk(Higress fork 了 tetratelabs 版,加了 redis、route call、streaming body)。入口 SetVMContext(entrypoint.go:27)把 VMContext 存入全局。
proxy_on_request_headers 等)和宿主函数(proxy_get_header_map_value 等)封装成 Go 能调的形式。用户几乎不直接碰它——都通过上层的 wrapper(Day 12)。这一层就是 Envoy 课学的 proxy-wasm 在 Go 侧的落地:Envoy 调 Go 函数、Go 调 Envoy 宿主函数。Higress fork 加了 redis 调用等增强 ABI(呼应 Envoy 课 Day 17 的 Higress 定制 host functions)。下行回调(Host→Wasm)
// proxywasm/internal/abi_callback_l7.go(//go:wasmexport,Envoy 调 Go)
proxy_on_request_headers → proxyOnRequestHeaders(:23)
proxy_on_request_body(:37)/ proxy_on_response_headers(:63)/ proxy_on_response_body(:76)
proxy_on_http_call_response(:102,外呼响应回调)
proxy_on_redis_call_response(:133,Higress 加的 Redis 回调)
proxy_on_request_headers → Go 侧处理 → 返回 types.Action(Continue/Pause)。这些是 //go:wasmexport 导出给 Envoy 调的函数(对应 Envoy 课 Day 17 的"下行 filter 钩子")。Authorization: Bearer xxx 走到关卡:① Envoy 下行调
proxy_on_request_headers 通知插件"有请求头了";② 插件想看这个头,就上行调
proxy_get_header_map_value("authorization") 读出 Bearer xxx;③ 插件判断非法,上行调
proxy_send_local_response(401) 当场拦下;④ 插件返回
types.ActionPause 告诉 Envoy"别往上游转了"。一进一出全靠"下行(Envoy 通知)+ 上行(插件操作 Envoy)"这两个方向的调用完成。
上行宿主调用(Wasm→Host)
// proxywasm/internal/abi_hostcalls.go(//go:wasmimport,Go 调 Envoy)
proxy_log / proxy_send_local_response
proxy_get/add/replace/remove_header_map_value(读写 header)
proxy_get_buffer_bytes(读 body)/ proxy_http_call(外呼)
proxy_redis_call(Higress)/ proxy_define_metric / proxy_increment_metric
//go:wasmimport 的宿主函数(对应 Envoy 课 Day 17 的"上行 host 函数")。下行(Envoy 通知插件)+ 上行(插件操作 Envoy)= 插件的全部能力边界。//go:wasmexport = 我导出、别人(Envoy)用;//go:wasmimport = 我导入、我去用别人(Envoy)的。插件能做的一切,无非"被通知"和"去操作"这两下——记住这句,看任何插件代码都不会迷路。连回 Envoy 课
• Envoy 课 Day 17:讲了 Envoy 的 proxy-wasm ABI、Context 双向翻译、VM 生命周期——那是 C++ 宿主侧。
• Istio 课 Day 17:讲了 Higress 用 EnvoyFilter 的
WasmPhase/WasmPriority 把 Wasm 插件插进 Envoy 过滤器链。• Higress 课(本周):讲用 wasm-go SDK 写插件(Go 侧),编译成 .wasm,由 Higress 控制面下发、Envoy 加载执行。
完整链路:wasm-go 写插件(Higress)→ 编译成 .wasm → WasmPlugin CRD 配置 → istiod 生成 EnvoyFilter 插入过滤器链(Istio)→ Envoy 的 Wasm runtime 加载执行(Envoy)。你三站都学了,就打通了整个 Wasm 插件生态的技术底座。
配置下发闭环
WasmPlugin CRD → Higress 控制面 convertIstioWasmPlugin(ingress_config.go:1123)转成标准 Istio WasmPlugin(含 Url 拉 .wasm、Sha256、PluginConfig)→ 经 MCP 喂 istiod → Envoy 从 URL 拉 .wasm 加载。
.wasm 文件放在 OCI/HTTP,WasmPlugin CRD 声明 URL + 配置。控制面翻译成 Istio WasmPlugin(Day 04-05 的翻译流程),Envoy 按 URL 拉取 .wasm 加载。Day 12-14 会看插件配置里的 _rules_ 怎么和 SDK 的 RuleMatcher 闭环(控制面写、数据面读)。今日小结 + 动手
🧠 今天你应该能回答
- Wasm 插件 vs golang-filter 两套机制的取舍?
- SDK 现在在哪(独立 module)?import 路径?
- proxy-wasm-go-sdk 是什么桥?
- 下行回调 vs 上行宿主调用各是什么方向?
- Wasm 插件怎么连接 Envoy 课和 Istio 课?
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/higress
ls plugins/
ls plugins/wasm-go/pkg/ # 只剩 mcp
cat plugins/wasm-go/extensions/hello-world/go.mod | grep wasm-go
SetCtx + Option 模式、三级 Context、配置解析、请求生命周期。