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 插件的开发。

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 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-modulengx_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-call

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 努力的目标。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/7856.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Hadoop(伪分布式)+Spark(local模式)搭建Hadoop和Spark组合环境

一、安装Hadoop环境使用Ubuntu 14.04 64位 作为系统环境&#xff08;Ubuntu 12.04&#xff0c;Ubuntu16.04 也行&#xff0c;32位、64位均可&#xff09;&#xff0c;请自行安装系统。Hadoop版本: Hadoop 2.7.4创建hadoop用户如果你安装 Ubuntu 的时候不是用的 "hadoop&qu…

研究的艺术 (The craft of research) 读书笔记

前言 如果你对这篇文章感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 对于研究者而言&#xff0c;写作是一件很重要的事&#xff0c;好的写作不仅能让更多人愿意读&#xff0c;获得更大影响力&…

Windows系统配置SSH服务

1.安装OpenSSH 打开【设置】-【应用】 选择【管理可选功能】 点击【添加可选功能】 选择【OpenSSH 服务端】&#xff0c;切记不是【OpenSSH 客户端】&#xff08;如果安装一个不行&#xff0c;就都安装&#xff0c;我都安装了可以用&#xff09;&#xff0c;然后点击下载即可 …

ERP系统如何让项目管理更轻松?

项目管理是许多企业的首要任务&#xff0c;通常有一个ERP系统来协助他们。然而&#xff0c;一些企业仍然没有意识到&#xff0c;ERP解决方案可以使他们的项目管理更容易。项目管理需要有一个目标&#xff0c;并在你朝着完成项目前进的过程中控制变量&#xff0c;而ERP系统指导你…

成都北大青鸟怎么样?

对于任何一个培训机构的了解大概的流程是&#xff1a;听说名字——网上搜索&#xff0c;可以看到机构官网&#xff0c;也会看到机构广告&#xff0c;当然也会看到各种有好有坏的评论&#xff0c;到这里会对机构形成初印象&#xff1b;然后如果身边有培训出身的小伙伴会去询问对…

【C语言进阶:自定义类型详解】联合(共用体)

本节重点内容&#xff1a; 联合类型的定义联合的特点联合大小的计算⚡联合类型的定义 联合也是一种特殊的自定义类型这种类型定义的变量也包含一系列的成员&#xff0c;特征是这些成员公用同一块空间&#xff08;所以联合也叫共用体&#xff09;。 为了方便大家理解举个例子…

Java 网络编程之NIO(ByteBuffer)

在 Java NIO 中&#xff0c;ByteBuffer 是用于存储和传输数据的一种数据结构。它提供了高效的数据存储和读取能力&#xff0c;使得 Java NIO 能够高效地处理大量的数据输入输出。 ByteBuffer 的作用包括以下几个方面: 存储数据:ByteBuffer 可以存储任意长度的数据&#xff0c;…

Elasticsearch 8.X 如何基于用户指定 ID 顺序召回数据?

1、实战问题如何根据输入的id 的顺序输出结果&#xff0c;id 个数有500个&#xff0c;还有分页&#xff1f;问题来源&#xff1a;https://t.zsxq.com/0cdyq7tzr2、方案探讨2.1 Elasticsearch 默认排序机制在 Elasticsearch 中&#xff0c;如果未指定排序规则&#xff0c;检索结…

Linux下实现的 HTTP 服务器

项目功能&#xff1a;&#xff08;1&#xff09;能接收客户端的GET请求&#xff1b;&#xff08;2&#xff09;能够解析客户端的请求报文&#xff0c;根据客户端要求找到相应的资源&#xff1b;&#xff08;2&#xff09;能够回复http应答报文&#xff1b;&#xff08;3&#x…

数据结构和算法学习记录——设计循环队列(数组实现循环队列)核心思路、题解过程、完整题解

目录 题目描述 题目示例 核心思路 链表实现 数组实现 重点 题解过程 结构体类型定义 创建一个循环队列并初始化 判断循环队列为空或为满 入队列函数 出队列函数 取队头数据 取队尾数据 销毁循环队列 完整题解 题目来源&#xff1a;力扣 题目描述 设计你的…

Sentinel滑动时间窗限流算法原理及源码解析(下)

文章目录对统计数据如何使用获取之前统计好的数据对统计数据如何使用 流控快速失败 获取之前统计好的数据

SpringBoot 项目的创建与启动

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

iosApplePay

1、Apple Pay 接入工程 - 简书 // 设置币种、国家码及merchant标识符等基本信息 PKPaymentRequest *payRequest [[PKPaymentRequest alloc]init]; payRequest.countryCode "CN"; //国家代码 payRequest.currencyCode "CNY"; //RMB的币种代码 …

“被裁员之前,没得到任何风声,措手不及...” 一个在职6年测试工程师内心独白

前言 一个码农&#xff08;软件测试工程师&#xff09;的自白 小张&#xff1a; 我们用工作五年的积蓄&#xff0c;在这个一线城市买了房子&#xff0c;买了车子&#xff0c;然后领证。我也在6年前进入了一个很多人梦寐以求新的公司 码农的新生活开始了。在这家公司里&…

ChatGPT如何为企业提供帮助?

数字化转型是指利用技术来改变企业的运营方式并为客户提供价值&#xff0c;这不仅仅是关于如何采用新的技术或工具。要想取得成功&#xff0c;就需要从根本上改变文化和心态。 ChatGPT如何为企业提供帮助?从数据分析到知识管理再到客户服务等等&#xff0c;人工智能聊天机器人…

光伏发电系统模拟及其发电预测开源python工具pvlib

1. 太阳辐照量模拟 pysolar是一个用于计算太阳位置和辐照量的Python库。它是基于python语言编写的&#xff0c;可以方便地在各种python项目中使用。pysolar主要用于计算太阳的位置、太阳高度角、太阳方位角、日出和日落时间等信息。这些信息可以用于太阳能电池板和太阳能集热器…

【设计模式】创建型-抽象工厂模式

文章目录一、抽象工厂模式1.1、产品族、产品等级1.2、抽象工厂模式中的角色1.3、实例一、抽象工厂模式 在工厂方法模式中&#xff0c;每一个具体的工厂子类只能生成一种具体的产品&#xff0c;如果想要生产另外一种产品&#xff0c;就需要重新定义一个抽象工厂类&#xff0c;这…

泡泡玛特“失速”,盲盒经济迎来拐点?

配图来自Canva可画​ 前些年泡泡玛特的飞速增长&#xff0c;曾经在行业内外引起了广泛的反响&#xff0c;其主打的盲盒经济也曾风靡一时、被众多行业效仿。不过&#xff0c;这种情况在疫情肆虐的2022年似乎受到了一些影响&#xff0c;这在其财报中就有所体现。 3月29日&#…

Python 小型项目大全 61~65

六十一、ROT13 密码 原文&#xff1a;http://inventwithpython.com/bigbookpython/project61.html ROT13 密码是最简单的加密算法之一&#xff0c;代表“旋转 13 个空格”密码将字母A到Z表示为数字 0 到 25&#xff0c;加密后的字母距离明文字母 13 个空格&#xff1a; A变成N&…

【Android】之【自定义View实践】

这里以一个进度条的加载为例子&#xff0c;先看效果&#xff08;运行效果是动态变化的&#xff09; 一、自定义属性 首先在res->values目录下新建attrs资源文件&#xff0c;如下图&#xff1a; 内容如下&#xff1a; <?xml version"1.0" encoding"utf…