可扩展性是网络代理软件最为关键的特性之一,灵活强大的可扩展性可以大大拓展网络代理软件的能力边界。作为新兴的开源高性能网络代理软件,Envoy 本身提供了相对丰富的可扩展能力,如基于 C++ 的原生扩展,基于 WASM/Lua 的动态扩展。但是 Envoy 现有可扩展能力都各自存在其局限性。在大规模落地实践 Envoy 网关/网格过程中,网易数帆为 Envoy 实现了一套基于 Lua 的企业级自定义扩展框架-Rider,应用于轻舟微服务平台,满足业务方所需要的易开发、高性能、功能丰富等各项要求。
目前,Rider(https://github.com/hango-io/rider) 扩展框架已经全面开源,并且被集成于开源 API 网关 Hango(https://github.com/hango-io/hango-gateway)当中,为 Hango 网关提供了灵活、强大、易用的自定义扩展能力。
1. Envoy 的可扩展性现状
在互联网体系下,凡是需要对外暴露的系统几乎都需要网络代理:较早出现的 HAProxy、Nginx 至今仍在流行;进入微服务时代后,功能更丰富、管控能力更强的 API 网关又成为流量入口必备组件。Envoy 因为其优异的性能、可扩展性、可观察性等优势,成为大量 API 网关的数据面选型,并且除了流量代理所需的基本功能外,Envoy 原生已经实现了很多代理所需高级功能,如高级负载均衡、熔断、限流等。因此,基于 Envoy 构建的 API 网关本身已经具备较为丰富的功能,能满足大部分应用代理的需求。但是在 API 网关的实际使用场景中,某些应用或者业务会根据自己的需求扩展出新的功能,可能是对 HTTP 的某些 Header 做些简单的处理,也可能是对接自己的 APM 等,因此 API 网关必须具备可扩展的能力以支撑应用或者业务根据自己的需求扩展相应的功能,而这个能力 Envoy 依然可以胜任,可以说基于 Envoy 实现的 API 网关的可扩展性强依赖于 Envoy 提供的可扩展能力。那么接下来我们就看一下目前 Envoy 提供的扩展机制。
1.1 原生 C++ 扩展
Envoy 通过可插拔的过滤器机制实现了原生 C++ 插件扩展的能力,如下图所示,L4 过滤器负责扩展协议代理能力及 L4 流量治理能力,L7 过滤器实现对流量的丰富治理功能。
这种扩展方式由于是 Envoy 原生提供的能力,因此性能自然是最佳的,但同时面临两个主要的问题,一个是插件开发者需要具有 C++ 语言的开发能力,另外是插件开发完后需要重新编译 Envoy 二进制文件再升级部署,无法做到插件功能的动态加载。为了解决这两个问题,Envoy 社区陆续实现了基于 Lua 和 WASM 的扩展机制,我们先看社区 Lua 扩展的原理。
1.2 社区 Lua 扩展
要想使用 Lua 语言开发原本使用 C++ 语言实现的 Envoy 插件,直观来看需要考虑以下两点:一个是 Lua 脚本如何在 Envoy 进程中执行;另一个是 Lua 脚本如何获得 Envoy 的内部数据和功能,比如 Header、Body 的获取。从这两个角度出发可以比较清晰的看一下 Envoy 社区 Lua 扩展的实现(其实 WASM 和 Rider 也是从这两个角度出发)。
如下图,和上面介绍的原生 C++ 扩展方案不同的是在 Envoy 的七层插件中多了个 Lua 插件,而这个用 C++ 开发的 Lua 插件就是回答上面两个问题的关键。首先 Lua 脚本如何在 Envoy 进程中执行,答案是通过 Lua 插件,Envoy 的 Lua 插件本身依然是用 C++ 开发的,因此可以在 Lua 插件中加载并运行 Lua 脚本;其次是 Lua 脚本如何获得 Envoy 的内部数据和功能,答案是 Lua 插件会通过 Lua CAPI 的形式提供 Envoy 内部数据和功能给 Lua 脚本。
社区 Lua 扩展一方面提供给了用户基于 Lua 语言开发插件的能力,相比 C++ 要简单很多,另一方面支持 Envoy 动态加载 Lua 脚本,无需重新编译升级。但同时由于 C++ 和 Lua 虚拟机交互带来的开销,Lua 扩展的性能自然会比原生 C++ 扩展差,而且 Envoy 社区当前的 Lua CAPI 交互方式会进一步加剧性能问题。除了性能问题,社区的 Lua 扩展还有个更大的缺陷 —— 不支持插件配置,直接导致社区 Lua 扩展的实用性大大下降。相比之下,WASM 和 Rider 实现了插件的可配置化, 并且 Rider 针对 Lua 扩展的性能做了一定的优化,使得 Rider 的 Lua 扩展在性能和功能方面都能满足企业级扩展的需求。
1.3 社区 WASM 扩展
WASM 是源自前端的技术