WebAssembly in Envoy

Posted by proxy-wasm-spec on Sunday, March 8, 2020

TOC

译者注:WebAssembly in Envoy的第二弹,继续探讨在Envoy中使用Wasm进行扩展。可以看到官方是下了很大的决心推广Wasm这种扩展模式,虽然一开始官方考虑的可能是ServiceMesh(Istio)中的扩展性,但是我认为在微服务网关的场景,可能用途更为广泛,毕竟所有公司在网关侧都会有太多的定制化、个性化的需求,而目前主流的网关如kong的插件机制都是基于它自身的生态,很难在其他基础设施中使用。proxy-wasm草案描述了代理(注意官方的用词是proxy,而不是envoy,体会其中的区别)如何与wasm中的插件进行交互的规范,其提出的代理无关、动态加载、隔离沙盒、访问安全性、多语言支持、资源限制等特性,对于网关的开发者来说有着巨大的诱惑。个人认为应对该规范的发展保持高度关注,一旦生态完善起来,Envoy很有可能成为新一代微服务网关的事实标准。

背景

截至2019年初,Envoy仍然是一个静态编译的二进制文件,其所有扩展都在构建时编译。 这意味着提供自定义扩展名的项目(例如Istio)必须维护和分发自己的二进制文件,而不是使用官方的和未经修改的Envoy二进制文件。

对于无法控制其部署的项目,这甚至会带来更多问题,因为对扩展的任何更新或错误修复都需要构建新的二进制文件,生成版本,进行发布,更重要的是需要在生产中重新部署它。

这也意味着在已部署的扩展和配置它们的控制平面之间经常会有版本漂移。

解决方案

尽管可以使用可动态加载的C++扩展解决部分问题,但目前尚不可行,因为由于Envoy开发的步伐很快,扩展插件并没有稳定的ABI甚至API可用,并且通常来说,更新Envoy需要更改代码,这使得更新版本成为一个手动过程。

相反,我们决定通过使用稳定的ABI在WebAssembly中编写和交付Envoy扩展来解决此问题,因为它带来了许多其他好处(如下所述)。

什么是WebAssembly?

WebAssembly(Wasm)是一种新兴的可移植二进制格式,用于执行代码。 在具有明确定义的资源约束以及用于与嵌入式主机环境(即代理)进行通信的API的情况下,该代码以近似原生的速度在内存安全的(对于主机)沙箱中执行。

收益

  • 敏捷。 扩展可以在运行时直接从控制平面交付和重新加载。 这意味着不仅每个人都可以使用正式的和未修改的代理版本来加载自定义扩展,而且还意味着可以在运行时推送或测试任何错误修复和更新,而无需更新或重新部署新的二进制文件。

  • 可靠性和隔离性。 由于扩展是在具有资源限制的沙箱内部署的,因此它们可以崩溃或泄漏内存,而不会导致整个代理宕机。 此外,还可以限制CPU和内存的使用。

  • 安全。 因为扩展部署在具有明确定义的API的沙箱内部,用于与代理进行通信,所以扩展具有有限访问权限,并且只能修改连接和请求中的有限数量的属性。 此外,由于代理可以协调这种交互行为,因此它可以隐藏或清除扩展中的敏感信息(例如,HTTP标头中的“Authorization”和“ Cookie”属性,或客户端的IP地址)。

  • 多样性。 可以将30多种编程语言编译为WebAssembly模块,从而允许来自所有背景(C,Go,Rust,Java,TypeScript等)的开发人员以他们选择的语言编写Proxy-Wasm扩展。

  • 可维护性。 由于扩展是使用独立于代理代码库的标准库编写的,因此我们可以提供稳定的ABI。

  • 可移植性。 由于主机环境和扩展之间的接口是与代理无关的,因此可以在各种代理中执行使用Proxy-Wasm编写的扩展,例如 Envoy,NGINX,ATS甚至在gRPC库中(假设它们都实现了标准)。

缺点

  • 由于需要启动许多虚拟机,每个虚拟机都有自己的内存块,因此内存使用率更高。

  • 由于需要将大量数据复制进出沙箱,因此对负载进行转码类扩展的性能较低。

  • CPU绑定类型扩展的性能降低。 与原生代码相比,速度下降预计将在2倍不到。

  • 由于需要引用Wasm运行时,因此二进制大小增加了。 WAVM约为20MB,V8约为10MB。

  • WebAssembly生态系统仍处于起步阶段,目前的开发重点是浏览器内部使用,其中JavaScript被视为宿主环境。

高层概述

借助Proxy-Wasm,开发人员可以使用他们选择的编程语言(最好是使用我们提供的特定语言的库)编写代理扩展。 然后,将这些扩展编译为可移植的Wasm模块,并以该格式分发。

在代理方面,一旦加载了Wasm模块(直接从磁盘或通过xDS从控制平面推送),便会验证它是否与已定义的Proxy-Wasm接口保持一致,并使用嵌入式Wasm运行时实例化,从而在每个工作线程中创建一个新的Wasm虚拟机。

对于每种Envoy扩展类型,我们都创建了一个垫片(shim),将扩展的接口转换为Proxy-Wasm调用,因此该接口与本机(C++)Envoy扩展中使用的接口非常相似,其中包含事件驱动的编程模型。

envoy-work-thread

运行时

为了执行提供的Proxy-Wasm扩展,代理需要嵌入一个Wasm运行时,它将在沙箱中执行代码。 当前,有两个C或C++ Wasm运行时:基于LLVM的WAVM和V8。 目前,WAVM和V8都嵌入在Envoy中,我们可以在配置中选择一个或另一个,但是我们正在期待上游的解决方案,届时可能只会有一个单一运行时。

虚拟机

Wasm运行时实例化Wasm模块时,它将为其创建Wasm虚拟机(VM实例)。

有几种模型可用于在VM实例和Proxy-Wasm扩展之间进行映射。 最终,这需要在启动延迟和资源使用以及隔离和安全性之间进行权衡。

  • 每个工作线程的每个Wasm模块都永久驻留在进程内VM(在多个配置的Wasm扩展用途中共享)。 一个Wasm模块可以包含多个扩展(例如,在单个程序包中的侦听器筛选器和传输套接字)。 对于每个Wasm模块,都会创建一个持久的进程内虚拟机实例,并且可以(但不必)由所有在配置中引用该Wasm模块的Proxy-Wasm扩展共享该实例。

  • 每个工作线程的每个Wasm扩展都永久驻留在进程内VM。 为每个Wasm扩展创建单个持久的进程内VM实例,并由配置中指定给定Wasm模块的所有Proxy-Wasm扩展共享,类似于现在实例化本机(C++)扩展的方式。

  • 每个工作线程的每个使用Wasm扩展的配置用例都永久驻留在进程内VM。 对于配置中引用给定Wasm模块的Proxy-Wasm扩展的每次配置使用,都会创建一个持久性进程内VM实例。 与以前的模型相比,此模型提供了更强的隔离保证,并且在多租户环境中应首选此模型。

  • 临时(按请求)进程内虚拟机。 为处理每个请求,Proxy-Wasm扩展创建一个新的临时进程内VM实例,并在请求完成后立即销毁它。 预计这种模式的开销会非常昂贵。

  • 进程外VM。 这超出了本文档的范围,但是对于在多租户环境中加载不受信任(且可能是恶意的)Wasm模块的部署,这些部署需要强大的安全性保证并希望防止类似Spectre的攻击,代理应使用实现了Proxy-Wasm的进程外沙箱与外部进行通信(例如,使用Filters-over-gRPC或共享内存),它将代表其执行Wasm模块并将结果返回代理。

主机环境

沙盒化的Wasm VM使用明确定义的接口与嵌入主机环境(即代理)进行通信,这些接口包括:从Wasm模块导出的功能(代理可以调用),Wasm VM可以调用的帮助程序功能以及用于内存管理的Wasm函数。

因为此接口是非常底层的并且相当稳定,所以它使我们能够定义一个稳定的ABI(功能原型将会在单独的文档种定义),扩展程序可以使用该接口。

支持的语言和API

可以使用任何可编译为WebAssembly的语言编写Proxy-Wasm扩展,但是由于这些扩展需要遵守上述接口,因此我们将提供一些选定语言的库(使用Emscripten,Rust,Go和TypeScript的C/C++) ,以加快这些扩展程序的开发。

从理论上讲,任何符合ABI的Wasm模块,无论以哪种语言编写,都可以立即使用,但Wasm生态系统仍然很年轻,缺乏标准,特别是对于非Web环境的支持。 一些语言有关于执行宿主环境的假设或要求,例如Go编译的Wasm模块使用syscall/js,并期望宿主环境为JavaScript。

控制平面(xDS)集成

可以通过使用Envoy的Config::DataSource在配置中引用它们来加载Proxy-Wasm扩展,该扩展既可以指向磁盘上的文件,也可以包含从控制平面(xDS)发送的嵌入式Wasm模块。 我们正在扩展此接口,以支持从HTTP服务器获取资源。 由于代理将执行加载的Wasm模块,因此强烈建议使用更严格的检查,例如SHA256校验或扩展的数字签名。

故障检测与通知

如果Wasm VM崩溃(例如由于Wasm扩展中的错误),代理应创建该VM的新实例,记录有关崩溃的信息,并将其公开给外部系统(例如使用stats),以便控制平面可以根据此信息采取行动。

理想情况下,代理还应该跟踪崩溃的数量,达到限制后,它应该停止重新启动Wasm VM(以防止进入崩溃循环),并开始拒绝连接或将错误返回给客户端。

可配置的资源限制

可以为每个已配置的Proxy-Wasm扩展设置资源限制(每个VM可以分配的最大内存,以及每个调用期间可以消耗的最大CPU时间)以限制资源使用。

可配置的API限制

可以为每个已配置的Proxy-Wasm扩展限制可用API的列表,以便于仅用于计算的扩展(例如压缩)无法访问它们不需要的API(例如HTTP / gRPC调用)。

此外,某些API可以清除输入和输出(例如,删除返回的标头值,或限制可以进行HTTP/gRPC调用的主机列表)。

生态系统(“Proxy-Wasm扩展商店”)

此项工作会在最终确定,并在社区中获得采用后完成。(注:截止本文发布,solo.io已经发布了WebAssembly Hub服务)

原文:Proxy-Wasm/spec/WebAssembly-in-Envoy