微服务架构应用渐广,服务网格热度陡升,赋予了API网关越来越重要的地位。API
网关将各个微服务对外暴露的接口聚合起来,所有接口调用都需要通过 API
网关进行访问。作为网格或者一众微服务的流量入口,API网关负责流量分发、监控、限流、日志收集等等,是微服务架构和服务网格中不可或缺的关键组件。而提到API网关,则不可不提到Envoy。作为一款强大的开源服务代理软件(类比于Nginx),Envoy为许多API网关产品提供基石和支撑。
Envoy是由lyft开源的边缘和和服务代理,后被捐赠给CNCF基金会。可以说,Envoy已经是云原生时代数据平面的事实标准。新兴API网关如Gloo,Ambassador都基于Envoy进行扩展开发;而在服务网格中,Istio、Kong社区Kuma、亚马逊AWS App Mesh都使用Envoy作为默认数据面。
与HAProxy以及Nginx等传统Proxy依赖静态配置文件来定义各种资源以及数据转发规则不同,Envoy几乎所有配置都可以通过订阅来动态获取,如监控指定路径下的文件、启动gRPC流或轮询REST接口,对应的发现服务以及各种各样的API统称为xDS。Envoy与xDS之间通过Proto约定请求和响应的数据模型,不同类型资源,对应的数据模型也不同。
以Istio中Pilot为例,当Pilot发现新的服务或路由规则被创建(通过监控K8S集群中特定CRD资源变化、或者发现Consul服务注册和配置变化),Pilot会通过已经和Envoy之间建立好的gRPC流将相关的配置推送到Envoy。Envoy接收到相关配置并校验无误之后,就会动态的更新运行时配置,使用新的配置更新相关资源。Pilot工作原理如图1所示。
图1. Envoy配置下发流程
利用xDS协议,Envoy可以实现配置的完全动态化,配置实时更新而无需重启Envoy或者影响业务。此外,利用其L3/L4/L7 Filter机制,Envoy可以完全无侵入的扩展各种强大的功能。利用其内置的Tracing机制和Stats模块,可以很方便的实现对流量的跟踪以及监控,保证Envoy中流量的可观察性。无论是Envoy Filter或者其Stats,都包含大量的内容,此处不会详述。本文只会概括性介绍Envoy中一些关键概念以及xDS相关内容。
资源本身是很抽象的概念,此处所谓的资源是指Envoy根据相关配置创建出来的具有某种特定功能或者目的的实体。在Envoy中,具有最为核心的四种资源:Listener,Router,Cluster,以及Filter。
简单理解,Listener是Envoy打开的一个监听端口,用于接收来自Downstream(客户端)连接。Envoy可以支持复数个Listener。多个Listener之间几乎所有的配置都是隔离的。Listener配置中核心包括监听地址、Filter链等。
Listener对应的配置/资源发现服务称之为LDS。LDS是Envoy正常工作的基础,没有LDS,Envoy就不能实现端口监听(如果启动配置也没有提供静态Listener的话),其他所有xDS服务也失去了作用。
在Envoy中,每个Upstream上游服务都被抽象成一个Cluster。Cluster包含该服务的连接池、超时时间、endpoints地址、端口、类型(类型决定了Envoy获取该Cluster具体可以访问的endpoint方法)等等。
Cluster对应的配置/资源发现服务称之为CDS。一般情况下,CDS服务会将其发现的所有可访问服务全量推送给Envoy。与CDS紧密相关的另一种服务称之为EDS。CDS服务负责Cluster资源的推送。而当该Cluster类型为EDS时,说明该Cluster的所有endpoints需要由xDS服务下发,而不使用DNS等去解析。下发endpoints的服务就称之为EDS。
Listener可以接收来自下游的连接,Cluster可以将流量发送给具体的上游服务,而Router则决定Listener在接收到下游连接和数据之后,应该将数据交给哪一个Cluster处理。它定义了数据分发的规则。虽然说到Router大部分时候都可以默认理解为HTTP路由,但是Envoy支持多种协议,如Dubbo、Redis等,所以此处Router泛指所有用于桥接Listener和后端服务(不限定HTTP)的规则与资源集合。
Route对应的配置/资源发现服务称之为RDS。Router中最核心配置包含匹配规则和目标Cluster,此外,也可能包含重试、分流、限流等等。
Filter,通俗的讲,就是插件。通过Filter机制,Envoy提供了极为强大的可扩展能力。在Envoy中,很多核心功能都使用Filter来实现。比如对于Http流量和服务的治理就是依赖HttpConnectionManager(Network Filter,负责协议解析)以及Router(负责流量分发)两个插件来实现。利用Filter机制,Envoy理论上可以实现任意协议的支持以及协议之间的转换,可以对请求流量进行全方位的修改和定制。强大的Filter机制带来的不仅仅是强大的可扩展性,同时还有优秀的可维护性。Filter机制让Envoy的使用者可以在不侵入社区源码的基础上对Envoy做各个方面的增强。
Filter本身并没有专门的xDS服务来发现配置。Filter所有配置都是嵌入在LDS、RDS以及CDS(Cluster Network Filter)中的。
xDS以及各个资源之间的关系如图2所示。
图2. xDS协议
作为一个服务代理软件,Envoy并不限定自己的使用方法。它最常扮演的是两种不同的角色,一种是作为集群流量入口API网关(Gateway),管理南北流量;一种是作为服务Sidecar,拦截并治理服务网格中东西流量。
图3. Envoy的两种角色
API网关负责集中管理集群或者网格对外暴露的接口,为集群外或者网格外客户端调用集群内或网格内服务提供了统一的流量入口和治理方案,其重要性自然不必多言。因此,此处重点解释Envoy作为Sidecar的功能和作用。
Sidecar是当前微服务领域先进的实践。微服务框架解决了单体应用过于复杂,难以维护,语言绑定,不易扩展等问题,使得多种语言、多种框架的多个微服务构成一个整体,并通过API网关向外提供统一接口。但是微服务框架也带来了服务治理复杂、故障定位难、服务注册、服务发现等新挑战。
克服这些挑战,业界有如下三种技术方案。
图4. 服务网格三种方案
作为Sidecar时,Envoy通过修改IP Table实现对服务的进出口流量的拦截,并进一步实现对进出口流量的管理。每个Sidecar通过xDS协议和控制面交互,获取集群中其他服务的相关信息以及各种服务治理相关(鉴权、分流、流量复制等等)的配置。服务本身只需要专注于业务逻辑,所有网络流量相关的工作都委托给Envoy Sidecar。
Envoy通过xDS协议与控制面实现配置数据的交换。当控制面检测中配置变化(比如通过Kubernetes Watch到新service或者其他的CRD资源更新),会向Envoy发送一个discoveryResponse来将更新后的配置下发到Envoy。之后,Envoy主线程在接受到数据之后,通过向各个工作线程中追加配置更新事件来完成配置的实际更新和生效。
但是,需要注意的是,控制面下发的discoveryResponse是一个全量的配置。换言之,哪怕是修改了一条路由中的一个小小请求头匹配,所有Listener、Cluster、Router都必须更新,Envoy会用接受到的新配置替换旧配置。使用现有更新方案虽然逻辑简单明了,但是在负载较多、配置量较大时,会造成大量的流量浪费和不必要的计算开销。
尤其是对于Sidecar模式下的Envoy,该问题会更加的明显。网格中服务需要访问其他服务时,其流量首先会被Envoy Sidecar所拦截,之后由Sidecar将请求转发给对应的服务。由于Sidecar并不了解其代理的服务依赖网格中哪些其他服务,所以它会记录服务网格中所有服务的相关信息。但是,事实上一个网格服务往往只会依赖网格中少量的几个服务。
因为上述问题,Envoy社区提出了delta xDS方案,实现增量的xDS配置更新。简单的说,在delta增量更新方案中,当配置发生变化时,只有发生变化的一项配置(配置的最小单位为一个完整的proto message)需要下发和更新。
基于delta增量更新方案,可以实现以下三种新的功能:
缓存擦除(或者说on demand loading):根据Envoy当前负载实际请求动态调整订阅资源类型,对于不再活跃的配置,取消订阅,从Envoy内存中擦除,直到相关配置再次被使用。通过该功能,可以有效限制Envoy配置所占用的数据量,在超大规模应用场景中,可以有效减少Envoy内存开销。
作为服务代理软件中的新秀,Envoy具有极为强大的可扩展能力,利用Envoy的L3/L4/L7 插件机制,网易轻舟微服务团队可以在各个层面对Envoy进行功能扩展。而且由于Envoy良好的封装和现代C++对各种操作的简化,其插件开发过程已经相当简单。真正复杂的部分往往是插件本身的逻辑,而不是C++语言或者Envoy。并且在插件开发过程中,无需过多的考虑线程安全的问题,大部分情况下,都可以将插件的执行逻辑当作单线程来处理。本文将简单介绍Envoy的插件模型。
需要额外说明的是,Envoy中的插件的重要性可能远比“插件”这一名词所体现出来的要多的多。在Envoy中,大量的核心功能都使用插件来实现,包括对Http流量的治理、Tracing机制、多协议支持等等。为了方便,后续插件可能使用单词Filter来表述,在本文中,所有“插件”与“Filter”等同。
此外,本文并不包含任何插件开发的相关内容,而是仅仅根据网易轻舟微服务团队的Service Mesh实践经验,科普性地梳理Envoy中的插件机制以及插件的配置模型。希望对Envoy的使用者或者更上层的工作能有所帮助。
Envoy包含多种不同类型的插件,如Listener、Network、Http以及Access
log Filter等。其中Access log Filter仅仅用于对access
log做一些个性化定制处理,所以不多做赘述。Envoy插件整体模型如下图1所示。
图1. Envoy插件模型
Listener Filter在连接建立之后,首先被执行处理,但是由于Listener Filter只能获得极少数的信息(remote address),所以在此进行的操作极少。在绝大多数情况下,也无需扩展定制Listener Filter。
Network Filter是在Listener Filter处理完成之后开始执行的插件。其工作在四层,相比与Listener Filter,Network Filter可以获取相对更加丰富的信息,但是也非常有限。Network Filter是envoy管理各种协议和流量的基础,通过扩展Network Filter,可以实现envoy对各种不同类型流量的治理,如Dubbo、redis、MQ等等。
在所有的四层插件之中,有一个特例需要特别强调:HttpConnectionManager。该插件负责处理HTTP协议的四层流量。该插件是Envoy强大的能力的基础,可以说是整个Envoy中最为重要的插件。
此外,在Envoy 1.2之后,引入了一种特殊的cluster network Filter,作用于某个cluster。指向该cluster的四层流量首先需要在该cluster下的所有cluster network Filter完成处理之后才会发送出去。而在接收到来自上游服务的响应之后,首先也会由该cluster下的所有cluster network Filter处理后再继续传递给其他Filter或者下游客户端。借助cluster network Filter,envoy可以实现任意协议之间的流量转换。以HTTP和Dubbo为例:当cluster A指向一个Dubbo服务时,可以为该cluster配置一个HTTP2Dubbo的cluster network Filter。之后,可以将HTTP请求发送给cluster A。cluster A下的HTTP2Dubbo会在四层将HTTP请求重新解析并且拼接编码出Dubbo请求的二进制序列并发送给上游Dubbo服务。在接收到Dubbo服务响应之后,又可以利用同样的方式将Dubbo响应转换为HTTP响应。如此,cluster A可以完全被当作一个HTTP服务来治理,所有的细节都被屏蔽在cluster network Filter之中。其数据处理流程如下图2所示。
图2. Cluster Network Filter
无需多言,互联网是在HTTP协议之上构建的,所以在Envoy中,处处可见对于HTTP协议的强调和特化处理。实际上,Http Filter只是前文提到的HttpConnectionManager插件下的一系列子插件,但是却在envoy中占据了最重要的位置。HttpConnectionManager插件在完成HTTP数据解析之后,会依次调用Http Filter提供的接口,对HTTP数据做进一步处理(简单的处理如替换header、复杂的处理如限流、错误注入、缓存等等)。Envoy丰富而强大的功能,很多都是依赖Http
Filter插件来实现。大部分情况下,在对Envoy做扩展增强时,也是通过扩展Http Filter来做到。Http
Filter实际工作流程如图3所示。
图3. Http Filters
之所以要单独讲插件配置,是因为大部分人都只是插件的使用者而非开发者。插件的使用者只需要知道如何下发正确配置给插件并让插件生效即可。此处只介绍HttpConnectionManager以及Http Filter插件配置,因为对于大部分人来说,无需了解四层及以下的插件内容(HttpConnectionManager是特例)。
在介绍之前,必要要再三强调:所有配置的生效与否、作用、影响都依赖于插件本身实现,没有任何强制手段可以让插件必须使用哪些配置。
虽然插件开发比如有一定的规范,并且大部分插件也会遵循规范,但是规范并不是强制。
如前所述:所有Http Filter实际上都只是HttpConnectionManager的子插件,因此,启用哪些插件,禁用哪些插件实际上都是由HttpConnectionManager来控制。其配置形式如下(此处只做科普,所以暂时无需纠结更深的细节,只需要有大概的认识即可):
http_filters:
- name: com.netease.iprestriction
config: {}
- name: envoy.router
config: {}
所有在上述配置列表中的插件将会被启用,其他插件则处于禁用状态。必须要额外强调的是:HttpConnectionManager的配置是Listener级别配置,所以插件的启用或者禁用也是以Listener(一个Listener就是一个监听端口,映射到控制面Pilot中,就是一个Gateway资源)为单位。换言之,当一个插件被启用时,在该Listener下的所有路由、所有请求上,都会生效;当一个插件被禁用时,那么在该Listener下的所有路由、所有请求上,都会禁用。
当然,插件启用只是说明HttpConnectionManager会调用该插件提供的接口,用于处理解析后的header以及body。但是插件本身具体的执行逻辑依旧由插件自身决定。
目前不存在某一个插件只在某一条路由上生效。如果有,是该插件本身内部的逻辑,该插件内部做了相关判断,如在不匹配的路由执行该插件时,直接返回等。
在Envoy中,存在两种不同的配置用于控制插件的行为,一种是基础配置,一种是路由配置。两种配置的数据模型可能相同,可能完全不同,可能部分相同,可能缺省,完全由插件设计者决定。举例来说,transformation插件没有基础配置,只有路由配置;iprestriction插件有基础配置,但是可以缺省,也有路由配置;supercache有基础配置,且不可缺省,也有路由配置。
首先介绍插件基础配置。插件基础配置即是插件初始化配置,该配置在启用插件(如上一段所述,在HttpConnectionManager配置中)时,在config
字段中直接提供,如上一段中示例配置中的supercache插件。显然,该配置作用范围和插件的启用类似,是对整个listener内所有路由生效的。假设网关只有一个listener(一般情况下都是一个listener),那么可以认为该配置就是在整个Envoy内全局生效的。
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。