随着 Envoy 在云原生网络领域的广泛应用,越来越多开发者开始关注如何扩展它的功能。Envoy 提供了多种扩展机制,不同机制在性能、安全性、开发复杂度和兼容性方面各有差异。
这篇文章是我在调研 Envoy 扩展机制过程中的总结,希望能为你理解和选择合适的扩展方式提供一些参考。
在讨论 Envoy 的“扩展机制”时,我们需要先明确一点:并非所有用于扩展功能的方式都算作 Envoy 的原生扩展(in-process extensions)。从架构上看,Envoy 的机制大致分为两类:
Ext_proc 和 ext_authz 本质上是通过 Envoy 提供的 gRPC/HTTP API 接口调用外部服务来实现请求处理逻辑,它们运行在独立进程中,并不注册为 Envoy 本地的过滤器链一部分。因此,严格来说,它们是“集成机制”而非“扩展机制”。
不过,为了更完整地覆盖用户常用的方式,在本文中我将它们一并纳入比较和讨论中。毕竟在实际使用中,它们和原生扩展一样,常用于处理 HTTP 请求和响应。
每种机制的使用成本、性能表现、适用范围都不一样。下面是我对它们的简要归纳。
这是一种最原始也最强大的方式,把自定义逻辑直接写入 Envoy 的源码中编译。这种方式拥有最好的性能(包括零拷贝和低延迟),适合对性能极致要求的场景。但缺点是:你需要维护自定义构建流程,分发自己的 Envoy 二进制文件,升级成本高。
Lua 是一种比较老牌的扩展机制,支持在 Envoy 同一进程中以协程方式运行逻辑代码。它的优点是轻量、上手快、无需重新构建 Envoy,可直接内联到配置中。但它也没有任何隔离机制,脚本运行崩溃可能影响整个 Envoy,因此应仅在信任的环境中使用。
Proxy-Wasm 提供了更现代的方式,可以用 Rust、Go 等语言编写逻辑,并以 WebAssembly 模块动态加载到 Envoy 中运行。它运行在沙箱 VM 中,有一定安全隔离,但当前生态仍不成熟,调试困难,性能略逊于 C++ 和动态模块,仍在不断演进中。
动态模块是最近出现的新机制(从 Envoy v1.34),它允许你用 Rust 编写符合 C ABI 的扩展逻辑,并以 .so
共享库形式在运行时加载到 Envoy 中。相比内置 C++,它不需要你自己编译 Envoy,但仍然具有近似的性能表现。适合性能敏感、同时又想避免维护 Envoy 构建的团队。
ext_proc 提供了一个灵活的方式:你可以在外部服务中完全自定义对请求和响应的处理,包括访问和修改 Body 内容。它通过 gRPC 调用,对数据进行深度检查(如 DLP、病毒扫描等)很有用。但由于是进程外调用,它的性能显然无法与进程内机制相比。
ext_authz 的功能类似,只不过它只处理请求路径,不能修改响应内容。典型场景是 OAuth2、JWT 鉴权、Header 检查等访问控制逻辑。它可以远程部署,不影响主流程,是最常用的认证机制之一。
为了更直观地理解六种机制的异同,我整理了下表,从执行方式、性能、安全性、兼容性等维度进行对比:
维度 | 内置 C++ 过滤器 | Lua 脚本 | Wasm | 动态模块 | ext_proc | ext_authz |
---|---|---|---|---|---|---|
实现方式/执行环境 | 原生 C++,编译入 Envoy | LuaJIT,进程内协程 | VM 中运行(V8/Wazero) | 共享库形式加载,进程内执行 | gRPC/REST,进程外服务 | gRPC/REST,进程外服务 |
性能 | 极致性能,零复制 | 中等,低于 C++ 高于 Wasm | 中等,数据需进出 VM | 高,接近原生性能 | 较低,需跨网络栈复制 | 高效处理元数据,适合轻量鉴权 |
开发语言/SDK | C++ | Lua,无 SDK,使用 stream API | Rust 最佳,支持 Go/C++ | Rust 官方支持,也可用 Go 等构建 | 任意支持 gRPC/REST 的语言 | 任意支持 gRPC/REST 的语言 |
部署/加载方式 | 静态编译 | 配置内联或引用脚本 | 动态加载 Wasm 模块 | 动态加载 .so |
远程服务或 Sidecar 部署 | 可远程部署 |
安全性/信任模型 | 完全受信任 | 无隔离机制,与 Envoy 等价信任 | 沙箱执行,隔离性好 | 与主进程共享内存,需完全信任 | 进程隔离,独立部署更安全 | 进程隔离,独立部署更安全 |
兼容性 | 与 Envoy 版本强耦合 | 依赖 Lua API 稳定性,兼容性尚可 | ABI 相对稳定,跨版本较友好 | 严格要求 ABI 匹配,版本锁定 | API 稳定,跨版本兼容性好 | API 稳定,跨版本兼容性好 |
适用场景 | 核心路径、流量关键路径 | 快速修改逻辑、请求头部调整 | 跨语言开发、安全执行、原型快速迭代 | 性能敏感 HTTP 扩展,不想自建构建链 | 响应检查、DLP、安全扫描 | 授权认证、用户访问控制 |
在我自己的实践与调研过程中,动态模块(Dynamic Modules)逐渐成为我最推荐的扩展机制。
它不仅具备接近内置 C++ 的性能,而且避免了重构 Envoy 构建系统的复杂性。这对很多需要性能保障又不愿意维护 forked Envoy 的团队来说,提供了一个优雅的折中方案。
相比 Wasm,动态模块直接运行于主进程中,无需数据序列化、无需跨 VM 边界复制数据,也没有 VM 启动和内存沙箱的负担,在处理请求头部、body 时具备天然优势。
当然,它目前还属于实验阶段,缺乏跨版本 ABI 兼容性,这意味着每个 Envoy 版本都需要重新构建对应模块。但对很多使用 固定版本或内部发布节奏的企业场景来说,这个限制是完全可以接受的。
从扩展机制演进来看,我认为动态模块很可能会逐步替代大部分 Wasm 的使用场景,特别是在要求高性能和低调试复杂度的企业环境中。
在接下来的博客中,我会分享一个基于 Rust 构建动态模块的完整示例,演示如何快速编写、构建并在 Envoy 中加载一个 HTTP 过滤器,敬请关注。
我的建议是:
每种机制都不是完美的银弹,理解它们背后的设计理念和权衡,才能做出最适合自己业务需求的选择。总之,如果你正在评估在 Envoy 中构建扩展的长期方案,我建议你密切关注动态模块的发展,并提前做好技术选型准备。
如你对某种扩展机制有实战经验或深入探索,也欢迎留言交流。欢迎关注我的博客 jimmysong.io,我会持续分享有关 Envoy、Istio 和服务网格的文章。
最后更新于 2025/05/08