0%

[服务网格Istio系列]--非侵入的流量管理-1

在之前的文章中介绍了:[服务网格Istio系列]--Istio架构介绍[服务网格Istio系列]--微服务 Kubernetes Istio关系,这里主要介绍Istio的非侵入的流量管理。

Istio流量治理的原理

流量治理是一个非常宽泛的话题,例如:

  • 动态修改服务访问的负载均衡策略,比如根据某个请求特性做会话保持;
  • 同一个服务有两个版本在线,将一部分流量切到某个版本上;
  • 对服务进行保护,例如显示并发连接数、限制请求数、隔离故障服务实例等;
  • 动态修改服务中的内容,或者模拟一个服务运行故障等;

在Istio中实现这些服务治理功能时无需修改任何应用的代码。较之微服务的SDK方式,Istio以一种更轻便、透明的方式向用户提供这些功能。只要应用运行在Istio的基础是设施上,就可以使用这些治理能力。

一句话总结Istio流量治理的目标:以基础设施的方式提供给用户非侵入的流量治理功能,用户只需要关注自己的业务逻辑开发,无需关注服务访问管理。

控制面会经过如下流程:

  1. 管理员通过命令行或者API创建流量规则;
  2. Pilot将流量规则转换为Envoy的标准格式;
  3. Pilot将规则下发到Envoy;

在数据面会经过如下流程:

  1. Envoy拦截Pod上本地容器的Inbound和Outbound流量
  2. 在流量经过Envoy时执行对应的流量规则,对流量进行治理;

负载均衡

服务调用方使用一个服务名发起访问的时候找到一个合适的后端,把流量导过去。

如下图:传统的负载均衡一般是在服务端提供的,例如,用浏览器访问一个WEB网站时,一般在网站入口处有一个负载均衡器来做请求的汇聚和转发。服务的虚拟IP和后端实例一般是通过静态配置文件维护的,负载均衡器通过健康检查保证客户端的请求呗路由到健康的后端实例上。

在微服务的场景下,负载均衡一般和服务发现配合使用,每个服务都有多个对等的服务实例,需要有一种机制将请求的服务名解析到服务实例地址上。服务发现负责从服务名解析一组服务实例的列表,负载均衡负责从中选择一个实例。

如下图所示,不管是SDK的微服务架构,还是Istio这样的Service Mesh架构,服务发现和负载均衡的工作流程都是类似的:

  1. 服务注册。各服务将服务名和服务实例的对应信息注册到服务注册中心。
  2. 服务发现。在客户端发起服务访问时,以同步或者异步的方式从注册中心获取服务对应的实例列表。
  3. 负载均衡。根据配置的负载均衡算法从实例列表中选择一个服务实例。

Istio的负载均衡正式其中的一个具体应用。在Istio中,Pilot服务维护服务发现数据,Pilot将服务发现数据通过Envoy的标准接口下发给数据面的Envoy,Envoy则根据配置的负载均衡策略选择一个实例转发请求。Istio当前支持的负载均衡算法包括:轮询、随即和最小连接数算法。

在Kubernetes上支持Service的重要组件Kube-proxy,实际上也是运行在工作节点的一个网络代理的负载均衡器,它实现了Service模型,默认通过轮询等方式把Service访问转发到后端实例Pod上,如下图:

服务熔断

熔断器在生活中一般指可以自动操作的电气开关,用来保护电路不会因为电流过载或者短路而受损,典型的动作是在检测到故障后马上中断电流。

熔断器这个概念眼神到计算机世界中指的是故障检测和处理逻辑,防止临时故障或者意外导致系统整体不可用,最典型的应用场景就是防止网络和服务调用故障级联发生,限制故障的影响范围,防止付账蔓延导致系统整体性能下降或雪崩。

如下图所示为级联故障示例,可以看出在4个服务间有调用关系,如果后端服务recommendation由于各种原因导致不可用,则前端服务forecast和frontend都会受影响。在这个过程中,若单个服务的故障蔓延到其他服务,就会影响整个系统的运行,所以需要让故障服务快速失败,让调用方服务forecast和frontend知道后端服务recommendation出现问题,并立即进行故障处理。这时,非常小概率发生的事情对整个系统的影响都足够大。

关于熔断的设计,Martin Fowler有一个静电的文章:https://martinfowler.com/bliki/CircuitBreaker.html,其中描述的熔断主要应用于微服务场景下的分布式调用中:在远程调用时,请求在超时前一直挂起,会导致请求链路上的级联故障和资源耗尽;熔断器封装了被保护的逻辑,监控调用是否失败,当连续调用失败的数量超过阀值时,熔断器就会跳闸,在跳闸后的一定时间段内,所有调用远程服务的尝试豆浆立刻返回失败;同时,熔断器设置了一个定时器,当计时到期时,允许有限数量的测试请求通过;如果这些请求成功,则熔断器恢复正常操作;如果这些请求失败,则维持断路状态。状态机制如下:

解释:

  • 熔断关闭:熔断器处于关闭状态,服务可以访问。熔断器维护了访问失败的计数器,若服务方式失败则加一;
  • 熔断开启:熔断器处于开启状态,服务不可访问,若有服务访问则立即出错。
  • 熔断半开启:熔断器处于半开启状态,允许对服务尝试请求,若服务访问成功则说明故障已经得到解决,否则说明故障依然存在。

序号表示状态流转,如下:

Martin这个状态机成为后面很多系统实现的设计指导,包括最著名的Hystrix。

Hystrix熔断

Hystrix是Netflix提供的众多服务治理工具集中的一个,在形态上是一个Java库,后来多在Spring Cloud中配合其他微服务治理工具集一起使用。

Hystrix的主要功能包括:

  • 阻断级联失败,防止雪崩;
  • 提供延迟和失败保护;
  • 快速失败并即时恢复;
  • 对每个服务调用都进行隔离;
  • 对每个服务都维护一个连接池,在连接池满时直接拒绝访问;
  • 配置熔断阀值,对服务访问直接走失败处理Fallback逻辑,可以定义失败处理逻辑;
  • 在熔断生效后,在设定的时间后探测是否恢复,若恢复则关闭熔断;
  • 提供实时监控、告警和操作控制。

Hystrix的熔断机制基本上与Martin的熔断机制一致。在实现上,如下图所示,Hystrix将要保护的过程封装在一个 HystrixCommand 中,将熔断功能应用到调用的方法上,并监视对该方法的失败调用,当失败次数达到阈值时,后续调用自动失败并被转到一个Fallback方法上。在 HystrixCommand 中封装的要保护的方法并不要求是一个对远端服务的请求,可以是任何需要保护的过程。每个 HystrixCommand都可以被设置一个 Fallback方法,用户可以写代码定义Fallback方法的处理逻辑。

在 Hystrix 的资源隔离方式中除了提供了熔断,还提供了对线程池的管理,减少和限制了单个服务故障对整个系统的影响,提高了整个系统的弹性。在使用上,不管是直接使用Netflix的工具集还是Spring Cloud中的包装,都建议在代码中写熔断处理逻辑,有针对性地进行处理,但侵入了业务代码,这也是与 Istio 比较大的差别。
业界一直以 Hystrix 作为熔断的实现模板,尤其是基于 Spring Cloud。但遗憾的是,Hystrix 在 1.5.18版本后就停止开发和代码合入,转为维护状态,其替代者是不太知名的Resilience4J。

Istio熔断

云原生场景下的服务调用关系更加复杂,前文提到的若干问题也更加严峻,Istio提供了一套非侵入的熔断能力来应对这种挑战。与Hystrix类似,在Istio中也提供了连接池和故障实例隔离的能力,只是概念术语稍有不同:前者在Istio 的配置中叫作连接池管理,后者叫作异常点检测,分别对应 Envoy的熔断和异常点检测。

解决的问题:

  1. 在Istio中通过限制某个客户端对目标服务的连接数、访问请求数等,避免对一个服务的过量访问,如果超过配置的阀值,则快速断路请求。还会限制重试次数,避免重试次数过多导致系统压力变大并加剧故障的传播。
  2. 如果某个服务实例频繁超时或者报错,则将该实例隔离,避免影响整个服务。

以上两个应用场景正好对应连接池管理和异常实例格式功能。Istio的连接池管理工作机制对TCP提供了最大连接数、连接超时时间管理方式,对HTTP提供了最大请求数、最大等待数、最大重试次数、每连接最大请求数等管理方式,他控制客户端对目标服务的连接和访问,在超过配置时快速拒绝。

如下图,通过Istio的连接池管理可以控制frontend服务对目标服务forecast的请求:

  1. 当frontend服务对目标服务forecast的请求不超过配置的最大连接数时,放行;
  2. 放frontend对forecast的请求不过配置的最大等待请求数时,进入连接池等待;
  3. 当frontend对forecast超过配置的最大等待请求数时,直接拒绝。

Istio提供的异常点检查机制动态的将实例从负载均衡池中移除,当连接的错误数超过配置的阀值时,后端实例会被移除。异常点检查在实现上对每个上游服务都进行跟踪,对于HTTP服务,如果有主机返回了连续的5XX,则会被踢出服务池;而对于TCP服务,如果到目标服务的连接超时和失败,则都会被记为出错。

另外,被移除的实例在一段时间之后,还会被加回来再次尝试访问,如果可以访问成功,则认为实例正常;如果访问不成功,则实例不正常,重新被逐出,后面驱逐的时间等于一个基础时间乘以驱逐的次数。这样,如果一个实例经过以上过程的多次尝试访问一直不可用,则下次会被隔离更久的时间。Istio的这个流程也是基于Martin的熔断模型设计和实现的,不同之处在于这里没有熔断半开状态,熔断器要打开多长时间取决于失败的次数。

另,在 Istio 中可以控制驱逐比例,即有多少比例的服务实例在不满足要求时被驱逐。当有太多实例被移除时,就会进入恐慌模式,这时会忽略负载均衡池上实例的健康标记,仍然会向所有实例发送请求,从而保证一个服务的整体可用性。

Istio和Hystrix的熔断进行对比。Istio实现的熔断器其实是一个黑盒,和业务没有耦合,不涉及代码,只要是对服务访问,保护就可以用,配置比较简单直接。

熔断功能本来就是叠加上去的服务保护,并不能完全替代代码中的异常处理。业务代码本来也应该做好各种异常处理,在发生异常的时候通知调用方的代码或者最终用户。

Istio 的熔断能力是对业务透明的,不影响也不关心业务代码的写法。当 Hystrix 开发的服务运行在Istio环境时,两种熔断机制叠加在一起。在故障场景下,如果Hystrix和Istio两种规则同时存在,则严格的规则先生效。当然,不推荐采用这种做法,建议业务代码处理好业务,把治理的事情交给Istio来做。

故障注入

故障注入是一个种评估系统可用性的有效方式,最早在硬件场景下将电路板短路,来观察对系统的影响,在软件场景下也是使用一种手段故意在待测试的系统中引入故障,从而测试其健壮性和应对故障的能力,例如异常处理、故障恢复等。只有当系统的所有服务都经过故障测试且具备容错能力时,整个应用才健壮可靠。

故障注入从方法上来说有编译期故障注入和运行期故障注入,前者要通过修改代码来模拟故障,后者在运行阶段触发故障。在分布式系统中,比较常用的方式是在网络协议栈中注入对应协议的故障,干预服务间的调用,不用修改业务代码。Istio的故障注入就是这样一种机制来实现,但不是在底层网络层破坏数据包,而是在网格中对特定的应用层协议进行故障注入,虽然在网络访问阶段进行注入,但其作用于应用层。这样,基于Istio的故障注入就可以模拟出应用的故障场景了。如下图,可以对某种请求注入一个指定的HTTP Code,这样,对于访问的客户端来说,就跟服务端发生异常一样。

还可以注入一个指定的延迟,这样客户端看到的就跟服务端真的响应慢一样,我们无须为了达到这种效果在服务端的代码里填一段sleep(500):

实际上,在 Istio 的故障注入中可以对故障的条件进行各种设置,例如只对某种特定请求注入故障,其他请求仍然正常。

灰度发布 Blue Green Release

在新版本上线时,不管是在技术上考虑产品的稳定性等因素,还是在商业上考虑新版本被用户接受的程度,直接将老版本全部升级是非常有风险的。所以一般的做法是,新老版本同时在线,新版本只切分少量流量出来,在确认新版本没有问题后,再逐步加大流量比例。这正是灰度发布要解决的问题。其核心是能配置一定的流量策略,将用户在同一个访问入口的流量导到不同的版本上。有如下几种典型场景。

蓝绿发布 Blue Green Release

主要思路如下图,让新版本部署在另一套独立的资源上,在新版本可用后将所有流量都从老版本切到新版本上来。当新版本工作正常时,删除老版本;当新版本工作有问题时,快速切回到老版本,因此蓝绿发布看上去更像一种热部署方式。在新老版本都可用时,升级切换和回退的速度都可以非常快,但快速切换的代价是要配置冗余的资源,即有两倍的原有资源,分别部署新老版本。另外,由于流量是全量切换的,所有如果新版本有问题,则所有的用户都受影响,单比蛮力发布在一套资源上重新安装新版本导致用户的访问全部中断,效果要好很多。

AB测试 ABTest

对于有一定规模的产品,在上线新特性时都比较谨慎,一般都需要经过一轮AB测试。在AB测试里面比较重要的是对评价的规划:要规划什么样的用户访问,采集什么样的指标,尤其是,指标的选取是与业务强先关的复杂过程,所以一般都有一个平台在支撑,包括业务指标埋点、收集和评价。

金丝雀发布 canary Release

金丝雀发布就比较直接,上线一个新版本,从老版本中切出一部分线上流量到新版本来判定新版本在生产环境中的实际表现。就像把一个金丝雀塞到瓦斯井里一样,探测这个新版在环境中是否可用。先让一小部分用户尝试新版本,在观察到新版本没有问题后再增加切换的比例,知道全部切换完成,是一个渐变,尝试的过程。

灰度发布,就是要支持对流量的管理。能否提供灵活的流量策略是判断基础设施灰度发布支持能力的重要指标。

灰度发布技术上的核心要求是要提供一种机制满足多版本同时在线,并能够灵活配置规则给不同的版本分配流量,可以采用以下几种方式。

基于负载均衡器的灰度发布

比较传统的灰度发布方式是是在入口的负载均衡器上配置流量策略,证方式要求负载均衡器必须支持相应的流量策略,并且只能对入口的服务做灰度发布,不支持对后端服务单独做灰度发布。如下图,可以子啊负载均衡器上配置规则对frontend服务进行灰度发布,但是没有地方给forecast服务配置分流策略,因此无法对forecast服务做灰度发布。

基于Kubernetes的灰度发布

在Kubernetes环境下可以基于Pod的数量比例分配流量,如下图,forecast服务的两个版本v2和v1分别有两个和三个实例,当流量被均衡的分发到每个实例上时,前者可以得到40%的流量,后者可以得到60%的流量,从而达到流量在两个版本间分配的效果。

给v1和v2版本设置对应比例的Pod数量,依靠Kuber-proxy把流量均衡的分发到目标后端,可以解决一个服务的多个版本分配流量的问题,但是限制非常明显:

  • 要求分配的流量比例必须和Pod数量成比例,如上图,当前的Pod比例不支持得到3:7的流量比例;
  • 另外,这种方式不支持根据请求的内容累分配流量,比如要求Chrome浏览器发来的请求和IE浏览器发来的请求分别访问不同的版本。

Istio叠加在Kubernetes上,从机制上可以提供比Kubernetes更细的服务控制粒度及更强的服务管理能力。

基于Istio的灰度发布

Istio本身并没有关于灰度发布的规则定义,灰度发布只是流量治理规则的一种典型应用,在进行灰度发布时,只要写个简单的流量管理配置即可。Istio在每个Pod里都注入了一个Envoy,因而只要在控制面配置分流策略,对目标服务发起访问的每个Envoy便都可以执行流量策略,完成灰度发布功能。

如下图示例,对recommendation服务进行灰度发布,配置20%的流量到v2版本,保留80%的流量在v1版本。通过Istio控制面Pilot下发配置到数据面的各个Envoy,调用recommendation服务的两个服务frontend和forecast都会执行相同的策略,对recommendation服务发起的请求会被各自的Envoy拦截并执行同样的分流策略。

在Istio中除了支持这种基于流量比例的策略,还支持非常灵活的基于请求内容的灰度策略,比如某个特性是专门为Mac操作系统开发的,则在该版本的流量策略中需要匹配请求放的操作系统。浏览器、请求的Headers等请求内容在Istio中都可以作为灰度发布的特性条件,如下图,根据Header的内容请求分发到不同的版本上。

服务访问入口

一组服务组合在一起可以完成一个独立的业务能力,一般都会有一个入口服务,从外部可以访问,主要是接受外部的请求并将其转发到后端的服务,有时还可以定义通用的过滤器在入口处做权限、限流等功能。

Kubernetes服务的访问入口

在Kubernetes中可以将服务发不成Loadbalancer类型的Service,通过一个外部端口就能够访问到集群中的指定服务,如下图,从外部近来的流量不用经过过滤和多余处理,就被直接转发到服务上。这种方式直接,简单,在云平台上部署的服务一般都可以依赖云厂商提供的Loadbalancer来实现。

Kubernetes支持另一种Ingress方式专门针对七层协议,Ingress作为一个总的入口,根据七层协议中的路径将服务指向不同的后端服务,如下图,在weather.com这个域名下可以发布两个服务,forecast服务被发布在weather.com/forecast上,advertisement服务被发布在weather.com/advertisement上,这时只需要用到一个外部地址:

其中,ingress是一套规则定义,将描述某个域名的特定路径的请求转发到集群指定的Service后端上。Ingress Controller作为Kubernetes的一个控制器,监听Kuber-apiserver的Ingress对应的后端服务,实时获取后端Service和Endpoints等的变化,结合Ingress配置的规则动态更新负载均衡器的路由配置。

Istio服务访问入口

如下图,在Istio中通过Gateway访问网格内的服务。这个Gateway和其他网格内的Sidecar一样,也是一个Envoy,从Istio的控制面接收配置,统一执行配置的规则。Gateway一般发布为Loadbalancer类型的Service,接收外部访问,执行治理,TLS终止等管理逻辑,并将请求转发给内部的服务。

网格入口的配置通过定义一个Gateway的资源对象描述,定义将一个外部访问映射到一组内部服务上。在 Istio 0.8版本之前正是使用本节介绍的 Kubernetes的 Ingress来描述服务访问入口的,因为Ingress七层的功能限制,Istio在0.8版本的V1alpha3流量规则中引入了Gateway资源对象,只定义接入点。Gateway只做四层到六层的端口、TLS配置等基本功能,VirtualService则定义七层路由等丰富内容。就这样复用了VirtualService,外部及内部的访问规则都使用VirtualService来描述。

外部接入服务治理

如下图,4个服务组成一个应用,后端依赖一个数据库服务,这就需要一种机制能够将数据库服务接入并治理。

关于这种第三方服务的管理,专门有一种Open Service Broker API来实现第三方软件的服务化,这种API通过定义Catalog、Provisioning、Updating、Binding、Unbinding等标准接口接入服务,在和Kubernetes结合的场景下,使用Service Catalog的扩展机制可以方便地在集群中管理云服务商提供的第三方服务,如图所示。

对一个数据库访问进行管理,比对两个纯粹的内部服务访问进行管理更重要。在Istio中通过一个ServiceEntry的资源对象将网格外的服务注册到网格上,然后像对网格内的普通服务一样对网格外的服务进行治理。

在Pilot中创建一个ServiceEntry,配置后端数据库服务的访问信息,在Istio的服务发现上就会维护这个服务的记录,并对该服务配置规则进行治理,从forecast服务想数据库发起的访问在经过Envoy时就会被拦截并进行治理。

ServiceEntry是 Istio 中对网格外的服务的推荐使用方式,当然也可以选择不做治理,直接让网格内的服务访问网格外的服务。

在大多数时候,在访问网格外的服务时,通过网格内服务的 Sidecar 就可以执行治理功能,但有时需要有一个专门的Egress Gateway来支持,如上面的例子所示。出于对安全或者网络规划的考虑,要求网格内所有外发的流量都必须经过这样一组专用节点,需要定义一个Egress Gateway并分配Egress节点,将所有的出口流量都转发到Gateway上进行管理。

来自:云原生服务网格Istio:原理、实践、架构与源码解析书籍,本文只作为读书笔记记录,版权属于原作者。


本文到这里就结束了,欢迎期待后面的文章。您可以关注下方的公众号二维码,在第一时间查看新文章。

公众号

如有疏忽错误欢迎在留言区评论指正,如果对您有所帮助欢迎点击下方进行打赏。