跳转到正文
API 网关指南
本文目录

第 92 篇

API 网关的 WebAssembly 插件:可移植性、隔离边界与 APISIX 限制

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 ABISDK 面向哪个 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 隔离”。如果租户之间互不信任,或系统要求严格的故障约束,独立数据面池可能是更合适的边界。

发布一个完整单元,而不是散落的二进制

应把以下产物一起版本化:

  1. 源代码提交和锁定的依赖;
  2. 语言 SDK 与 Proxy-Wasm ABI 版本;
  3. 编译器和构建参数;
  4. 模块摘要、签名或证明;
  5. 所需宿主调用与属性;
  6. APISIX 版本和运行时构建;
  7. 静态模块注册以及路由/全局规则配置;
  8. 一致性、负载、故障与回滚测试。

从运维方控制的只读路径加载模块,并在发布前校验摘要。路由应该引用已经批准的插件名称和有边界的配置,不应允许租户提供任意文件路径或可下载模块。

测试失败与回滚路径

测试用例不能只覆盖成功请求,还应包括:

  • 插件配置缺失或格式错误;
  • 回调 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交付。

编辑本篇文章

获取方案