Skip to main content

重磅功能!Apache APISIX 拥抱 WASM 生态

· One min read

在即将发布的 Apache APISIX 版本(2.11.0)中将会新增对于 WASM 的支持!通过阅读本文你将了解到 Apache APISIX 如何从 0 到 1 部署这项功能的支持与开发。

在即将发布的 Apache APISIX 版本(2.11.0)中,我们新增了对于 WASM 的支持!现在开发者除了可以使用 Lua、Java、Go、Python、JavaScript 等多种编程语言开发 APISIX 的插件之外,也可以用 WASM 来开发插件。

拥抱 WASM 生态

WASM 全称 WebAssembly,与上述具体编程语言运行时的不同之处在于,它是一套字节码标准,专门设计成可以在宿主环境中嵌套使用。

如果某种编程语言提供编译成 WASM 字节码的功能,就可以把该语言的应用编译成 WASM 字节码,运行在某个支持 WASM 的宿主环境中。

听起来,是不是只要某个宿主环境支持 WASM,就能像操作系统一样运行任意应用呢?

但其实这里有个限制,就像操作系统需要实现特定标准的 syscall 一样,要想运行特定的应用,也需要实现该应用所需的 API。

以 JavaScript 为例,虽然同样是 JavaScript 代码,但是针对浏览器写的 JS 模块不能直接用在 npm 包里面,因为两个的 API 不一样。

所以仅仅把 WASM 放到 Apache APISIX 里面并行不通,要想让开发者在 Apache APISIX 上运行 WASM,我们还需要提供一套专门的 API。

为什么选择 Proxy WASM#

对于如何提供这套 API,我们曾经权衡过两套方案:

  1. 参考 lua-nginx-module 的接口,实现对应的 WASM 版 API
  2. 实现 Proxy WASM 这一套标准

Proxy WASM 是 Envoy 的 WASM API 标准。所以上述问题其实等价于,我们是自己搞一套 API 标准还是复用 Envoy 已有标准呢?

WASM API 标准可以拆成两个方面来看:

1. Host,负责提供 API 的实现2. SDK,要想在不同的编程语言里面调用提供的 API,需要使用该语言来实现一套胶水层

如果我们遵循 Envoy 的标准,优势在于可以复用 Envoy 现有的 WASM SDK(Proxy WASM SDK),而不足之处在于这套标准是 Envoy 结合自己情况制定的,如果我们跟着实现,没有自己量身定制那么轻松。

经过社区的讨论后,我们最终决定采用 Proxy WASM 标准。「做难且正确的事」,实现 Proxy WASM 自然是难的事,但我们相信这是正确的事,通过社区的合作和共建,可以构建更加繁荣的生态。

如何在 Apache APISIX 中使用 WASM#

Apache APISIX 目前已初步支持 WASM,可以使用 WASM 来编写 fault-injection 插件的部分功能。感兴趣的读者可以在本月底的 Apache APISIX 2.11.0 版本中尝尝鲜,敬请期待!

下面我们将结合 proxy-wasm-go-sdk 来讲讲怎么用 WASM 实现注入自定义响应的功能。

步骤一:基于 proxy-wasm-go-sdk 编写代码#

实现代码(包含 go.mod 和其他)具体细节可点击此处查阅。

这里需要解释下,虽然 proxy-wasm-go-sdk 这个项目带了 Go 的名字,但它其实用的是 tinygo 而不是原生的 Go。因为原生的 Go 在支持 WASI(你可以认为它是非浏览器的 WASM 运行时接口)时会有一些问题,详情可点击此处查阅。

步骤二:构建对应的 WASM 文件#

tinygo build -o ./fault-injection/main.go.wasm -scheduler=none -target=wasi ./fault-injection/main.go

步骤三:在 Apache APISIX 的 config.yaml 引用该文件#

apisix:        ...wasm:    plugins:        - name: wasm_fault_injection          priority: 7997          file: t/wasm/fault-injection/main.go.wasm

通过以上操作,你可以像用 Lua 插件一样用这个 WASM 插件,比如:

uri: "/wasm"plugins:  wasm_fault_injection:    conf: '{"body":"hello world", "http_status":200}'upstream:  type: roundrobin  nodes:    127.0.0.1:1980: 1

注意 WASM 插件的配置都是 conf 字段下面的一条字符串,由对应的插件自己去做解析。

横向测评——条条大道通罗马#

Apache APISIX 发展到现在,已经有三种编写插件的方式:

  1. 原生的 Lua way,跑在 APISIX 里面
  2. 多种语言的外部插件 runner,插件逻辑跑在 APISIX 外面
  3. 把多种语言编译成 WASM,依然跑在 APISIX 里面

APISIX 生态支持

这三种方式在诸如生态、成熟度等各个方面都差异很大。正巧我们都可以用它们来实现 fault-injection,所以可以比比看。

步骤一:配置路由#

Lua way 的 fault-injection,自然是使用内置的 fault-injection 插件。Runner way 的 fault-injection 实现具体可点击此处查阅。

接下来让我们分别给它们配置不同的路由:

---uri: "/wasm"plugins:  wasm_fault_injection:    conf: '{"body":"hello world", "http_status":200}'upstream:  type: roundrobin  nodes:    127.0.0.1:1980: 1---plugins:  ext-plugin-pre-req:    conf:    - name: fault-injection      value: '{"body":"hello world", "http_status":200}'upstream:  nodes:    127.0.0.1:1980: 1  type: roundrobinuri: /ext-plugin---plugins:  fault-injection:    abort:      body: hello world      http_status: 200upstream:  nodes:    127.0.0.1:1980: 1  type: roundrobinuri: /fault-injection

步骤二:实际压测#

接下来试着用 wrk 进行压测,具体数据对比如下:

¥ wrk -d 30 -t 5 -c 50 http://127.0.0.1:9080/wasm | Running 30s test @ http://127.0.0.1:9080/wasm | 5 threads and 50 connections
Thread StatsLatencyReq/Sec
Avg66.17ms7.01k
Sdev226.42ms3.09k
Max1.99s33.97k
+/- Stdev91.89%82.28%
Request details650497 requests in 36.33s, 119.70MB read
Socket errorsconnect 0, read 0, write 0, timeout 63
Request/sec17903.17
Transfer/sec3.29MB
¥ wrk -d 30 -t 5 -c 50 http://127.0.0.1:9080/ext-plugin | Running 30s test @ http://127.0.0.1:9080/ext-plugin | 5 threads and 50 connections
Thread StatsLatencyReq/Sec
Avg95.69ms3.23k
Sdev229.09ms1.47k
Max1.70s15.18k
+/- Stdev87.37%83.89%
Request details362151 requests in 30.50s, 66.64MB read
Socket errorsconnect 0, read 0, write 0, timeout 17
Request/sec11873.12
Transfer/sec2.18MB
¥ wrk -d 30 -t 5 -c 50 http://127.0.0.1:9080/fault-injection | Running 30s test @ http://127.0.0.1:9080/fault-injection | 5 threads and 50 connections
Thread StatsLatencyReq/Sec
Avg86.91ms7.90k
Sdev263.14ms2.04k
Max1.91s15.60k
+/- Stdev90.73%81.97%
Request details974326 requests in 30.07s, 179.29MB read
Socket errorsconnect 0, read 0, write 0, timeout 8
Request/sec32405.28
Transfer/sec5.96MB

从上述结果可以看到,WASM 版本的性能介于外部插件和原生 Lua 之间。

WASM 版本的性能之所以比外部插件好那么多,是因为 fault-injection 功能简单,所以外部插件 RPC 带来的性能损耗过于明显。考虑到我们还没有对 WASM 实现做任何优化,这种情况已经让我们感到满意了。

而 WASM 的另一个好处,就是让我们一下子拥有多语言的支持(这也托了 Proxy WASM SDK 的福)。具体细节可参考下方文档:

道路曲折,但前途光明#

说了这么多 WASM 的好处,是不是有点心动呢?但它目前并非是一个完美的解决方案, WASM/Proxy WASM 还是有一些不够成熟的地方。比如:

  • 编程语言支持待完善:原生 Go 的 WASM 支持,主要是基于浏览器环境的,所以我们不得不用 tinygo 来实现。但是 tinygo 作为一个年轻的项目,还是有不少局限性。
  • Proxy WASM 生态有待成熟:AssemblyScript 版本的 fault injection 实现,并没有 JSON decode 的部分。这是因为 AssemblyScript SDK 是基于 AssemblyScript 0.14.x 版本实现的,而几个开源的 AssemblyScript JSON 库都是针对高版本 AssemblyScript 实现的,没办法用在较为陈旧的 AssemblyScript 0.14 上。
  • WASM 没有内置协程:WASM 目前尚未内置协程,所以没办法很好地被宿主的调度系统给调度起来。

虽然上面列举了几点不足之处,但是我们相信这个技术栈的前景是光明的:

  1. 包括 Apache APISIX 和 Envoy 等开源项目对于 WASM 都很看重,有许多初创公司和大企业为 WASM 生态添砖加瓦,这意味着诸如 AssemblyScript SDK 停滞不前这样的困难,只会是暂时。长久看,Proxy WASM 的生态会枝繁叶茂。
  2. WASM 作为 serverless 和 edge computing 的宠儿,有着光明的未来。在众多实际场景的落地和优化,会更快的解决技术上的不足。

写在最后#

Apache APISIX 是个紧跟技术潮流的项目,“好风凭借力,送我上青天”,Apache APISIX 支持 WASM 是个长期的过程。

“千里之行,始于足下”,Apache APISIX 为了支持 WASM,已经发起了 wasm-nginx-module 这个开源项目。感兴趣的读者可以关注该项目的进展,“独行者速,众行者远”,期待你的加入,一起创造世界顶级项目。