速来报名!AICon北京站鸿蒙专场~ 了解详情
写点什么

Envoy 部分源码分析与定制开发

  • 2019-11-06
  • 本文字数:5715 字

    阅读完需:约 19 分钟

Envoy 部分源码分析与定制开发

Envoy 是目前社区中构建 Service Mesh 时的首选反向代理。作为 CNCF 旗下“毕业”的项目,社区给予了 Envoy 充分的肯定,丰富而实用的功能甚至可以采用 Envoy 轻松替换或对接一些传统的 Load Balacer,增强对服务整个调用过程的可见性和控制能力。


在 Istio 和 AWS AppMesh 以及一些自研的 Service Mesh 中,Envoy 都扮演了关键角色。无论 Istio 抑或是 AppMesh,它们所能提供的能力,都受限于 Envoy 本身,如果说 Istio 或 AppMesh 是一道菜,那 Envoy 就是这道菜的食材,是整个 Service Mesh 的灵魂。


Envoy 提供了丰富的协议和连接处理能力,还有各种各样的 Filter 可供选择,复杂的配置也可以支持各种使用场景,此外它还提供了一整套 Filter 框架,定制 Filter 加入对请求的处理过程就可以增加各种各样的功能。


遗憾的是,目前社区对 Envoy 本身的定制开发讨论比较少,大多关注在 Envoy 的 Control Plane 上,Envoy 官方文档也缺乏相应的指导,使得对 Envoy 本身的定制和扩展显得困难重重。本文通过介绍 Envoy 中 Filter Chain 来提供一种向 Envoy 中添加自定义功能的方法,希望对从事相关领域的工程人员有所帮助和启发。


文中如不特别注明,涉及的源码和对应的版本为:


  • Envoy: e5c31e3058cd0480f03feb2855ab1bb93b30e2ce

  • Istio Proxy: 1.1.16 (481d00ab40aee6ae74341e8f522d931bdd8e6951)

Filter Chain

Envoy 将很多独立的功能性组件封装到独立的 Filter 中,在配置中可以按照需要的顺序来配置这些标准化组件,实现丰富的流量控制策略。深入理解 Filter Chain 的设置有助于理解 Envoy 的整个模型,充分利用 Envoy 提供的原生能力。


例如 MySQL Filter 与 TCP Filter 组合,可以监控和优化 MySQL 的连接,提供 MySQL 的 accesslog 支持,也可以为 MySQL 访问增加统一的 metadata 支持,如图 1.1。


图 1.1 MySQL Proxy 的拓扑



Envoy 提供了一系列针对 TCP 协议的 Network Filter,其中的 envoy.http_connection_manager 支持额外的 HTTP Filter Chain, 提供了 HTTP 协议(HTTP/1.1, HTTP/2, gRPC)的流量感知和控制能力, Filter Chain 结构如图 1.2。


图 1.2 envoy.http_connection_manager 和 HTTP Filter 之间的关系



下面从 Envoy 的配置接口定义来看 Filter 的定义来进一步可理解它的 Filter Chain(本文只看 static resource 的定义,因为动态配置发现与静态配置除了管理方式不同之外,接口本身是一样的)。


图 1.3 静态配置的 Protobuf 定义






特别提一下 HTTP Filter,它们实际上是由 envoy.http_connection_manager 来顺序执行的,这里简单介绍注册 HTTP Filter 和最后调用这些 Filter 的过程。


首先,在 Envoy 程序启动阶段,配置工厂对象被注册到一个全局 Map 中,这样就可以在就可以通过配置来控制 Envoy 的行为了。REGISTER_FACTORY 是一个宏定义,也可以直接调用 Registry::RegisterFactory() 在程序初始化阶段注册 Filter 的配置工厂对象。


图 1.4 注册 Filter 配置工厂对象



配置工厂对象注册完成后,就可以根据一个 name,比如 envoy.router 得到配置工厂对象,然后初始化对应的 Filter 实例,详细的注册过程和实现后文介绍 Envoy 定制开发的小节有详细介绍,此处不赘述。


注册完配置工厂对象,接下来就需要注册 Filter 本身的工厂对象,如 envoy.http_connection_manager 实现的 callback 接口可以把 HTTP Filter 的工厂对象注册到它的注册表中,如图 1.5。


图 1.5 注册 HTTP Filter 的 callback 接口



这个接口实际上就将 HTTP Filter 动态添加到 envoy.http_connection_manager 的注册表中,在处理请求的过程中,这些 Filter 就会被按照配置中的顺序依次执行,完成对请求的预处理。


以上简单介绍了 Filter Chain 的基本原理,这是 Envoy 中不太好理解的一个地方,因为它可以随着编译时选择的具体 Filter 改变,在官方文档中很难体现出具体细节。极端的灵活性带来了极端的复杂度,而这样的复杂度的结果就是使用者很难充分利用它提供的能力,因此熟悉 Filter 的源码就是相关从业人员的必修课之一。

典型 Filter

本节仅对几个常用的 HTTP Filter 做源码层面的介绍,只关注怎么在 Envoy 里面加入新的 Filter 以及如何使用新的 Filter,不涉及 Network Filter 以及比较复杂的 HTTP Filter, 如 envoy.http_connection_manager 和 envoy.router 等。

envoy.rate_limit

Envoy 提供基于 gRPC 的限流扩展,可以基于自定义的限流服务来实现针对 HTTP 上下文的限流,先看 Filter 注册过程,注册一个 Filter 分为两部分:


  • 注册配置工厂对象,这样就可以在配置中使用这个 Filter 了

  • 注册 Filter 本身,这样就可以使用该 Filter 来影响流量处理行为了


首先调用 REGISTER_FACTORY 这个宏定义,将它的配置接口注册到 Envoy 的注册表中,这样在配置中就可以用 envoy.rate_limit 来对流量限流了,在配置工厂中调用 addStreamFilter 将 envoy.rate_limit 的 Filter 工厂对象注册到 envoy.http_connection_manager 的 Filter 注册表中,这样就可以用它来实现针对 HTTP 协议的流量控制了,如图 2.1.1。


图 2.1.1 注册 envoy.rate_limit 的 配置工厂对象



envoy.rate_limit Filter 中主要实现了 StreamFilter 的两个方法 decodeHeaders 和 encodeHeaders,除此之外其他的方法没有逻辑,直接进入下一个 Filter。


在 decodeHeaders 中实现了主要的限流逻辑,需要自定义一个扩展的 gRPC 限流接口,来决策是否拒绝请求,它的输入就是当前请求的 HTTP Header,如图 2.1.2。


图 2.1.2 decodeHeaders: envoy.rate_limit 中限流的实现



envoy.rate_limit 在获取配置的时候还检查了 per_filter_config,这是 Envoy 为 HTTP Filter 提供的一种扩展接口,在 envoy.rate_limit 中一旦配置了 per_filter_config 就会覆盖默认的 Filter 配置,如图 2.1.3。


图 2.1.3 envoy.rate_limit 对 per_filter_config 的支持



接着 encodeHeaders 在响应中增加了一组 header,用以在调用限流结果结束后,结果写入 HeaderMap 中,来表征限流的结果,如图 2.1.4。


图 2.1.4 envoy.rate_limit 中修改响应 header



大多数解决特定工程问题的 Filter 都主要关注 Request Header 中的信息,不过 Envoy 中 decodeHeader 中传入的 Header 是同时支持 HTTP/1.x 和 HTTP/2 的。


还有一个值得注意的小细节就是 envoy.rate_limit 中为限流设置了 20ms 的默认超时,现实中这个时间需要根据外接限流服务的情况来设置。

envoy.fault

envoy.fault 为下游的服务提供了 Fault Injection 的能力,模拟给定的请求失败和异常延迟,用以定位噪音下的复杂系统中难以定位的异常。


还是先看它的注册过程,先注册了名为 envoy.fault 的配置工厂对象,这样配置文件里面就可以使用名为 envoy.fault 的 HTTP Filter 了,然后在配置工厂里面调用 addStreamFilter 将配置工厂注册到 Envoy 的注册表中,然后就可以使用它来控制 HTTP 流量了,如图 2.2.1。


图 2.2.1 注册 envoy.fault 的配置工厂到注册表中



在 envoy.fault 中也支持 per_filter_config,与前文介绍的 envoy.rate_limit 是类似的,不赘述。envoy.fault 在 decodeHeaders 中根据配置中定义的规则来判断是否应该直接返回噪声或是增加随机延迟,如图 2.2.2。


图 2.2.2 envoy.fault 随机噪声的实现



envoy.fault 也是一个中规中矩的 HTTP Filter,实现了 decodeHeaders,但工程上尤其是微服务中,主动加入噪声无论是对检测系统稳定性还是分析一些很难定位的问题都很有用,这在 Istio 被抽象成了 VirtualService 中的 Fault,而且 Istio 还将 Envoy 配置中原本独立管理的配置和 Route 直接关联在了一起,这也能降低整体配置的复杂度。

自定义 Filter

本节实现了一个简单的 HTTP Filter istio.evangelist,并用它来实现对 HTTP 流量的额外控制(打印日志)。为了简单起见,这里基于 Istio Proxy 来实现这个扩展,可以充分利用已有的 Bazel rules。

自定义 Filter 配置

先来定义 istio.evangelist 配置接口,Envoy 在编译中会根据这里定义的 Protobuf 接口来生成相应的接口代码,如图 3.1.1。


图 3.1.1 基于 Protobuf 定义 istio.evangelist 的配置



如果需要对配置做静态检查,可以为接口定义清晰的约束,这是因为 Envoy 生成 gRPC 接口文件的时候中引入了 PGV ,利用 Protobuf 中的 Custom Options 来自动生成接口的静态检查代码。


这里为 istio.evangelist 的配置定义约束要求传入的 msg 必须是长度 8-25 的字符(具体支持的校验规则参考 PGV 的官方文档),如图 3.1.2。


图 3.1.2 为 istio.evangelist 的配置增加静态检查



充分的静态检查能为程序逻辑提供一个输入的变化范围的基准线,降低整体逻辑的复杂度,Envoy 能提供如此复杂的功能,而又不显得臃肿和难以理解,大概是得益于它这套优雅的配置管理框架。

Filter 实现和使用

Envoy 在编译阶段选择所需的 Filter,在程序初始化阶段将 Filter 的配置工厂对象注册到 Envoy 的注册表中,完成这一步之后就可以在配置里面使用 istio.evangelist 了,如图 3.2.1。


图 3.2.1 注册 istio.evangelist 的配置管理数据结构



值得一提的是,Envoy 中定义了 RegisterFactory 来注册所有的扩展组件,无论是 Network Filter, HTTP Filter 还是很多其他扩展都是在启动时注册到 Envoy 中,然后可以根据一个唯一的 Name 来从 Factory 中初始化对应组件的对象。


在 Envoy 中其他的组件也是这样注册的,Envoy 中使用了 RegisterFactory 的一个宏定义,如 tcp_proxy,如图 3.2.2。


图 3.2.2 Envoy 中 RegisterFactory 的宏定义 REGISTER_FACTORY



这个地方其实就是把 class 注册到一个 map 中,注册的时候用到的 key 其实就是自定义的 factory 中的 name() 方法返回,而 value 则为对应 factory 的对象,如图 3.2.3。


图 3.2.3 注册 Filter 工厂对象



自定义的 Factory 中定义了三个方法,实际上这实现了 Envoy::Extensions::HttpFilters::Common::FactoryBase 中定义的两个抽象方法:


  • createFilterFactory:根据静态配置初始化对应 filter 的 factory

  • createFilterFactoryFromProto: 根据动态配置接口初始化对应 filter 的 factory


这里还定义了一个 name() 方法,返回这个 filter 的注册的名字为 envoy.evangelist ,如图 3.2.3。


图 3.2.3 定义并注册 Evangelist Filter 的工厂对象




由于这里定义的 envoy.evangelist 是一个 HTTP Filter,所以这里返回的是 Http::FilterFactoryCb,如果是 Network Filter,就需要返回对应的 Network::FilterFactoryCb,如图 3.2.4。


图 3.2.4 Filter 对应的接口 HTTP/Network Filter



实现了 Factory 之后就定义了在启动 Envoy 的时候,将自定义的 Filter 加入 Envoy 的注册表中,这样无论是动态配置还是静态配置就都可以用到相应的 Filter 了。接下来实现 Evangelist Filter 的处理逻辑,如图 3.2.5。


图 3.2.5 Evangelist Filter 的处理逻辑




Envoy 中为 HTTP Filter 定义了三种接口,如图 3.2.6:


  • StreamDecoderFilter: 对请求做预处理,如认证、授权等

  • StreamEncoderFilter:对响应做二次处理

  • StreamFilter:对请求做预处理,并且对响应做二次处理


在注册这三种接口的时候需要分别调用不通的 callback 方法,将 Filter 注册到三个独立的注册表中,供 envoy.http_connection_manager 来根据配置一次执行。


图 3.2.6 HTTP Filter 的三种接口



最后,在本文中自定义的 Evangelist Filter 处理 HTTP 请求过程中打印几条日志,如图 3.2.7。


图 3.2.7 在 Evangelist Filter 处理请求过程中打印日志



增加了自定义的 Evangelist Filter 之后,编译新的 Istio Proxy,可见这时候在 HTTP Filter 已经可以使用了,如图 3.2.8。


图 3.2.8 Evangelist Filter 已经注册到定义后的 Envoy 中



启动一个 Envoy 服务,将 www.example.com 配置成它的 upstream,如图 3.2.9。


图 3.2.9 配置中增加 Evangelist Filter



本地访问可以看到日志中已经打印出来 Evangelist Filter 中对应方法的日志,如图 3.2.10。


图 3.2.10 打印 Evangelist Filter 处理请求过程日志



至此,istio.evangelist 已经集成到了 Envoy 中,如果需要实现额外的扩展,就可以在 decodeHeaders 等接口方法中来实现相关的逻辑。在设计一个 Filter 的时候尽可能明确它的作用范围,如果只作用于 Request 的处理过程,那其实实现 StreamDecodeFilter 就够了;如果要作用于 Response 的处理过程,可以实现 StreamEncodeFilter,避免引入过多冗余代码。

总结

Envoy 大部分功能是以插件形式存在的,灵活的 Filter Chain 让它成为社区中 Service Mesh 的不二选择,不过这样灵活性带来的问题是复杂度和相比较高的学习成本。


Envoy 官方文档中介绍一些常用功能的时候缺少注意事项的介绍,比如在 HTTP 处理过程中的自动 retry 支持,什么时候触发 retry、以及触发 retry 的时候什么逻辑会被反复执行等。这都需要对相关的实现有源码层面的理解,在理解了 Filter Chain 的实现机制之后,无论是对设计过程中的技术选型还是运维过程中的排查问题都有很大的帮助。


此外,Envoy 的配置管理是一个很值得借鉴的点,它通过 Protobuf 来定义配置的数据模型,包括接口数据结构与静态检查,为支持多种配置管理方式打下了基础。Envoy 还为 Protobuf 实现了基于 Custom Options 的扩展,在生成代码的时候可以对内容进行静态检查,这在很多基于 Protobuf 通信(如 gRPC)的场景下可以很大程度降低接口的维护成本(目前社区中类似开源的方案不是很多,可以算独树一帜)。


此外,深入学习 Envoy 源码的基础之一是需要理解它基于 Bazel 的构建框架,只有充分理解构建的过程,在定制开发时才能有的放矢,如第三方库的版本都可以从 Bazel 脚本中寻找答案,这里不再赘述。


总之,Envoy 是一个非常灵活的框架,其配置数据结构也是随着编译时选择插件的列表产生变化,也使得文档中详细介绍每个细节带来了困难,社区出产生的 Istio 等项目带来的主要价值之一就是将 Envoy 中复杂多变的、没有具体业务含义的功能抽象为与实际工程问题相关的概念,降低了学习成本。


笔者管中窥豹,从 Filter 以及基于 Filter 的扩展开发入手简单的分析了 Envoy 中最灵活的部分,希望对相关从业人员带来一些启发。


作者介绍:


杨谕黔,FreeWheel 基础架构组 Lead Software Engineer, 主要关注微服务治理、容器和 Service Mesh 相关的自动化运维和开发。


2019-11-06 10:595162

评论 2 条评论

发布
用户头像
Envoy 官方文档挺难读的,ServiceMesher 社区翻译了一版,https://www.servicemesher.com/envoy/
2019-11-06 18:00
回复
用户头像
👍
2019-11-06 14:25
回复
没有更多了
发现更多内容

全景剖析阿里云容器网络数据链路(六):ASM Istio

阿里巴巴中间件

阿里云 容器 云原生

大国重器用友BIP,助力贸易行业数智化转型

用友BIP

数智化

详解Redis的主从同步原理

C++后台开发

redis 中间件 主从同步 后端开发 Linux服务器开发

如何基于 Antmove 将小程序快速迁移至 FinClip 环境

FN0

小程序 支付宝小程序 finclip

软件测试/测试开发 | 测试平台开发-前端开发之Vue.js 框架

测试人

软件测试 测试开发 测试平台

转型调研 | “鼎新汇•企业行”第一站:走进中国联通软件研究院

信通院IOMM数字化转型团队

数字化转型 IOMM 鼎新汇•企业行 鼎新杯

从青铜到王者,揭秘 Serverless 自动化函数最佳配置

阿里巴巴云原生

阿里云 Serverless 云原生

关于印度跨境数据传输,印度放宽了跨境数据传输

镭速

Nydus 在约苗平台的容器镜像加速实践

SOFAStack

开源 互联网 开发

阿里云函数计算助力高德RTA广告投放系统架构升级

阿里巴巴中间件

阿里云 云原生 函数计算

博睿数据数智领航营全国巡讲火热预约中,扫码即可参与报名~ ​​​

博睿数据

智能运维 博睿数据 数智领航营

车企数据分类分级的实践指南出炉!“数据安全推进计划”发布,奇点云参编

奇点云

数据安全 奇点云 数据分类分级 车企

搞定预设,让你的 ChatGPT 不受限制 | 社区征文

极客飞兔

人工智能 聊天机器人 openai ChatGPT

附安装包和快捷键!5个不能错过的 Blender 插件

Finovy Cloud

软件 blender 3ds Max云渲染

基于人形检测的划区域客流统计

华为云开发者联盟

人工智能 华为云 华为云开发者联盟 企业号 3 月 PK 榜 人形检测

60% 程序员大呼:我要远程办公!

引迈信息

敏捷开发 低代码 远程办公

免费下载丨一看即会,Serverless 技术进阶必读百宝书

阿里巴巴云原生

阿里云 Serverless 云原生

从资源弹性到数据弹性,乾象如何将云上量化研究效率提升 40%?

阿里巴巴云原生

阿里云 云原生

怎么预防LED显示屏静电

Dylan

设备 LED显示屏 全彩LED显示屏

消灭报销,从超级差规开始,用友BIP解决大型企业商旅费控核心难题用友BIP

用友BIP

差旅报销

软件测试/测试开发 | 跨平台API对接(Java)

测试人

软件测试 自动化测试 测试发开

ChatGPT 仅仅是一款工具而已 | 社区征文

小鑫同学

ChatGPT

Nacos+ThreadPoolExecutor构建动态线程池

小小怪下士

Java 程序员 线程池

技术专家云集,OpenHarmony技术峰会分论坛聚焦内核及视窗创新

Geek_2d6073

干货演讲!龙蜥自动化运维平台SysOM 2.0调度、内存相关诊断功能介绍 | 第 70-71 期

OpenAnolis小助手

内存 系统运维 sig 龙蜥大讲堂 SysOM

BI工具数据看板哪个好,瓴羊Quick BI整不错!

流量猫猫头

阿里云 ACK@Edge 助力元戎启行加速进入自动驾驶规模化生产

阿里巴巴云原生

阿里云 云原生容器 云原生r

科技大势怎么看 2023怎么干?

加入高科技仿生人

人工智能 低代码 科技 数字孪生 6G

云快充研发中心平台架构师谈云原生稳定性建设之路

阿里巴巴中间件

阿里云 容器 云原生

分投趣fintoch去中心化借贷交易dapp系统开发搭建

开发微hkkf5566

Envoy 部分源码分析与定制开发_软件工程_杨谕黔_InfoQ精选文章