1

什么是WebAssembly


WebAssembly(Wasm)是一种可移植的字节码格式,本身为了解决日益复杂的前端 Web 应用以及有限的JavaScript 脚本解释性能而诞生的技术,通过该技术可以使用非JavaScript 编程语言编写代码并且能在浏览器上运行[1]。

随着Wasm的发展,现在Wasm不仅仅可以用于浏览器,同样可以被应用在server-side程序中,它已经被定义为一个可移植、体积小、加载快的一种通用二进制格式。其指令本身并不直接面向程序开发者,而是被设计为其他编程语言(如Rust、C++、Go)的编译目标,可以在任何具有Wasm运行时的平台上运行。

Wasm在云原生领域的应用生态正在蓬勃发展。Wasm程序可以像容器一样在云原生环境中运行,帮助开发者轻松地构建和部署应用程序。Wasm可以提供更高的性能,更快的启动时间,更低的资源消耗,以及更好的可移植性,是除容器外的另一种对计算和资源的抽象方式。因此Wasm出身就符合云原生的理念,可以很好地在云上运行。目前围绕Wasm相关的云原生相关项目正在涌现。


2

为什么需要WebAssembly


上层语言无关


其实像Wasm这种“编译中间目标 + 跨平台执行”的思想在某些编程语言中已经存在。例如在Java语言中,开发者编写的代码被编译为字节码,再由Java虚拟机执行字节码。不同的是,Wasm是一个通用的编译目标,它允许开发者用任何他们熟悉的编程语言(只要支持编译目标为Wasm,具体请参见支持的语言列表[2])来编写Wasm程序,不需要为了开发某个功能而学习一门新的语言。同时,语言无关性意味着Wasm解锁了各种上层语言生态的大门,使其开发过程本身就具备灵活便利的特点。


轻量级和高性能


Wasm能够达到接近原生机器码的运行速度,有着快速的启动时间和很小的空间占用。与Docker容器相比,Wasm及其运行时可以快速执行并且体积非常小,在运行时性能方面,WebAssembly 比 Docker 快 10%-50%;在启动时间方面,Wasm比Docker快约100倍[3]。在serverless场景中,冷启动是一个困扰已久的问题,而Wasm在这方面有着天然的优势,因此Wasm被认为是一个解决冷启动问题的高效运行时。


高安全性


Wasm二进制是在内存安全的隔离沙箱中执行的,它不仅比原生二进制文件更安全,而且比Docker这样的OS级容器更安全。Wasm对系统资源的操作必须申请明确的权限[4],拥有权限后通过一系列接口与主机环境通信。通过严格的资源约束控制,Wasm可以安全可靠地在各种平台上运行。


Wasm这种良好的隔离性和安全性也使得其非常适合多租户的场景。这种特性正是云计算非常需要的,因此Wasm有潜力成为除容器外的另一种计算运行时。


3

Wasm为Envoy带来新的扩展性

Envoy是一个高性能、可编程的L3/L4和L7网络代理,许多服务网格和网关都采用Envoy作为数据面。


Envoy通过监听器(Listener)捕获网络数据包,根据数据包的内容匹配某个过滤器链(Filter Chain)中,之后按顺序执行该链中的过滤器(Network Filter)对捕获的数据包进行操作,实现用户定义的各种流量治理策略。Envoy本身自带很多种类的过滤器,这些开箱即用的过滤器 cover了大部分的应用场景,但是在某些需要自定义功能的场景下,用户必须实现自己的过滤器。


WebAssembly:让Istio变得更强大_推送

Envoy filter chains[5]


在Wasm出现之前,添加过滤器有两种方式:


1. 修改Envoy代码,使用C++语言编写原生的过滤器(Native C++ filters)。在早期(2019年初),Envoy是一个静态编译的二进制文件,其所有扩展都在构建时编译。这意味着提供自定义扩展的项目必须维护和分发自己的二进制文件。这种方式需要开发者熟悉C++语言和Envoy过滤器的开发模式,在开发完成后重新编译一个新的Envoy二进制且维护它与上游社区的版本。目前Istio社区采用的就是这种方式,Istio fork了上游的Envoy,在其基础之上添加了自己定制的一些插件。然而,这种方式对开发者要求较高,且需要花费额外的精力维护。


2. 使用Lua脚本编写过滤器。这种方式相比于上一种更加简单,用户可以直接在xDS配置中直接编写Lua脚本(inline)或指定本地的一个Lua脚本文件,适合过滤器逻辑非常简单的情况。然而,这种方式并不适用于过滤器逻辑复杂的情况,而且需要用户在Istio中手动创建EnvoyFilter进行额外的配置。


可以看出,上述两种扩展Envoy的方式都不是非常“优雅”。为了解决这个问题,Envoy社区提出了Wasm Filter特性,在Envoy中内嵌了一个Wasm运行时,通过proxy-wasm定义的网络代理相关接口来运行Wasm二进制。这意味着Envoy可以在运行中动态地加载用户开发的Wasm模块,并将其作为一个过滤器插入到过滤器链中。Wasm能够解决上述传统方式的各种问题,用户能够用任何支持的语言开发自己的Wasm过滤器,对Envoy本身无侵入。Envoy发起者称“Wasm是Envoy可扩展性的未来”。



Envoy与Wasm的交互[6]


WebAssembly:让Istio变得更强大_Docker_02


4

Istio WasmPlugin API

Envoy对Wasm的支持为Istio带来了全新的扩展机制。在早期,用户可以使用EnvoyFilter手动在Envoy配置中添加Wasm filter,这种方式非常地繁琐,用户体验并不友好,且EnvoyFilter是一个“break glass”API,社区并不保证其不同版本的向后兼容性。


为了更好地支持Wasm,Istio在1.12版本中添加了一个新的CRD,即WasmPlugin。用户可以通过WasmPlugin方便灵活地将Wasm插件下发到指定的工作负载,使得开发人员可以更简单健壮地扩展网格功能。


WebAssembly:让Istio变得更强大_开发者_03


WasmPlugin yaml示例[7]


上图展示了一个最基本的WasmPlugin配置,在配置中用户需要指定Wasm模块的url,该url可以是像容器镜像一样的OCI格式的Wasm,也可以是代理本地的一个文件(通常要求用户手动挂载到容器中),或者是http协议的url,用于直接获取远程Wasm模块文件。一般推荐的方式第是一种,将编写好的代码编译成Wasm二进制后,打包成一个OCI镜像,方便分发和复现。


用户可以通过selector指定要下发Wasm的proxy,如果selector为空,则代表下发到该namespace下的所有proxy。


除此之外,如果用户需要指定Wasm插件在过滤器链中执行的位置,可以通过phase和priority两个参数来控制。phase用来指定在http filter链中的何处插入此Wasm 插件,可以设置为authentication,authorization或istio stats filter之前,未设置时会在istio stats filter之后插入;priority用来控制多个WasmPlugin在同一个phase中的执行顺序,priority值大的优先。


5

Istio下发Wasm配置流程解析

Istiod推送过程


每当用户更新WasmPlugin时,istiod就会触发一次config update。首先,istiod会更新本次xDS push的context,将当前的WasmPlugin信息按照namespace分类,保存到push context中。


之后,在LDS(Listener Discovery Service)推送的过程中,istiod为proxy构建Listener的http filter时,会从push context中找出与该proxy匹配的WasmPlugin并按照priority排序,最后根据phase将Wasm filter插入到http filter chain中的某个位置。




Istio插入Wasm filter代码[8]


注意此处插入的只是Wasm filter的名称,具体的Wasm filter配置则是通过后续的ECDS(Extension Config Discovery Service)下发的。在ECDS中,istiod会构建出实际的Wasm filter配置并推送给proxy。




 Wasm filter envoy配置示例[9]


6

Proxy接收过程

Proxy接收过程


Envoy的Wasm filter配置本身是不支持使用OCI镜像格式作为data source的。那么Istio是如何支持使用OCI镜像分发Wasm二进制的呢?


答案是通过istio-agent的代理。Istio的proxy中包含两个进程,一个是Envoy本身,另一个是istio-agent。istio-agent会代理Envoy与istiod之间的xDS通信。对于ECDS,istio-agent在收到推送时,会读取其内容,假如其中的Wasm filter使用OCI镜像或者http/https作为data source(即需要执行的Wasm二进制),那么istio-agent会从远程仓库中拉取该Wasm二进制并缓存至本地。之后会修改ECDS的内容,将Wasm filter的data source改为刚才保存的本地文件,再将修改后的ECDS内容发送给Envoy。


对Wasm为Envoy带来新的扩展性的价值简要总结:

综上,Envoy和Istio对Wasm的支持大大加强了服务网格的扩展性。用户通过Wasm能够以可扩展、灵活、安全的方式对代理进行自定义配置,应对各种场景的业务需求,例如认证、授权、Tracing、请求内容转换/检查等等。同时,Istio提供的API使Wasm成为了服务网格中的“一等公民”,用户可以方便地将Wasm下发到指定的工作负载,该过程是完全动态的,应用无需重启。这种高效率的扩展方式使得服务网格具备了可编程性。


7

未来展望

目前,Wasm仍在快速发展,相关的特性在Istio和Envoy中还处在alpha阶段。为了加速Wasm生态,让所有Wasm程序有一个“common language”,社区正在设计一个标准的Wasm interface —— WASI(WebAssembly System Interface),用于访问和操作系统资源。未来proxy-wasm可能会与WASI融合,为Wasm程序提供一个标准的交互接口。同时,Wasm将支持更多高级语言作为前端,用于构建轻量、高性能的应用程序。随着Wasm生态的逐步成熟,期待它能在云计算领域中带来更多令人兴奋的可能性。

参考资料

[1]https://cloudnative.to/blog/envoy-wasm-source-deep-dive/

[2]https://github.com/appcypher/awesome-wasm-langs

[3]https://kubesphere.io/zh/blogs/can-webassembly-replace-docker/

[4]https://cloud.tencent.com/developer/article/1751193

[5]https://tetrate.io/blog/wasm-modules-and-envoy-extensibility-explained-part-1/

[6]https://istio.io/latest/docs/concepts/wasm/

[7]https://tetrate.io/blog/istio-wasm-extensions-and-ecosystem/

[8]https://github.com/istio/istio/blob/1.16.2/pilot/pkg/networking/core/v1alpha3/listener_builder.go#L378-L386

[9]https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/wasm_filter#id1