Wasm 扩展(Higress 的基础)
这是全课最关键的一课之一——因为 Higress 的插件生态就建立在 Envoy 的 Wasm 支持上。今天看 WebAssembly 插件怎么工作、proxy-wasm ABI、Context 双向翻译层。
为什么要 Wasm
.wasm 文件,Envoy 运行时动态加载它——不用重编译 Envoy、不用懂 Envoy C++ 内核、还能热更新插件。而且 Wasm 运行在沙箱里(安全隔离,插件崩了不影响 Envoy)。这就是 Higress "插件市场"的技术基础——用户用 wasm-go(本系列后面)写插件,Higress 动态加载。.wasm 卡带。onRequestHeaders 等钩子)和"插件怎么调代理"(getHeader/sendResponse 等宿主函数)。遵守这套插头标准,卡带就能到处插。host=Envoy 侧插座、SDK=给插件作者的接线库(wasm-go 就是 Go 版 SDK)、引擎(V8 等)=真正执行卡带字节码的读卡器。decodeHeaders → Context 翻译成钩子 onRequestHeaders 通知插件 → 插件调宿主函数 addHeaderMapValue("x-user","alice") → Context 把它翻译成对 Envoy header 的真实写入 → 后端收到时就多了这个头。全程不用改 Envoy 一行 C++、不用重编译。proxy-wasm
Envoy 的 Wasm 支持基于 proxy-wasm(代理的 Wasm 规范)。higress-group 这个 fork 用的是 Higress 定制版依赖(bazel/repository_locations.bzl):proxy_wasm_cpp_host(宿主,来自 github.com/higress-group)、proxy_wasm_cpp_sdk(SDK)。运行时引擎有 v8/wamr/wasmtime/null 四种(source/extensions/wasm_runtime/)。
Wasm filter 工厂
// source/extensions/filters/http/wasm/config.h(name="envoy.filters.http.wasm")
// createFilterFactoryFromProtoTyped 返回闭包:
return [filter_config](Http::FilterChainFactoryCallbacks& callbacks) -> void {
auto filter = filter_config->createContext(); // 每条请求创建一个 Context
callbacks.addStreamFilter(filter); // config.h:62 作为 HTTP filter
callbacks.addAccessLogHandler(filter); // config.h:63
};
REGISTER_FACTORY 注册、配置里写 envoy.filters.http.wasm 就启用。每个请求创建一个 Context(L04)。所以 Wasm 插件对 Envoy 来说就是"一个特殊的 L7 过滤器",无缝融入 Day 08 的过滤器链。Context 翻译层
// source/extensions/common/wasm/context.h:107
class Context : public proxy_wasm::ContextBase, // proxy-wasm ABI 上下文
public Http::StreamFilter, // 同时是 Envoy HTTP filter
public Network::Filter, ... { };
Context 一身两职:对 Envoy 它是一个 Http::StreamFilter(Day 08 的 L7 过滤器);对 Wasm 插件它是 proxy_wasm::ContextBase(ABI 上下文)。它坐在中间做双向翻译:Envoy 的过滤器事件(decodeHeaders 等)→ 翻译成 proxy-wasm 的钩子(onRequestHeaders)调用插件;插件调宿主函数(getHeader)→ 翻译成操作 Envoy 的数据。就像同声传译:一边是 Envoy 的"语言",一边是 Wasm 插件的"语言",Context 让它们能对话。每个请求一个 Context 实例。👨🏫 老师:不是。插件被关在沙箱里,它能做的一切都取决于宿主暴露了哪些 host functions(上行 ABI)。宿主没给的能力,插件碰不到——这既是安全边界,也是为什么 Higress 要"加料"(L07 给插件开了 redisCall、setBuffer 等更多 host functions)。
双向 ABI
下行(Envoy → Wasm,filter 钩子):decodeHeaders(context.h:179)→ 调插件的 onRequestHeaders;decodeData/encodeHeaders/encodeData/log 同理。
上行(Wasm → 宿主,host functions):插件调这些,Context 实现:
getHeaderMapValue(:240)/ addHeaderMapValue(:238) // 读写 header
getBuffer(:252) // 读 body
sendLocalResponse(:217) // 直接返回响应
httpCall(:262)/ grpcCall(:281) // 外呼其他服务
defineMetric(:275)/ incrementMetric(:276) // 指标(映射到 Envoy stats,Day 18)
VM 与运行时
Wasm(wasm.h:44)是一个 Wasm 执行实例;PluginConfig(wasm.h:224)是配置载体,getOrCreateThreadLocalPlugin(:216)按线程惰性克隆 VM。运行时工厂 WasmRuntimeFactory(wasm_runtime_factory.h:15)决定用哪个引擎(默认 V8)。
getOrCreateThreadLocalPlugin 按线程克隆),插件状态线程隔离——不用加锁。这就是为什么 Wasm 能无缝融入 Envoy 的高性能架构。WasmRuntimeFactory 是又一个 Day 16 的工厂模式应用——V8/WAMR/Wasmtime 各是一个可选引擎实现(REGISTER_FACTORY(V8RuntimeFactory, WasmRuntimeFactory))。Higress 定制
higress-group fork 对 Wasm 做了增强(#if defined(HIGRESS)):setBuffer(改请求/响应体)、redisCall(context.h:270,插件直接调 Redis)、getUpstreamHosts/setUpstreamOverrideHost(:223/:224,插件干预负载均衡选主机)、VM 失败时按 FailurePolicy + 退避热重载。
今日小结 + 动手
🧠 今天你应该能回答
- Wasm 解决 C++ 扩展的什么痛点?(重编译/门槛/热更新/沙箱)
- proxy-wasm 是什么?host / SDK / 引擎各是什么?
- Context 为什么是"双面翻译官"?
- 双向 ABI:下行和上行各是什么方向?
- Wasm VM 怎么呼应 Envoy 线程模型?Higress 定制了什么 ABI?
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/envoy
sed -n '107,120p' source/extensions/common/wasm/context.h # Context 多继承
grep -n 'getHeaderMapValue\|httpCall\|redisCall\|setUpstreamOverrideHost' source/extensions/common/wasm/context.h | head
ls source/extensions/wasm_runtime/
/stats /clusters /config_dump)怎么内省运行状态。