在Istio 中实现服务治理功能时无需修改任何应用的代码。较之微服务的SDK方式,Istio以一种更轻便、透明的方式向用户提供了这些功能。用户可以使用自己喜欢的任意语言和框架进行开发,专注于自己的业务,完全不用嵌入任何治理逻辑。只要应用运行在Istio的基础设施上,就可以使用这些治理能力。
一句话总结Istio流量治理的目标:以基础设施的方式提供给用户非侵入的流量流量治理能力,用户只需要关注自己的业务逻辑开发,无需关注服务访问管理。
Istio了流量治理的概要流程如下:
在控制面会经过如下流程:
在数据面会经过如下流程:
下面具体赖看下Istio提供了哪些流量治理功能。
负载均衡从严格意义上讲兵不应该算治理能力,因为它只做了服务间互访的基础工作,在服务调用方使用一个服务名发起访问的时候能够找到一个合适的后端,把流量导过去。
传统的负载均衡一般是在服务端提供的,例如访问一个Web服务,一般在网站入口处有一个负载均衡器在请求的汇聚和转发。服务的虚拟IP和后端实例一般是通过静态配置文件维护,负载均衡器通过健康检查保证客户端的请求被路由到健康的后端实例上。
在微服务场景下,负载均衡一般和服务发现配合使用,每个服务都有多个对等的服务实例,需要一种机制将请求的服务名解析到服务实例地址上。服务发现负责从服务名称解析一组服务实例的列表,负载均衡负责从中选择一个实例。
不管是微服务的SDK架构,还是Istio这样的Service mesh架构,服务发现和负载均衡的工资流程都是类似的,如下所述:
Istio的负载均衡正是其中的一个具体应用。在Istio中,Pilot负责维护服务发现数据,Pilot讲服务发现数据通过Envoy的标准接口下发给数据面的Envoy,Envoy则根据配置的负载均衡策略选择一个实例转发请求。Istio当前支持的主要负载均衡算法包括:轮询、随机和最小连接数算法。
在kubernetes上支持Service的重要组件KubeProxy,实际上也是运行在工作节点的一个网络代理和负载均衡器,它实现了Service模型,默认通过轮询等方式把Service访问转发到后端实例Pod上。
关于熔断,大家比较熟悉的一个落地产品就是Hystrix。Hystrix是Netflix提供的众多服务治理工具集中的一个,在形态上是一个Java库,在2011年出现,后来躲在spring cloud中配合其他微服务治理工具集一起使用。
Hystrix 的主要功能包括:
Hystrix的熔断机制基本上与Martin的熔断机制一致。Hystrix将要保护的过程封装在一个HystrixCommand中,讲熔断功能应用到调用方法上,并监视对该方法的失败调用,当失败次数达到阈值时,后续调用自动失败并被转到一个Fallback方法上。在HystrixCommand中封装的要保护的方法并不要求是一个对远端服务的请求,可以是任何需要保护的过程。每个HystrixCommand都可以被设置一个Fallback方法,用户可以写代码定义Fallback方法处理逻辑。
在Hystrix的资源隔离方式中除了提供熔断,还提供了对线程池的管理,减少和限制了单个服务故障对整个系统的影响,提高整个系统的弹性。
在使用上,不管是直接使用Netflix的工具还是Spring Cloud中的包装,都建议在代码中写熔断处理逻辑,有针对性地进行处理,但是侵入了业务代码,这也是与Istio比较大的差别。
业界一直以Hystrix作为熔断的实现模板,尤其是基于Spring Cloud。但遗憾的是,Hystrix在1.5.18版本后停止了开发和代码并入,转为维护状态,其代替者是不太知名的Resilience4J。
云原生场景下的服务调用关系更加复杂,前文提到的若干问题也更加严峻,Istio提供了一套非侵入的熔断能力来应对这种挑战。
与Hystrix类似,在Istio中也提供了连接池和故障实例隔离的能力,只是概念术语稍有不同:前者在Istio的配置中叫做连接池管理,后者叫做异常点检测,分别对应Envoy的熔断和异常点检测。
在Istio 0.8版本前使用V1alpha1接口,其中专门有个CircuitBreaker配置,包含对连接池和故障实例隔离的全部配置。在Istio1.1 V1alpha3 接口中,CircuitBreaker功能被拆分成连接池管理(ConnectionPoolSettings)和异常点检测(OutlierDetection)这两种配置,由用户选择搭配使用。
首先看看解决的问题,如下所述:
以上两个应用场景正好对应连接池管理和异常实例隔离功能。
Istio的连接池管理工作机制对TCP提供了最大连接数、连接超时等管理方式,对HTTP提供了最大请求数、最大等待请求数、最大重试次数、每连接最大请求数等管理方式,它控制客户端对目标服务的连接访问,在超过配置时快速拒绝。
通过Istio的连接池管理科员控制一个服务对目标服务的请求:
Istio 提供异常点检测机制动态的将异常实例从负载均衡池中移除,当连续的错误数超过配置的阈值时,后端实例会被移除。异常点检测会在实现上对每个上游服务都进行追踪,对于HTTP服务,如果有主机连续返回了5XX,则会被提出服务池;TCP6服务,如果有目标服务的连接超时和失败,则会被标记为出错。
另外,被移除的实例在一段时间后,还会被加回来再次尝试访问,如果可以访问成功,则认为实例正常;如果访问不成功,则实例不正常,重新被逐出,后面驱逐的时间等于一个基础时间乘以驱逐的次数。这样,一个实例经过以上过程的多次尝试访问一直不可用,则下次会被隔离的更久。可以看到,Istio的这个流程也是基于Martin的熔断模型设计和实现的,不同之处在于这里没有熔断半开状态,熔断器要打开多长时间取决于失败的次数。
另外,在Istio中可以控制驱逐比例,既有多少比例的服务实例在不满足要求时被驱逐。当大多数实例被移除时,就会进入恐慌模式,这时会忽略负载均衡池上实例的健康标记,任然会向所有实例发送请求,以保证一个服务的整体可用性。
下面是Istio和Hystrix的熔断简单对比。可以看到与Hystrix相比,Istio实现的熔断器其实是一个黑盒,和业务没有耦合,不涉及代码,只要对服务访问的保护就可以用,配置比较简单直接。
比较内容 | Hystrix | Istio |
---|---|---|
管理方式 | 白盒 | 黑盒 |
熔断使用方法 | 可以实现精细的定制行为,例如写Fallbreak处理方法 | 只用简单配置即可 |
和业务代码结合 | 业务调用要包装在熔断保护的HystrixCommand内,对代码有侵入、要求时Java代码 | 非侵入,语言无关 |
功能对照 | 熔断、隔离仓 | 异常点检测、 连接池 |
熔断保护内容 | 大部分是微服务间的服务请求保护,但也可以处理非访问故障场景 | 主要控制服务间的请求 |
熔断功能本来就是叠加上去的服务保护啊,并不能完全替代代码中的异常处理。业务代码本来也公共做好各种异常处理,在发生异常时候通知调用方的代码或者最终的用户。
Istio的熔断能力是对业务透明的,不影响也不关心业务代码的写法。当Hystrix开发的服务运行在Istio环境时,两种熔断机制叠加在一起。在故障发生时,如果两者的规则同时存在,则严格的规则生效。当然不推荐这种做法,建议业务代码处理好业务,把治理的事情交给Istio处理。
对于一个系统,尤其是一个复杂的系统,重要的不是故障会不会发生,二十什么时候发生。开发人员需要花80%的时间来处理非正常场景。测试人员甚至要花大于80%的时间来执行这些异常测试项,并构造各种故障场景,尤其是那种理论上才出现的故障,让人苦不堪言。
故障注入在软件场景下,使用一种手段故意在待测试的系统中引入故障,以测试其健壮性和应对故障的能力,例如异常处理、故障恢复等。只有当系统的所有服务都经过故障测试且具备容错能力时,整个应用才健壮可靠。
故障注入从方法上来讲有编译期故障注入和运行期故障注入,前者需要通过修改代码模拟故障,后者在运行阶段出发故障。在分布式系统中,比较常用的方法是在网络协议栈中注入对应的协议故障,干预服务间的调用,不用修改业务代码。Istio的故障注入就是这样一种机制的实现,但不是在底层网络层破坏数据包,而是在网络中对特定的应用层协议进行故障注入,虽然在网络访问阶段进行注入,但其作用于应用层。这样,基于Istio故障注入就可以模拟出应用层的故障场景。可以对某种请求注入一个特定的HTTP code。这样。对于访问的客户端来说,就跟服务发生异常一样。
还可以注入一个指定的延时,这样客户端看到的就跟服务端真的响应慢一样,我们无须为了达到这种效果在服务端的代码里添一段sleep(500)。
实际上,在Istio的故障注入中可以对故障的条件进行各种设置,例如只对某种特定请求注入故障,其他请求任然正常。
在新版本上线时,不管是技术上的考虑产品的稳定性等因素,还是在商业上考虑新版本被用户接受的程度上,直接全部升级为新版本是非常有风险的。所以一般的做法是,新老版本同时在线,新版本只切分少量流量出来,在确认新版没问题后,再逐步加大流量比例。这正是灰度发布要解决的问题。其核心是能配置一定的流量策略,将用户在同一个访问入口的流量导到不同的版本上。有如下几种典型场景。
蓝绿发布主要是,让新版本部署在另一套独立的资源上,在新版本可用后,将所有流量从老板切换到新版本上。当新版本工作正常时,删除老版本;当新版本工作有问题的时候,快速切回老版本,因此蓝绿发布更像是一种热部署方式。升级切换和回退的速度都可以非常快,但是快速切换的代价是配置冗余的资源,既有两倍的原有资源,分别部署新老版本。另外,流量时全部切换的,所以新版本如果有问题,则所有用户都受影响。
AB测试的场景比较明确,就是同时在线上部署A和B两个对等的版本赖接受流量,按一定的目标选取策略让一部分用户使用A版本,让一部分用户使用B版本,手机这两部分用户的使用反馈,即对用户采用后做相关比较,通过分析数据来最终决定采用哪个版本。
对于有一定用户规模的产品,在上线新特性时都比较谨慎,一般都需要经过一轮测试,在AB测试里面比较重要的是对评价的规划;要规划什么样的用户访问,采集什么样的访问指标,尤其是,指标的选取是与业务相关的复杂过程,所以一般都有一个平台在支撑,包括业务指标埋点、收集和评价。
金丝雀发布比较直接,上线一个新版本,从老版本中切分一部分线上流量到新版赖判定新版本在生产环境中的实际表现。先让一部分用户尝试新版本,在观察到新版没有问题后再增加切换的比例,直到全部切换完成,是一个渐变、尝试的过程。
蓝绿发布、AB测试个金丝雀发布的差别比较细微,有时只有金丝雀才被成为灰度发布,这里不用条纠结这些划分,只需关注其共同的需求,就是要支持对流量的管理。能否提供灵活的流量策略是判断基础设施灰度发布支持能力的重要指标。
灰度发布技术上的核心要求时提供一种机制满足多不同版本同时在线,并能够灵活配置规则给不同的版本分配流量,可以采用以下几种方式。
1)基于负载均衡器的灰度发布
比较传统的灰度发布方式是在入口的负载均衡器上配置流量策略,这种方式要求负载均衡器必须支持相应的流量策略,并且只能对入口的服务做灰度发布,不支持对后端服务单独做灰度发布。例如,可以在负载均衡器上配置流量规则对服务A进行灰度发布,但是没有地方给服务A的后端服务B配置分流策略,因此无法对服务B做灰度发布。
2)基于Kubernetes 的灰度发布
在Kubernetes 环境下可以基于Pod的数量比例分配流量。例如服务A的两个版本V1和V2分别有两个和三个实例,当流量被均衡地分发到每个实例上时,前者可以获得40%的流量,后者60% ,从而达到流量在两个版本间分配的效果。
给V1和V2版本设置对应比例的Pod数量,依靠Kube-proxy 把流量均衡地分发到目标后端,可以解决一个服务的多个版本分配流量的问题,但是限制也比较明显:首先。要分配的流量必须和Pod数量成比例;另外,这种方式不支持根据请求的内容来分配流量,比如要求Chrome浏览器发来的请求和IE浏览器发来的请求分别访问不同的版本。
3)基于Istio的灰度发布
不同于前面介绍的熔断、故障注入、负载均衡等功能,Istio本身并没有关于灰度发布的规则定义,灰度发布只是流量治理规则的一种典型应用,在灰度发布时,只需要写个简单的流量规则配置即可。
Istio在每个Pod都注入了一个Envoy,因为只要在控制面配置分流策略,对目标服务发起访问的每个Envoy便都可以执行流量策略,完成灰度发布功能。
例如对服务A进行灰度发布,配置20%的流量到V2版本,保留80%的流量在V1版本。通过Istio控制面Pilot下发配置到数据面的各个Envoy,调用服务A的两个服务B与服务C都会执行同样的策略,对服务A发起的请求会被各自的Envoy拦截并执行同样的分流策略。
在Istio中除了支持这种基于流量比例的策略,还支持非常灵活的基于请求内容的灰度策略。比如某个特性是专门为Mac操作系统开发的,则在该版本的流量策略中需要匹配请求方的操作系统。浏览器、请求的Header等请求内容在Istio中都可以作为灰度发布的特征条件。
一组服务组合在一起可以完成一个独立的业务共患难,一般都会有一个入口服务,从外部可以访问,主要是接收外部的请求并将其转发到后端的服务,有时还可以定义通用的过滤器在入口处做权限、限流等功能。
1)Kubernetes 服务的访问入口
在Kubernetes中可以将服务发布成Loadbalancer类型的Service,通过一个外部端口就能访问到集群中的指定服务。例如,从外部进来的流量不用经过过滤和多余处理,就被转发到服务上。这种方式直接、简单,在云平台上部署的服务一般都可以依赖云厂商提供的Loadbalancer来实现。
Kubernetes支持另一种Ingress方式专门针对七层协议。Ingress作为一个总的入口,根据七层协议中的路径将服务指向不同的后端服务。例如,在”weather.com“这个域名下,可以发布两个及多个服务,服务test1被发布到“weather.com/test1”上,服务test2 被发布到“weather.com/test2”上,这时只需要一个外部地址。
其中Ingress是一套规则定义,将描述某个域名的特定路径的请求转发刀片集群指定的Service后端上。Ingress Controller作为Kubernetes的一个控制器,监听Kube-apiserver的Ingress对应的后端服务,实时获取后端Service的Endpoint等变化,结合Ingress配置的规则动态更新负载均衡器的路由配置。
2)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赖描述。
随着系统越来越复杂,服务间的依赖也越来越多,当实现一个完整的功能时,只依靠内部的服务是无法支撑的,
例如,四个服务组成一个应用,后端依赖一个数据库服务,这就需要一种机制能将数据库服务接入并治理。在当前的云化场景下,这个数据库可以是部署的一个外部服务,也可以是一个RDS的云服务。在托管平台上搭建的应用一般都会访问数据库、分布式缓存等中间服务。
关于这种第三方服务的管理,专门有一种Open Service Broker API来实现第三方软件的服务化,这种API通过定义Catalog、Provisioning、Updating、Binding、Unbinding等标准的接口接入服务,在和Kubernetes结合的场景下,使用Service Catalog 的扩展机制可以方便地在集群中管理云服务商提供的第三方服务。
Istio 可以方便地对网格内部的服务访问进行治理,那么如何对这种网格外的服务访问进行治理呢?从实际需求来看,对一个数据库访问进行管理,比对两个纯粹的内部服务访问进行管理更重要。在Istio中是通过一个ServiceEntry 的资源对象将网格外的服务注册到网格上,然后像对网格内的普通服务一样对网格外的服务访问进行治理。
关于ServiceEntry的配置方式,后续会专门讲到。ServiceEntry是Istio中对网格外部的服务的推荐使用方式,当然也可以选择不治理,直接让网格内的服务访问网格外的服务。
在大多数情况下,在访问网格外的服务时,通过网格内服务的Sidecar就可以执行治理功能,但有时需要一个专门的Egress Gateway 赖支持。出于对安全或者网络规划的考虑,要求网络内所有外发的流量都必须经过这样一组专门节点,需要定义一个Egress Gateway并分配Egress节点,将所有的出口流量都转发到Gateway上进行管理。