Envoy Wasm 插件执行架构:扩展点、执行模型与运行时配置全解析
导读
本文基于 Envoy 官方架构文档(wasm.rst)展开,系统讲解 Envoy 如何通过 Proxy-Wasm ABI 执行 WebAssembly 插件:从插件可挂载的五大扩展点,到 Context / Plugin / VM 三大抽象的关系,再到过滤器运行时在主线程与工作线程间的执行与克隆模型,最后深入到 proxy_call_foreign_function 宿主函数、Envoy Attributes 以及 V8 / WAMR / Wasmtime / Null VM 四类运行时。读完本文,你将掌握 Envoy Wasm 插件"写一次、随处运行"的底层原理,并能结合 wasm.proto 中 VmConfig、PluginConfig 的字段含义,独立配置与调试 Wasm 过滤器。
一、Wasm 插件与 Proxy-Wasm 规范
Envoy 支持执行 Wasm 插件:这些模块针对 Proxy-Wasm 规范 编写,该规范定义了宿主(Host)与访客(Guest)之间的扩展接口,使模块可以实现自定义的扩展逻辑。当前推荐使用 Proxy-Wasm ABI(Application Binary Interface)版本 0.2.1。
Wasm 提供了一种可移植且紧凑的二进制可执行格式,可以从多种语言(如 C++ SDK、Rust SDK)一次编译、随处运行。也就是说,同一份 .wasm 字节码既可以运行在 Envoy 中,也可以运行在其他支持 Proxy-Wasm ABI 的代理(如 Mosn、Nginx 的 wasm 模块等)上,插件开发者无需为每个宿主重新编译或重写业务逻辑。
在 Envoy 仓库中,Wasm 相关的公共代码集中在 source/extensions/common/wasm 目录,其中:
- wasm.h 定义了
Wasm类(Wasm 执行实例,管理 Envoy 侧的 Wasm 接口)、WasmHandle与PluginHandle; - wasm_vm.cc 实现了
EnvoyWasmVmIntegration,负责把 Envoy 的日志、错误处理与 Null VM 的宿主函数桥接进 Proxy-Wasm 运行时; - foreign.cc 实现了一组 Envoy 扩展的宿主函数(见下文"Foreign functions")。
动手实践:官方在示例项目(envoyproxy/examples)中提供了 wasm-cc sandbox,可参考 Wasm 过滤器配置文档 中展示的 envoy.yaml 示例,了解如何从本地磁盘加载一个 Wasm 二进制并挂载为过滤器。
二、五大扩展点:Wasm 插件可以挂在哪里
Envoy 提供了多个扩展点,Wasm 插件可以在其中被调用:
| 扩展点 | 对应 v3 API 消息 | 作用 |
|---|---|---|
| HTTP 过滤器 | extensions.filters.http.wasm.v3.Wasm | 在 HTTP 请求/响应路径上执行插件逻辑 |
| 网络层(L4)过滤器 | extensions.filters.network.wasm.v3.Wasm | 在 TCP 连接层执行插件逻辑 |
| 统计接收器(StatsSink) | extensions.stat_sinks.wasm.v3.Wasm | 将插件作为指标导出通道 |
| 访问日志器(AccessLogger) | extensions.access_loggers.wasm.v3.WasmAccessLog | 用插件自定义访问日志格式与输出 |
| 后台服务 | extensions.wasm.v3.WasmService | 作为独立后台服务运行,不绑定具体请求 |
Envoy 在插件上具体调用哪些函数,取决于该插件被配置在哪个扩展点上。以 HTTP 过滤器为例:
- 在 api/envoy/extensions/filters/http/wasm/v3/wasm.proto 中,
Wasm消息只包含一个config字段,类型为通用的envoy.extensions.wasm.v3.PluginConfig; - 在 source/extensions/filters/http/wasm/config.h 中,
WasmFilterConfig以名称envoy.filters.http.wasm注册为NamedHttpFilterConfigFactory,同时通过UpstreamWasmFilterConfig注册为上游 HTTP 过滤器工厂; - 注册后,过滤器工厂在创建过滤器链时会把插件同时注册为 stream filter 与 access log handler(见
config.h中addStreamFilter(filter)与addAccessLogHandler(filter)的调用),印证了 HTTP 插件可以同时参与请求处理和日志输出。
从 source/extensions/extensions_metadata.yaml(约第 797 行)可以看到 envoy.filters.http.wasm 的元数据:当前状态标记为 alpha(仍在活跃开发中,能力会持续扩展,配置结构也可能变化),安全姿态(security_posture)标记为 unknown,配置时需结合自身信任模型评估风险。
三、Contexts、Plugins 与 VMs:三个核心抽象
理解 Envoy Wasm 的关键是区分三个概念:
Context(上下文) 指宿主(Envoy)与访客(Wasm 字节码)之间的接口实现。以 HTTP 过滤器为例,存在两类 context:
- root context(ID 为 0):仅承载配置的上下文,负责接收与处理插件配置;
- per-request context:每个请求创建一次,承载单次请求的流式回调。
VM(虚拟机) 指一个 Wasm 字节码模块的执行实例:
- 通过
vm_id字段可以为同一份字节码创建多个 VM,每个 VM 拥有独立的 per-VM 内存; - 反过来,同一个 VM 也可以容纳针对不同扩展(插件)的多个实现,供不同 context 使用。VM 共享 可以优化运行时开销、减小字节码总二进制体积,因为多个扩展可以共享同一份代码。
Plugin(插件) 是连接 context 与 VM 的纽带。它指定:
- 该 context 使用哪个 Wasm VM;
- 在 VM 执行实例中调用哪个扩展(通过 PluginConfig.root_id 字段值标识);
- 在 xDS 更新时,如何把配置调用路由到对应扩展。
在源码层面,plugin.h 中的 Plugin 类把 PluginConfig 的 name、root_id、vm_id、runtime、configuration 等字段组装成 proxy_wasm::PluginBase,并维护 Envoy 侧的 WasmConfig(含允许的能力列表 allowed_capabilities_ 与注入的环境变量 envs_);wasm.h 中的 Wasm 类则通过 getRootContext 按插件找到对应的 root context,实现"Plugin → VM → Context"的寻址链路。
三者的协作关系
PluginConfig (name, root_id, vm_config, configuration)
│
▼
Plugin ──► 选择/启动 VM (按 vm_id + 代码哈希 共享)
│
▼
root context (ID=0, 配置回调) ──► per-request context (每个请求一个)
四、过滤器执行模型:主线程加载、工作线程内联执行
运行时的执行模型是理解性能特征的核心:
-
运行时:Wasm 模块在工作线程上内联执行(inline),通过 ABI 定义的一系列流式回调(stream callbacks)与 Envoy 交互。工作线程彼此独立,不共享 Wasm 执行实例及其运行时内存。异步或阻塞操作被委托给 Envoy 执行,完成后再由 Envoy 在插件上调用独立的完成回调(completion callback)。
-
配置时:Wasm 模块在主线程(main thread)上加载到 Wasm 引擎中。对于"模块二进制 ×
vm_id"的每种组合,会派生一个独立的 Wasm 执行实例。该实例通过回调接收每个插件的 Wasm 配置——在包含该 Wasm 过滤器的每个 xDS listener 上,这个回调可能被反复触发。如果回调接受配置,主执行实例会被克隆到每个工作线程。 -
与普通过滤器的关键区别:Wasm 过滤器的配置模型是跨 xDS listener 共享的。普通 HTTP 过滤器为每个 xDS listener 独立实例化,而 xDS listener 们在 xDS 更新期间共享同一个主 Wasm 执行实例。这正体现了上一节"VM 共享降低开销"的设计意图。
从源码看,wasm.h 中的 WasmHandle 实现了 ThreadLocal::ThreadLocalObject,getOrCreateThreadLocalPlugin 负责为每个工作线程创建/获取线程局部插件句柄;PluginHandleSharedPtrThreadLocal 作为 ThreadLocalObject 存储了每线程的插件句柄与最近加载时间戳(last_load),这与文档描述的"主实例克隆到每个 worker 线程"的执行模型一一对应。
五、Envoy Attributes:通过 proxy_get_property 暴露宿主属性
Wasm ABI 通过专用的 proxy_get_property 接口桩向插件暴露 Envoy 特有的宿主属性。这些就是 Envoy 标准的 Attributes,返回值按照类型进行二进制序列化。
Envoys 的属性命名采用点分隔路径(如 request.path),类型固定(string、int 等),且可能因上下文而存在或缺失。在 Wasm 扩展中,属性值按类型序列化:
- 字符串(
string)与字节(bytes)原样传递; - 整数(
int/uint)按 64 位直接传递; - 时间戳与时长(
timestamp/duration)近似为纳秒; - 结构化值递归转换为一组键值对。
常用的请求/响应/连接属性包括:
- 请求属性(仅 HTTP 过滤器可用):
request.path、request.url_path、request.host、request.scheme、request.method、request.headers、request.time、request.id、request.protocol等;请求完成后还有request.duration、request.size、request.total_size; - 响应属性(仅请求完成后可用):
response.code、response.code_details、response.flags、response.headers、response.size、response.total_size、response.backend_latency等; - 连接属性:
source.address、source.port、destination.address、destination.port、connection.id、connection.mtls、connection.tls_version等。
这些属性同样被 CEL 运行时(如 RBAC 过滤器)使用,因此基于属性的策略(RBAC)与基于 Wasm 的扩展可以共享同一套属性语义。
六、Foreign functions:超越 ABI 的宿主扩展能力
Envoy 在 Proxy-Wasm ABI 之上,通过 proxy_call_foreign_function 二进制接口提供了额外功能。从 foreign.cc 的实现看,这些功能通过 proxy_wasm::RegisterForeignFunction 注册:
| 函数名 | 功能 | 源码佐证 |
|---|---|---|
sign |
创建密码学签名 | 通过 importPrivateKey 从 PEM/DER 格式导入私钥(见 foreign.cc 中的 SignArguments 处理) |
verify_signature |
验证密码学签名 | 通过 importPublicKey 从 PEM/DER 导入公钥(VerifySignatureArguments) |
compress |
应用 zlib 压缩 | foreign.cc 直接引入 zlib.h |
uncompress |
应用 zlib 解压 | 同上 |
declare_property |
创建带类型信息的占位 filter state 对象 | 使用 ext/declare_property.pb.h 消息 |
set_envoy_filter_state |
设置 filter state 对象 | 使用 ext/set_envoy_filter_state.pb.h 消息 |
clear_route_cache |
更新已选中的路由 | — |
expr_create |
编译 CEL 表达式供后续求值 | 受 WASM_USE_CEL_PARSER 编译开关保护,依赖 eval/public/cel_expr_builder_factory.h 等 CEL 库 |
expr_evalute |
求值已编译的 CEL 表达式 | 同上(注意原文档中此名称为 expr_evalute,为拼写沿用) |
expr_delete |
删除已编译的 CEL 表达式 | 同上 |
这些函数的正确性有对应的单元测试验证。在 test/extensions/common/wasm/wasm_test.cc 中:
Foreign测试用例验证了compress/uncompress行为:对 2000 字节数据压缩后体积约 2x 字节(测试注释说明zlib与zlib-ng的压缩大小略有差异,因此使用正则匹配"compress 2000 -> 2[0-9]");OnForeign测试用例验证了on_foreign_function回调,以7、13两个参数调用,期望插件输出on_foreign_function 7 13。
这些测试覆盖了 V8 等真实 Wasm 运行时(加载 test_cpp.wasm)与 Null VM(插件名为 CommonWasmTestCpp)两种路径,印证了 foreign functions 在真实字节码与本地编译两种模式下行为一致。
七、Wasm runtime:V8、WAMR、Wasmtime 与 Null VM
Envoy Wasm 可以通过 VmConfig.runtime 字段配置使用多种 Wasm 运行时实现,只要该运行时包含在 Envoy 发行版中即可:
- V8(
envoy.wasm.runtime.v8):基于 Google V8 的 WebAssembly 运行时,是官方构建中默认优先启用的引擎; - WAMR(
envoy.wasm.runtime.wamr):基于 bytecodealliance wasm-micro-runtime 的运行时,官方构建默认不启用; - Wasmtime(
envoy.wasm.runtime.wasmtime):基于 Bytecode Alliance Wasmtime 的运行时,官方构建默认不启用; - Null VM(
envoy.wasm.runtime.null):特殊的伪 Wasm 运行时,Wasm 插件代码被编译为原生(非 Wasm)代码并静态链接进 Envoy 二进制,因此没有沙箱隔离,主要用于测试与性能对比。
从 wasm.proto 的注释可以确认运行时选择规则:runtime 默认为 Envoy 构建时可用的第一个 Wasm 引擎,搜索优先级为 v8 → wasmtime → wamr。
源码层面,各运行时以扩展工厂形式注册。例如 source/extensions/wasm_runtime/v8/config.cc 中:
V8RuntimeFactory的createWasmVm()返回proxy_wasm::createV8Vm();- 工厂名称为
envoy.wasm.runtime.v8; - 通过
#if defined(PROXY_WASM_HAS_RUNTIME_V8)条件编译,仅在构建时包含 V8 的情况下执行REGISTER_FACTORY注册。
类似地,source/extensions/wasm_runtime 目录下还有 wamr/config.cc、wasmtime/config.cc 与 null/config.cc。工厂的统一基类在 wasm_runtime_factory.h 中定义,类别(category)为 envoy.wasm.runtime。
Null VM 的使用方式:当使用 Null VM 时,VmConfig.code 字段的 inline_string 中应填写已静态链接进 Envoy 二进制的插件注册名(如测试中的 CommonWasmTestCpp),而非字节码文件。由于代码直接链接进二进制,它不受沙箱保护,仅应在可信环境中使用。
八、配置实战:VmConfig 与 PluginConfig 字段详解
Wasm 的配置入口是 envoy/extensions/wasm/v3/wasm.proto 中定义的 VmConfig 与 PluginConfig。下面结合字段注释逐一展开。
VmConfig:VM 级配置
| 字段 | 类型 | 说明 |
|---|---|---|
vm_id |
string | 与 wasm 代码哈希(或 Null VM 插件注册名)共同决定使用哪个 VM。所有使用相同 vm_id 与代码的插件共享同一个 VM,可留空。共享 VM 可降低内存占用、便于数据共享,但也可能带来安全影响 |
runtime |
string | Wasm 运行时类型,默认取构建时第一个可用引擎(v8 → wasmtime → wamr) |
code |
config.core.v3.AsyncDataSource | 要执行的 Wasm 代码,支持本地文件、内联字符串或远程拉取(异步数据源) |
configuration |
google.protobuf.Any | 新 VM 初始化(proxy_on_start)时使用的 Wasm 配置。Struct 会序列化为 JSON 后传给插件;BytesValue 与 StringValue 去掉包装直接传递 |
allow_precompiled |
bool | 是否允许 wasm 文件包含预编译代码。⚠️ 预编译代码不会被校验,仅应对可信来源开启 |
nack_on_code_cache_miss |
bool | 为 true 且代码需远程拉取但缓存未命中时:NACK 配置更新并在后台拉取填充缓存;否则异步拉取代码并进入 warming 状态 |
environment_variables |
EnvironmentVariables | 注入 VM 的环境变量,通过 WASI 的 environ_get / environ_get_sizes 系统调用可见(通常由语言标准库隐式调用)。存在键空间冲突时 Envoy 会拒绝配置 |
capability_restriction_config |
CapabilityRestrictionConfig | 限制模块可用的 Proxy-Wasm 能力,按名称映射 allowed_capabilities。限制在 VM 创建时应用,被该 VM 内所有插件共享,属于 VM 属性而非单个插件属性 |
EnvironmentVariables 支持两类注入:host_env_keys(从 Envoy 自身环境变量按 key 透传,key 不存在则忽略)与 key_values(显式 KEY=VALUE 键值对)。
CapabilityRestrictionConfig.allowed_capabilities 的能力名称取自 Proxy-Wasm ABI 规范,此外还实现了以下 WASI 能力:fd_write、fd_read、fd_seek、fd_close、fd_fdstat_get、environ_get、environ_sizes_get、args_get、args_sizes_get、proc_exit、clock_time_get、random_get。每个能力映射到的 SanitizationConfig(输入清洗)目前尚未实现,应保持为空。
PluginConfig:插件级配置
| 字段 | 类型 | 说明 |
|---|---|---|
name |
string | VM 内过滤器/服务的唯一名称。当多个过滤器/服务由相同 vm_id + root_id 处理时,用于区分及日志/调试定位 |
root_id |
string | VM 内一组共享 RootContext 与 Context 的过滤器/服务的唯一 ID。留空时,所有相同 vm_id 下 root_id 为空的过滤器/服务将共享 Context |
vm_config |
VmConfig(oneof vm) | 查找或启动 VM 的配置 |
configuration |
google.protobuf.Any | 用于配置/重配置插件(proxy_on_configure),序列化规则同 VmConfig.configuration |
fail_open |
bool | 已废弃(3.0 移除),改用 failure_policy。VM 发生致命错误(异常、abort()、on_start/on_configure 返回 false)时,若为 true 则绕过过滤器放行 |
failure_policy |
FailurePolicy | 插件失败策略(见下文) |
reload_config |
ReloadConfig | 仅当 failure_policy 为 FAIL_RELOAD 时生效,配置 VM 失败重载的退避策略 |
capability_restriction_config |
CapabilityRestrictionConfig | 已废弃(3.0 移除),能力限制属于 VM 级属性,应配置在 vm_config 上;若此处设置而 vm_config 未设置,会用于填充后者 |
allow_on_headers_stop_iteration |
google.protobuf.BoolValue | 是否允许插件的 onRequestHeaders / onResponseHeaders 回调返回 FilterHeadersStatus::StopIteration |
FailurePolicy:VM 失败时的处置策略
wasm.proto 中定义了 FailurePolicy 枚举,在 VM 发生致命错误(如异常、abort())时生效:
- UNSPECIFIED(0):未指定,使用默认策略,即
FAIL_CLOSED; - FAIL_RELOAD(1):VM 失败时为新请求创建新的插件实例。注意仅对
proxy_wasm::FailState::RuntimeError生效,其余失败回退到FAIL_CLOSED; - FAIL_CLOSED(2):与该 VM 关联的所有插件返回 HTTP 503 错误(默认行为,失败关闭);
- FAIL_OPEN(3):忽略与该 VM 关联的所有插件,过滤器链继续执行,适用于插件可选的场景(失败开放)。
ReloadConfig 提供 backoff(config.core.v3.BackoffStrategy)字段配置失败重载的退避策略,未指定时默认使用 1s 基础间隔。
参考配置骨架
以下是一个 HTTP 层 Wasm 过滤器的最小配置骨架(字段与 PluginConfig 对应,具体键名以实际 proto 为准):
http_filters:
- name: envoy.filters.http.wasm
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
config:
name: my_wasm_plugin
root_id: my_root
vm_config:
vm_id: my_vm
runtime: envoy.wasm.runtime.v8
code:
local:
filename: /path/to/plugin.wasm
allow_precompiled: false
configuration:
"@type": type.googleapis.com/google.protobuf.StringValue
value: "plugin config string"
failure_policy: FAIL_CLOSED
完整示例可参考 Wasm 过滤器配置文档 中从本地磁盘加载 Wasm 二进制的配置片段(该文档同时展示了下游与上游两种过滤器配置)。注意该过滤器目前不支持 Windows,且处于实验性阶段(alpha)。
九、小结
Envoy 的 Wasm 支持以 Proxy-Wasm ABI 0.2.1 为契约,把 C++/Rust 等语言编写的扩展编译成可移植字节码,并能在 HTTP 过滤器、网络过滤器、StatsSink、AccessLogger 与后台服务五个扩展点上运行。其核心设计可以概括为:
- 三层抽象:Context(接口实现)、Plugin(配置与路由纽带)、VM(隔离的执行实例,可按
vm_id共享); - 两阶段执行模型:主线程加载与配置回调 → 接受配置后克隆主实例到各工作线程,运行时内联执行流式回调,阻塞操作委托 Envoy 并在完成回调中收尾;
- 宿主能力扩展:通过
proxy_get_property暴露标准 Attributes,通过proxy_call_foreign_function提供签名、压缩、CEL 表达式与 filter state 等十项增强功能; - 多运行时可选:V8(默认优先)、Wasmtime、WAMR 以及无沙箱的 Null VM,均以
envoy.wasm.runtime.*扩展工厂注册; - 精细化失败控制:
FailurePolicy提供FAIL_CLOSED/FAIL_OPEN/FAIL_RELOAD三级策略,配合能力限制与重载退避,让插件故障可控。
无论是出于性能对比、功能扩展还是安全加固,理解上述模型都能帮助你更准确地判断 Wasm 插件在 Envoy 中的行为边界与配置取舍。更深入的 API 字段细节,可继续查阅 wasm.proto 及对应扩展点(HTTP、Network、StatsSink、AccessLog、Bootstrap)的 proto 与源码实现。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.24 K638- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python670
SlideSCIPPT插件,支持素材库、AI助手、一键添加图片标题,复制粘贴位置、一键图片对齐、一键插入Markdown(加粗、超链接等行内样式、代码块、LaTeX等块级样式)、便捷导出图片!C#230
hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程Python52874
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go22545
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java36351