WebAssembly 助力云原生:APISIX 如何借助 Wasm 插件实现扩展功能?
本文将介绍 Wasm,以及 Apache APISIX 如何实现 Wasm 功能。
作者朱欣欣,API7.ai 技术工程师
什么是 Wasm
Wasm 是 WebAssembly 的缩写。WebAssembly/Wasm 是一个基于堆栈的虚拟机设计的指令格式。 在 Wasm 未出现之前,浏览器中只能支持运行 Javascript 语言。当 Wasm 出现之后,使得高级语言例如 C/C++/Golang 能够在浏览器中运行。当前,主流的浏览器包括 Chrome、Firefox、Safari 等浏览器都已完成对 Wasm 的支持。并且得益于 WASI 项目的推进,服务端也已经能够支持运行 Wasm 指令。 如今在网关侧,Apache APISIX 也已完成对 Wasm 的支持,开发者可以通过高级语言 C/C++/Go/Rust 并按照 proxy-wasm 规范来完成 Wasm 插件的开发。
为什么 APISIX 要支持 Wasm 插件
相比较原生的 Lua 插件,Wasm 插件存在如下优势:
可扩展性:APISIX 通过支持 Wasm,我们可以结合 proxy-wasm 提供的 SDK,使用 C++/Golang/Rust 等语言进行插件开发。由于高级语言往往拥有更加丰富的生态,所以我们可以依托于这些生态来实现支持更多功能丰富的插件。
安全性:由于 APISIX 和 Wasm 之前的调用依托于 proxy-wasm 提供的 ABI(二进制应用接口),这部分的访问调用更为安全。Wasm 插件只允许对请求进行特定的修改。另外,由于 Wasm 插件运行在特定的 VM 中,所以即使插件运行出现崩溃也不会影响 APISIX 主进程的运行。
APISIX 如何支持 WASM
了解完 Wasm,现在我们将从自顶向下的角度来看 APISIX 是如何支持 Wasm 插件功能的。
APISIX Wasm 插件
在 APISIX 中,我们可以使用高级语言 C/C++/Go/Rust 来按照 proxy-wasm 规范以及对应的 SDK 来编写插件。
proxy-wasm 是 Envoy 推出的在 L4/L7 代理之间的 ABI 的规范与标准。 在该规范中定义了包含内存管理、四层代理、七层代理扩展等 ABI。 例如在七层代理中,proxy-wasm 规范定义了proxy_on_http_request_headers,proxy_on_http_request_body,proxy_on_http_request_trailers,proxy_on_http_response_headers 等 ABI,使得模块能够在各个阶段对请求内容进行获取与修改。
例如,我们使用 Golang 结合 proxy-wasm-go-sdk, 编写如下插件:
proxy-wasm-go-sdk 正是上述 proxy-wasm 规范的 SDK,它帮助开发者更好的使用 Golang 编写 proxy-wasm 插件。 不过需要注意的是,由于原生 Golang 在支持 WASI 时存在一些问题,因此该 SDK 基于 TinyGo 实现,更多内容可以点击进行查看。
该插件的主要功能用于将 HTTP 修改请求的响应状态码与响应体,引用自 APISIX 链接,
... func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus { data, err := proxywasm.GetPluginConfiguration() if err != nil { proxywasm.LogErrorf("error reading plugin configuration: %v", err) return types.OnPluginStartStatusFailed } var p fastjson.Parser v, err := p.ParseBytes(data) if err != nil { proxywasm.LogErrorf("error decoding plugin configuration: %v", err) return types.OnPluginStartStatusFailed } ctx.Body = v.GetStringBytes("body") ctx.HttpStatus = uint32(v.GetUint("http_status")) if v.Exists("percentage") { ctx.Percentage = v.GetInt("percentage") } else { ctx.Percentage = 100 } // schema check if ctx.HttpStatus < 200 { proxywasm.LogError("bad http_status") return types.OnPluginStartStatusFailed } if ctx.Percentage < 0 || ctx.Percentage > 100 { proxywasm.LogError("bad percentage") return types.OnPluginStartStatusFailed } return types.OnPluginStartStatusOK } func (ctx *httpLifecycle) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action { plugin := ctx.parent if !sampleHit(plugin.Percentage) { return types.ActionContinue } err := proxywasm.SendHttpResponse(plugin.HttpStatus, nil, plugin.Body, -1) if err != nil { proxywasm.LogErrorf("failed to send local response: %v", err) return types.ActionContinue } return types.ActionPause } ...
之后,我们通过 tiny-go 将上述的 Golang 代码编译生成 .wasm
文件
tinygo build -o wasm_fault_injection.go.wasm -scheduler=none -target=wasi ./main.go
完成编译之后,我们得到了 fault_injection.go.wasm
文件
如果对 wasm 文件内容感兴趣的话,我们可以使用 wasm-tool 工具来查看该 wasm 文件的具体内容。
wasm-tools dump hello.go.wasm
将 wasm_fault_injection.go.wasm
配置到 APISIX 到 config.yaml
,并将该插件命名为 wasm_fault_injection。
apisix: ... wasm: plugins: - name: wasm_fault_injection priority: 7997 file: wasm_fault_injection.go.wasm
之后,我们启动 APISIX ,并创建一条路由引用该 Wasm 插件:
curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '{ "uri":"/*", "upstream":{ "type":"roundrobin", "timeout":{ "connect":1, "read":1, "send":1 }, "nodes":{ "httpbin.org:80":1 } }, "plugins":{ "wasm_fault_injection":{ "conf":"{\"http_status\":200, \"body\":\"Hello WebAssembly!\n\"}" } }, "name":"wasm_fault_injection" }'
进行访问测试,发现响应体已被修改为 "Hello WebAssembly",由此 Wasm 插件已经生效。
curl 127.0.0.1:9080/get -v * Trying 127.0.0.1:9080... * Connected to 127.0.0.1 (127.0.0.1) port 9080 (#0) > GET /get HTTP/1.1 > Host: 127.0.0.1:9080 > User-Agent: curl/7.81.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Date: Thu, 09 Feb 2023 07:46:50 GMT < Content-Type: text/plain; charset=utf-8 < Transfer-Encoding: chunked < Connection: keep-alive < Server: APISIX/3.1.0 < Hello WebAssembly!
Wasm-nginx-module
了解完 Apache APISIX 如何使用 Wasm 插件,现在我们更进一步来了解 "为什么我们能在 Wasm 插件中获取到请求的内容并修改请求?" 。 由于 APISIX 选用 Openresty 作为底层框架,因此 Wasm 插件中想要能够获取到请求内容和修改请求内容,就需要和 openResty 或者 NGINX 提供的 API 进行交互。wasm-nginx-module
正是提供了这部分能力。
wasm-nginx-module 是由 API7 研发的支持 Wasm 的 NGINX 模块。 该模块尝试在 NGINX 的基础上实现 proxy-wasm-abi,并且向上封装了 Lua API,使得我们能够在 Lua 层面完成 proxy-wasm-abi 的调用。 更多内容可参考 wasm-nginx-module。
例如,当我们的 APISIX 运行到 "access" 阶段时,会调用 wasm-nginx-module
中提供的 Lua 方法 on_http_request_headers。
-- apisix/wasm.lua ... local ok, err = wasm.on_http_request_headers(plugin_ctx) if not ok then core.log.error(name, ": failed to run wasm plugin: ", err) return 503 end end ...
之后在该方法中,将调用 wasm-nginx-module
中 ngx_http_wasm_on_http
方法,
ngx_int_t ngx_http_wasm_on_http(ngx_http_wasm_plugin_ctx_t *hwp_ctx, ngx_http_request_t *r, ngx_http_wasm_phase_t type, const u_char *body, size_t size, int end_of_body) { ... ctx = ngx_http_wasm_get_module_ctx(r); if (type == HTTP_REQUEST_HEADERS) { cb_name = &proxy_on_request_headers; } else if (type == HTTP_REQUEST_BODY) { cb_name = &proxy_on_request_body; } else if (type == HTTP_RESPONSE_HEADERS) { cb_name = &proxy_on_response_headers; } else { cb_name = &proxy_on_response_body; } if (type == HTTP_REQUEST_HEADERS || type == HTTP_RESPONSE_HEADERS) { if (hwp_ctx->hw_plugin->abi_version == PROXY_WASM_ABI_VER_010) { rc = ngx_wasm_vm->call(hwp_ctx->hw_plugin->plugin, cb_name, true, NGX_WASM_PARAM_I32_I32, http_ctx->id, 0); } else { rc = ngx_wasm_vm->call(hwp_ctx->hw_plugin->plugin, cb_name, true, NGX_WASM_PARAM_I32_I32_I32, http_ctx->id, 0, 1); } } else { rc = ngx_wasm_vm->call(hwp_ctx->hw_plugin->plugin, cb_name, true, NGX_WASM_PARAM_I32_I32_I32, http_ctx->id, size, end_of_body); } ... }
在 wasm-nginx-module
中,我们将根据不同的阶段,设置 cb_name
,例如:HTTP_REQUEST_HEADERS
对应 proxy_on_request_headers
,之后将在 ngx_wasm_vm->call
中调用 vm 中的方法也就是我们在上文中提到的 wasm 插件 OnHttpRequestHeaders
的方法。
至此,整个 APISIX 调用 wasm 插件,运行 Golang 的调用链便梳理完成,调用链如下:
Wasm VM
Wasm VM 用于真正执行 Wasm 代码的虚拟机,在 wasm-nginx-module
中实现了对两种虚拟机 "wasmtime" 和 "wasmedge" 两种虚拟机,在 APISIX 中默认选择使用 "wasmtime" 作为 Wasm 代码的运行虚拟机。
Wasmtime Wasmtime 是由 bytecodealliance 开源的 WebAssembly 和 WASI 的小型高效运行时。它能够在 Web 外部运行 WebAssembly 代码,即可以用作命令行使用,也可以作为 WebAssembly 运行引擎嵌入到其他程序作为库使用。 Wasmedge Wasmedge 是为边缘计算优化的轻量级、高性能、可扩展的 WebAssembly (Wasm) 虚拟机,可用于云原生、边缘和去中心化的应用。
在 Wasm vm 中首先通过 load
方法将 .wasm
文件加载到内存,之后我们便可以通过 VM 的 call 方法来调用这些方法。VM 底层依托于 WASI 的接口实现,使得 Wasm 代码不仅能够运行在浏览器端,同时也支持能够在服务端进行。
总结
通过本文我们了解到 Wasm 是什么以及 APISIX 如何支持 Wasm 插件。APISIX 通过支持 Wasm 插件,不但可以扩充对多语言的支持,例如通过 C++, Rust, Golang, AssemblyScript 等进行插件开发,而且由于 WebAssembly 正在从浏览器走向云原生拥有了更加丰富的生态与使用场景,因此 APISIX 也可以借助 Wasm 完成在 API 网关侧更多的扩展功能,解决更多使用场景。
关于 API7.ai 与 APISIX
API7.ai(支流科技 )是一家提供 API 处理和分析的开源基础软件公司,于 2019 年开源了新一代云原生 API 网关 -- APISIX 并捐赠给 Apache 软件基金会。此后,API7.ai 一直积极投入支持 Apache APISIX 的开发、维护和社区运营。与千万贡献者、使用者、支持者一起做出世界级的开源项目,是 API7.ai 努力的目标。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
百度研发效能从度量到数字化蜕变之路
作者 |乌拉 导读 企业降本增效的诉求越发强烈,研发效能成为近来极为火爆的话题,本文从效能分析整体思路、实践案例、技术实现介绍了如何从效能度量逐步演变形成基于价值的数字化决策系统的过程,通过本文可以了解到: 1、研发效能的本质和数字化分析思路 2、以实际分析场景为例,了解如何通过数据构建问题分析模型,挖掘效能问题并驱动改进落地 3、搭建具备数字化智能诊断决策能力的数字化平台的技术实现方案 全文9164字,预计阅读时间23分钟。 01 前言 企业降本增效的诉求越发强烈,研发效能成为近来极为火爆的话题,效能数字化建设也成为很多公司度量效能现状,发现效能瓶颈的不二选择。关于研发效能度量百度已经深耕多年,从开始通过工程能力地图的形式驱动工程能力提升,到建立研发效能度量平台,通过在线化报表数据驱动各业务团队研发核心指标的提升。 △工程能力地图 △在线化度量报表 前期的效能分析基本分为两步开展: 定性分析:主要通过指标趋势分析等方式来了解效能变化情况,是提升了还是下降了,进而判断团队效能好坏。 定量分析:通过下钻、逻辑树、相关性分析等方式找到效能提升或者下降的原因,并根据原因制定改进措施。 可以看...
- 下一篇
围观 Jpom 支持使用第三方 OAuth2 认证用户
Jpom 介绍 📢 Jpom 是一款简而轻的低侵入式在线构建、自动部署、日常运维、项目运维监控软件。 来我们先看看距离上次将大版本 2.10.36 发布到现在 Jpom 又卷了多少个版本(偷偷告诉您算上 beta 版本已经有 15+ 😱) 又有哪些精彩的功能。 还有更多变动等您来发现(新增、优化、修复累积达 30+)。 近期主要功能变动 📋 从上次 3月20日大版本发布后 开启了 beta 计划 新增了文件管理中心 支持 OAuth2 第三方平台登录(Gitee、MaxKey、Github) 新增了证书统一管理 部分数据支持跨工作空间共享(仓库、服务端脚本、节点脚本) 优化部分页面布局(列表固定操作列、构建页面、docker 控制台页面) 精彩变动说明 📝 本文主要选取精彩的变动相关功能说明和截图示例,具体功能使用方式可以安装 Jpom 或者升级到最新版本来体验。 beta 计划 📬 考虑到 Jpom 近期在 2.10.x 版本为 3.x 版本的过渡版本,在过渡期间肯定有较多或者频繁更新,这样会给部分用户带来更新 OR 不更新的顾虑和纠结。 在大家一起讨论建议下我们在 2.1...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2全家桶,快速入门学习开发网站教程
- MySQL8.0.19开启GTID主从同步CentOS8
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS6,7,8上安装Nginx,支持https2.0的开启