使用 Rust 为 Envoy 构建插件
今天就带大家使用 Rust
构建 WebAssembly
作为 Envoy
的插件。
什么是 WebAssembly
WebAssembly
(Wasm)是一种由多种语言编写的,可移植的字节码格式,它能以以接近本机的速度执行。其最初的设计目标与上述挑战很相符,并且在其背后得到了相当大的行业支持。Wasm
是在所有主流浏览器中可以本地运行的第四种标准语言(继 HTML,CSS 和 JavaScript 之后),于 2019 年 12 月成为 W3C 正式建议。这使我们有信心对其进行战略下注。
WebAssembly In Envoy
在早期的 Envoy
版本中,支持两种插件模式 C++动态库
/ Lua脚本
的模式,不过这两种模式都对语言本身产生了需求,因此在后续提出了 wasm
的支持,一开始作为一个 side
项目独立演进的 envoy-wasm
使用 Wasm
扩展 Envoy
带来了几个主要好处:
敏捷性
:可以用Istio
控制平面在运行时下发和重载扩展。这就可以快速的进行扩展开发→ 测试→ 发布周期,而无需重启Envoy
。发布库
:一旦完成合并到主树中之后,Istio
和其它程序将能够使用Envoy
的发布库,而不是自己构建。这也方便Envoy
社区迁移某些内置扩展到这个模型,从而减少他们的工作。可靠性和隔离性
:扩展部署在具有资源限制的沙箱中,这意味着它们现在可以崩溃或泄漏内存,但不会让整个Envoy
挂掉。CPU 和内存使用率也可以受到限制。安全性
:沙盒具有一个明确定义的API
,用于和Envoy
通信,因此扩展只能访问和修改链接或者请求中有限数量的属性。此外,由于Envoy
协调整个交互,因此它可以隐藏或清除扩展中的敏感信息(例如,HTTP
头中的 “Authorization”和“Cookie”,或者客户端的 IP 地址)。灵活性
:可以将超过 30 种编程语言编译为WebAssembly
,可以让各种技术背景的开发人员都可以用他们选择的语言来编写Envoy
扩展,比如:C++,Go,Rust,Java,TypeScript 等。
在 proxy-wasm spec
有着 ABI 的定义,并且为很多语言提供了 SDK
,那我们先来看看我们最适合的 Rust SDK
Rust WebAssembly In Action
在 proxy-wasm-rust-sdk
提供了操作 Proxy SDK
。
准备工作
因为需要 wasm
的支持,先安装我们的目标架构
1 | rustup target add wasm32-unknown-unknown |
我们首先创建一个我们的 Rust
项目
1 | cargo new --lib my-frist-wasm-filter |
因为我们最终生成的代码给 envoy
使用,因此还需要修改生成的 lib
类型,顺带加上我们的 lib
依赖
1 | [lib] |
插件运行机制
作为插件十之八九都是被 宿主
回调的,因此查阅 abi
有哪些函数,我们大致上就知道我们能做哪些工作了,文档在此
重要的函数
回调点
- _start: 在模块的载入和初始化的时候会调用,一般用来设置一些状态量
- proxy_on_context_create: 上下文创建的时候回调
- proxy_on_done: 在context 处理完成
- proxy_on_vm_start: 在启动 wasm 虚拟器的时候
- proxy_on_new_connection: 建立新连接时
- proxy_on_upstream_data: 收到数据时
- proxy_on_http_request_headers: 收到 http headers 的时候
虚拟机环境实现的函数
- proxy_log: 打印日志
- proxy_done: 当前context 处理完成
- proxy_get_shared_data: 获得 context 共享的对象
对象
整个 WASM
中最重要的就是 Context
对象,这个封装了对于系统的操作,实现 Proxy-Wasm
插件需要进行如下 2 个步骤。
- 定义
Context
- 使用定义的
Context
重载默认实现
Context
对象,在系统中多种定义,比如 RootContext
HttpContext
StreamContext
对应不同处理流程中的
比如 HttpContext
1 | pub trait HttpContext: Context { |
Example
我们来实现我们第一个 Hello World
吧,就是在我们每一次接收到请求的时候,打印出一句 Hello World
1 | use log::info; |
单元测试的写法和普通系统无异,这里就不做展开。
集成测试
首先我们其编译
1 | cargo build --target wasm32-unknown-unknown --release |
我们现在需要一个 Envoy
来进行我们的测试。
1 | docker pull envoyproxy/envoy-dev:af418e1096a386000f936744a1a884b6ce87cee0 |
创建一个 bootstrap.yml
然后启动
1 | static_resources: |
然后就是最激动人心的运行时刻。
1 | $ docker run --rm -it -v $(pwd)/envoy-custom.yaml:/envoy-custom.yaml -v $(pwd)/target/wasm32-unknown-unknown/release/my_frist_wasm_filter.wasm:/my_frist_wasm_filter.wasm -p 9901:9901 -p 10000:10000 envoyproxy/envoy-dev -c /envoy-custom.yaml |
我们看到了我们打印的
…
[2020-10-30 09:38:40.116][26][info][wasm] [source/extensions/common/wasm/context.cc:1154] wasm log: Hello new Connection.
…
代表着我们的插件运行成功了。
周边生态
webassembly hub
(https://webassemblyhub.io/): 因为很多插件可以复用,因此istio
联合 solo.io
社区完成了 webassemblyhub
的建设
除此之外还提供了 wasme
这个 cli
工具可以帮助我们简单的开发系统。