第 92 篇
API 网关的 WebAssembly 插件:可移植性、隔离边界与 APISIX 限制
API7.ai
2026年10月29日
WebAssembly(Wasm)可以让平台团队以可移植二进制文件的方式分发网关扩展,不必每次改动都重新构建网关。但真正有用的契约并不是“编译一次,到处运行”,而是:编译到宿主支持的 Proxy-Wasm ABI,只使用目标网关已经实现的宿主调用,并针对该网关的具体版本与执行阶段完成验证。
Wasm 也能缩小扩展与宿主之间的接口,但这并不意味着不可信模块会自动变得无害。供应链控制、资源限制、发布隔离和故障恢复仍然是平台团队的责任。
核心要点
- Proxy-Wasm 兼容性由 ABI 和宿主能力共同决定,不能只看文件是不是
.wasm。 - 采用 Wasm 插件前,先确认回调阶段、请求/响应体访问方式与失败行为。
- 内存沙箱不等于进程隔离、租户隔离、资源隔离或供应链隔离。
- 在 Apache APISIX 3.19.0 中,Proxy-Wasm API 仅实现了一部分,而且所有 Wasm 插件运行在同一个 Wasm VM 中。
- 应把模块摘要、SDK 与 ABI、测试用例、网关版本、配置和回滚产物作为同一个发布单元锁定。
- 按网关实例或流量切片做金丝雀发布,避免故障扩展一次性消耗整个集群的错误预算。
明确可移植性契约
Proxy-Wasm 规定了 L4/L7 代理宿主与 Wasm 扩展之间的 ABI 和约定。语言 SDK 会把上层插件代码转换为跨越该 ABI 的调用;网关宿主则决定自己实现哪些 ABI 版本、回调、属性和宿主函数。
因此,兼容性至少有四层:
| 层级 | 需要回答的问题 |
|---|---|
| Wasm 二进制 | 所选运行时能否校验并实例化该模块? |
| Proxy-Wasm ABI | SDK 面向哪个 ABI 版本? |
| 宿主实现 | 当前网关版本实现了哪些回调、属性和宿主调用? |
| 网关语义 | 回调在哪个阶段执行,当时还能修改或拒绝什么? |
一个模块即使是合法的 Wasm,也可能因为宿主没有实现所需的 Proxy-Wasm 调用而失败。即使加载成功,也可能因为不同网关暴露的属性、缓冲规则或生命周期行为不同而产生差异。应为每种宿主维护兼容性测试套件,而不是从文件扩展名推断兼容性。
把扩展放到正确的请求生命周期阶段
1flowchart LR
2 C[客户端] --> R[网关 rewrite 阶段]
3 R --> A[网关 access 阶段]
4 A --> U[上游服务]
5 U --> H[响应 header_filter 阶段]
6 H --> B[响应 body_filter 阶段]
7 B --> C
8 W[Proxy-Wasm 模块] -. 在支持的回调中执行宿主调用 .-> R
9 W -. 在支持的回调中执行宿主调用 .-> A
10 W -. 在支持的回调中执行宿主调用 .-> H
11 W -. 在支持的回调中执行宿主调用 .-> B阶段会改变行为含义。请求重写可以在访问策略执行前规范路由输入;访问阶段回调可以在代理前拒绝请求;响应体回调则在上游已经返回响应后运行,此时可能已经来不及修改已提交的状态码。正文检查还可能引入仅处理请求头时没有的缓冲和复制成本。
对每个回调,都要记录:
- 模块可以读取哪些输入,以及输入由谁提供;
- 可以修改哪些字段;
- 响应是否已经开始;
- 正文大小与执行成本的上限;
- 遇到 trap、超时或无效结果时,客户端会收到什么;
- 平台会放行、拒绝,还是把扩展移出服务。
不要因为宿主暴露了某个请求头、属性或上下文值,就把它称为可信身份。下游策略能否依赖该值,仍取决于认证状态与值的来源。
理解 APISIX 3.19 的边界
Apache APISIX 3.19.0 支持使用 Proxy-Wasm SDK 编写的插件,但其文档也明确说明,目前只实现了少量 API。因此,兼容性必须在 APISIX 上实际证明,不能从另一个 Proxy-Wasm 宿主类推。
APISIX 文档中的编程模型包含多个作用域:
- 所有 Wasm 插件运行在同一个 Wasm VM 中;
- 每个插件拥有自己的
VMContext; - 每个配置了插件的路由或全局规则拥有一个
PluginContext; - 每个命中配置的 HTTP 请求拥有一个
HttpContext。
这些上下文用于组织生命周期与状态,并不是独立的操作系统进程或租户沙箱。Wasm 模块仍在网关数据面进程内运行,会占用请求路径上的延迟、CPU、内存或宿主调用容量。
APISIX 会把请求头回调映射到 access 或 rewrite 阶段。默认值是 access;选择 rewrite 会让回调更早执行。这两条路径可能看到不同的网关状态,也会影响后续策略读取到的修改结果。请求体和响应体回调不会自动执行:插件必须在对应的请求头或响应头回调中,设置文档规定的 wasm_process_req_body 或 wasm_process_resp_body 属性。
下面是 APISIX 3.19.0 的 config.yaml 配置片段,不是完整部署:
1wasm:
2 plugins:
3 - name: request_guard
4 priority: 7999
5 file: /opt/apisix/wasm/request-guard.wasm
6 http_request_phase: access当 http_request_phase: access 时(也是默认值),请求头回调在 access 阶段运行;当配置为 http_request_phase: rewrite 时,则在 rewrite 阶段运行。修改这个设置不只是优化时间点,还会改变策略顺序,因此必须与所有读取或修改同一请求的插件一起做回归测试。
路由通过 conf 传递插件上下文:
1{
2 "uri": "/orders/*",
3 "plugins": {
4 "request_guard": {
5 "conf": {
6 "policy": "orders-read-v3"
7 }
8 }
9 },
10 "upstream_id": "orders"
11}这只是配置片段,省略了模块、上游、Admin API 认证和测试服务。模块必须先校验 conf 的结构和边界,再使用它。控制面写入成功并不能证明每个字段都可以安全使用。
区分四类隔离
WebAssembly 安全模型 会把模块内存与宿主隔离,并要求模块通过嵌入环境提供的 API 与外部交互。这是有价值的故障约束,但生产隔离还有更多维度:
| 边界 | 解决的问题 | 运维侧仍需提供的控制 |
|---|---|---|
| 内存沙箱 | 防止直接任意访问宿主内存 | 运行时漏洞和不安全的宿主函数 |
| 能力边界 | 暴露哪些宿主调用与属性 | 最小权限宿主 API 设计和兼容性审查 |
| 资源边界 | CPU、内存、正文缓冲、旁路调用 | 强制限制、负载测试和过载行为 |
| 进程或租户边界 | 插件与租户之间的故障半径 | 必要时采用独立网关池或进程外执行 |
| 供应链边界 | 构建和批准了哪个二进制 | 可复现构建、签名/摘要、SBOM、扫描和来源证明 |
APISIX 的共享 VM 设计让最后一步验证尤其重要:不要宣称文档中的宿主模型并未提供的“每插件 VM 隔离”。如果租户之间互不信任,或系统要求严格的故障约束,独立数据面池可能是更合适的边界。
发布一个完整单元,而不是散落的二进制
应把以下产物一起版本化:
- 源代码提交和锁定的依赖;
- 语言 SDK 与 Proxy-Wasm ABI 版本;
- 编译器和构建参数;
- 模块摘要、签名或证明;
- 所需宿主调用与属性;
- APISIX 版本和运行时构建;
- 静态模块注册以及路由/全局规则配置;
- 一致性、负载、故障与回滚测试。
从运维方控制的只读路径加载模块,并在发布前校验摘要。路由应该引用已经批准的插件名称和有边界的配置,不应允许租户提供任意文件路径或可下载模块。
测试失败与回滚路径
测试用例不能只覆盖成功请求,还应包括:
- 插件配置缺失或格式错误;
- 回调 trap 或宿主调用返回无效结果;
- 达到允许的请求头和正文大小上限;
- 如果宿主支持旁路调用,则覆盖调用缓慢或失败;
- 配置启用后的第一个请求;
- 使用不同
HttpContext的并发请求; - 同一请求同时命中路由和全局规则;
- 响应已经开始后,正文阶段发生失败;
- Worker 重启、配置重载、金丝雀移除和回滚。
在同一工作负载下,对比扩展关闭与启用时的网关延迟和错误率。一个模块即使功能正确,只要消耗了请求路径的预算,也不适合直接晋级。
用明确的故障半径发布
优先采用独立的金丝雀网关池或受控流量切片。晋级门槛应包含请求成功率、延迟、trap、Worker 重启、内存增长、正文缓冲压力和应用正确性。旧模块与旧配置必须成对保留,避免回滚时把旧二进制与不兼容的新 conf 结构组合起来。
如果扩展负责执行安全策略,要明确它无法运行时的行为。放行可以保持可用性,却可能绕过控制;拒绝可以保留控制,却可能导致中断。应把这项决策写入威胁模型、告警和事件手册,而不是依赖偶然的运行时默认值。
WebAssembly 插件检查清单
- 模块由哪个 Proxy-Wasm ABI 与 SDK 版本生成?
- APISIX 3.19 是否实现了全部所需回调、属性和宿主调用?
- 请求回调是有意放在
access还是rewrite? - 是否确实需要正文回调,并已设置边界和显式启用?
- 每个身份或路由输入是否都能追溯到可信来源?
- 模块摘要、来源和依赖是否在加载前得到校验?
- CPU、内存、正文和旁路调用成本是否经过并发测试?
- 所需租户边界是否超出了 APISIX 文档中的共享 VM 模型?
- 是否测试了 trap、过载、首次请求、响应已开始和回滚路径?
- 运维人员能否在不重建网关、不破坏路由配置的情况下移除模块?
常见问题
Proxy-Wasm 插件可以在所有 API 网关之间移植吗?
不能。ABI 提升了可移植性,但目标宿主必须实现模块用到的回调、属性和宿主调用。每种网关产品和版本都需要单独测试。
Wasm 能让第三方插件安全地运行在生产环境中吗?
不能只靠 Wasm。内存沙箱降低了一类风险,但模块仍需要来源、能力、资源、租户、发布和恢复控制。
所有自定义网关插件都应该使用 Wasm 吗?
不应该。已有内置插件能完成需求时,优先使用内置插件。当语言与分发优势大于宿主 API 缺口和运行时成本时,再选择 Wasm;需要更强进程隔离或更丰富库生态时,可以考虑外部服务或插件运行器。
后续步骤
先把扩展映射到 API 网关请求生命周期,再通过 API 网关 DevSecOps 控制和安全的网关 CI/CD交付。