Istio之流量治理原理

Posted by 淦 Blog on July 14, 2023

概念和原理

Istio流量治理的目标:以基础设施的方式向用户提供各种非侵入的流量治理能力,用户只需关注自己的业务逻辑开发,无须关注通用的服务治理能力。

在控制面会经过如下流程:

(1)管理员通过命令行或者API创建流量规则;

(2)Istio的控制面Pilot将流量规则转换为Envoy的标准格式;

(3)Pilot通过xDS将规则下发给服务网格数据面Envoy。

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

(1)Envoy拦截本地业务进程的Inbound流量和Outbound流量,并解析流量;

(2)在流量经过Envoy时执行接收到的对应的流量规则,对流量进行治理。

负载均衡

Istio数据面代理Envoy执行负载均衡策略,控制面Istiod负责维护服务发现数据。

Istiod将服务发现数据通过Envoy的EDS标准接口下发给Envoy,Envoy根据配置的负载均衡策略选择一个实例转发请求。Istio支持轮询、随机和最小连接数等负载均衡算法。

服务熔断

熔断器一般指故障检测和处理,防止因临时故障或意外而导致系统整体不可用。熔断器最典型的应用场景是防止网络和服务调用故障级联发生,限制故障的影响范围,防止故障蔓延导致系统整体性能下降或雪崩。

熔断主要应用于微服务的分布式调用场景中:在远程调用时,请求在超时前一直挂起,会导致请求链路上的级联故障和资源耗尽;熔断器封装了被保护的逻辑,监控调用是否失败,当连续调用失败的数量超过阈值时,熔断器就会跳闸,在跳闸后的一定时间段内,所有调用远程服务的尝试都将立即返回失败;同时,熔断器设置了一个计时器,当计时到期时,允许有限数量的测试请求通过;如果这些请求成功,则熔断器恢复正常操作;如果这些请求失败,则维持断路状态。

Istio提供了连接池管理和异常点检查,分别对应Envoy的熔断和异常点检查。

Istio在0.8版本之前使用v1alpha1接口,其中专门有个CircuitBreaker配置,包含对连接池和故障实例隔离的全部配置。在Istio 1.1以后的v1alpha3和当前的v1beta1接口中,CircuitBreaker功能被拆分成连接池管理(ConnectionPoolSettings)和异常点检查(OutlierDetection)这两种配置。

(1)在Istio中通过限制某个客户端对目标服务的连接数、访问请求数等,避免对一个服务的过量访问,如果超过配置的阈值,则快速断路请求。还会限制重试次数,避免重试次数过多导致系统压力变大并加剧故障的传播;

(2)如果某个服务实例频繁超时或者出错,在考察的时间段内连续异常的次数超过阈值,则将该实例隔离,避免影响整个服务。

Istio的连接池管理机制为TCP提供了对最大连接数、连接超时时间等的管理方式,为HTTP提供了对最大请求数、最大等待请求数、最大重试次数、每次连接的最大请求数等的管理方式,控制客户端对目标服务的连接和访问,在超过配置时快速拒绝。

通过Istio的连接池管理可以控制frontend对目标服务forecast的访问请求:

(1)当frontend对forecast的请求不超过配置的最大连接数时,放行;

(2)当frontend对forecast的请求不超过配置的最大等待请求数时,进入连接池等待;

(3)当frontend对forecast的请求超过配置的最大等待请求数时,直接拒绝。

Istio提供的异常点检查机制动态地将异常实例从负载均衡池中移除,保证了服务的总体访问成功率。

当连续的错误数超过配置的阈值时,后端实例会被移除。异常点检查在实现上对每个上游服务都进行跟踪,记录服务访问情况。

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

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

故障注入

故障注入是一种评估系统可靠性的有效方法,是使用一种手段故意在待测试的系统中引入故障,测试系统的健壮性和应对故障的能力,例如异常处理、故障恢复等。只有当系统的所有服务都经过故障测试且具备容错能力时,整个应用才满足对韧性的要求。

Istio的故障注入是在服务网格中对特定的应用层协议进行故障注入,虽然在网络访问阶段进行注入,但其作用于应用层,即可模拟应用的故障场景。

对某种请求注入一个指定的HTTP状态码,这样对于客户端来说,就跟服务端发生异常一样。

给特定的服务注入一个固定的延迟,客户端看到的就跟服务端真的响应慢一样。

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

灰度发布

新老版本同时在线,新版本只切分少量流量出来,在确认新版本没有问题后,再逐步加大流量比例,这正是灰度发布要解决的问题。其核心是配置一定的流量策略,将用户在同一个访问入口的流量分配到不同的版本上。

Istio本身并没有关于灰度发布的规则定义,灰度发布只是流量治理规则的一种典型应用。

基于Istio的灰度发布主要通过服务网格数据面代理解析应用协议,执行控制面配置的分流规则,在不同的版本间进行灵活的流量切分。

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

在Istio中除了支持基于流量比例的策略,还支持基于请求内容的灰度策略。比如某个特性是专门为Mac操作系统开发的,则需要在该版本的流量策略中匹配请求方的操作系统。各种请求的头域等请求内容在Istio中都可以作为灰度发布的特征条件,比如为根据头域group的不同取值将请求分发到不同的版本。

故障转移

故障转移是系统韧性设计中的一个基础能力,一般表示在服务器、系统、硬件或网络发生故障不能服务时切换到备用的系统,自动无缝完成故障检测和切换,减少或消除对使用方或最终用户的影响,从而提高整个系统对外的可用性。

在Istio中可以以非侵入的方式实现故障转移过程,在控制面配置故障转移策略,服务网格数据面拦截到源服务对目标服务的请求时,在转发请求的过程中根据故障转移策略将流量转移到配置的服务实例。

还可以根据服务实例上的位置标签对服务实例分组进行优先级排序,标签匹配得越多,说明和源实例越亲和,优先级也相应越高,在故障转移过程中获取的流量就越多。比如将上面的Region、Zone和Subzone三级的结构设置为优先级标签时,三个全匹配的优先级最高,只有Region和Zone匹配的次之,以此类推,全部不匹配的优先级最低。

限流

概念

限流经常需要综合多种因素进行容量规划,在出现流量高峰且超过规划的限流阈值时拒绝服务请求,防止系统过载,保障服务总体的可用性。

Istio限流模型

滑动窗口限流模型

Mixer早期基于Redis实现的固定窗口(FIXED_WINDOW)和滑动窗口(ROLLING_WINDOW)这两种限流算法。

固定窗口是在配置的时间内计数,如果达到配置的上限,就拒绝后面的请求。这种算法有一个非常明显的问题,就是限制不均匀。假如限流1秒内最多有1000个请求,如果在前100ms内有超过1000 个请求,则在后面的900ms内所有请求都会被拒绝。另外,更致命的是固定窗口的两倍配速问题。其限流算法要求仍然是每秒1000个请求,如果在第1秒的前900ms内没有请求,但是在最后100ms内有1000个请求,紧接着在第2秒的前100ms内有1000个请求,则根据国定窗口的限流算法、这种请求是满足限流规则的。但如果考察中间的 200ms,则总共有2000个情求,这超过了每秒1000个请求的限流目标、这个瞬时大流量可能会对后端服务造成超过预期的过载压力。

滑动窗口是把原来大的固定考察时段划分成小的桶,对每个桶都独立计数,将总的考察时段作为一个可以滑动的窗口。在采用了滑动窗口后,相邻200ms的2000个请求的问题就不会发生了。当限流规则是每秒限流1000个请求时,后面的请求因不满足规则而被拒绝。可以配置每个桶的长度,长度越小,控制就越平滑,限流就越精确。但记录的数据越多、对限流后端资源的要求就越高。

令牌桶的限流模型

令牌桶被应用于Istio数据面的本地限流中,通过控制令牌桶内令牌的总数和令牌注入的速率来控制服务的流量。通过这种方式可以将所有请求都比较均匀地分到一个时间段内,并且可以接收一定范围内的突发流量。

(1)初始时,在令牌桶内有一定数量的令牌。

(2)当有匹配限流规则的请求到来时,执行限流检查,每个请求都消费一个令牌。当请求过于频繁时,桶里的令牌被消费完,后面的请求拿不到令牌,请求失败。

(3)桶中每隔固定的间隔就会填充一定量的令牌。在这个填充过程中,如果请求较少,令牌消费较慢,导致桶中令牌满,即令牌总数超过配置的令牌总数,则会丢弃后面填充的令牌。

原理

基于服务端的Redis限流

Istio早期版本的限流基于服务端Mixer来控制,所有服务间的请求都经过中心侧的Mixer的通用检查逻辑,由Mixer调用限流的Adapter来实现限流逻辑,再回复给服务网格数据面执行限流动作。限流的Adapter有基于内存的Memory Quota和基于Redis后端的Redis Quota,其中,前者并不具备高可用能力,在生产中基本不会用到。

###数据面的本地限流 在Telemetry V2版本中,Istio的限流功能直接基于数据面Envoy开发而成。Envoy支持两种类型的限流,分别是本地限流和全局限流。本地限流给每个数据面代理都配置限流规则,只限制在该代理上通过的流量。这种方式比较简单,不依赖任何外部后端。

forecast的两个实例的代理的限流逻辑分别作用于本实例上的流量,彼此间不会有任何关系。

本地限流的特点是简单、直接,但缺点即可能出现限流不均衡的问题。如果一个服务有多个实例,则当实例上的流量不均衡时,有些实例上的流量因为超过限流阈值所以被拒绝,有些实例上的流量不饱和,导致服务整体的流量并未达到容量上限却被限流。比如上面的forecast本来规划的容量是每秒处理2000个请求,当有两个实例时,规划在每个实例上配置每秒限流1000个请求。但在实际环境下,在考察时段内,第1个实例收到800个请求,第2个实例收到1200个请求,会出现第2个实例的200个请求被限流,服务总体在这段时间只处理了1800个请求。

数据面的全局限流

要解决上述本地限流的问题,需要定义针对一个服务的限流,这就是全局限流。Envoy的全局限流基于一个实现了标准限流接口的gRPC限流后端服务,一般依赖 Redis管理全局限流配额。

当有服务访问时,所有相关实例都会调用同一个限流后端,在同一个限流后端的服务上做全局判定,保证单位时间内对目标服务整体的访问量不会超过配置的阈值。

在原理上,Envoy作为数据面代理,既可以作用在服务提供方,对人流量进行限流;也可以作用在服务调用方,对出流量进行限流。在调用方限流是触发限流后调用方不会请求服务端,避免服务端产生额外的消耗。但当服务调用方的服务实例数量较多、难以管理,或者调用方形态比较复杂,特别是某些调用不能通过服务网格代理管理时,基于调用方的限流控制会比较困难。而当在目标服务的代理上限流时,不管从哪个调用方来的请求,都会被新的限流策略统一管理。在实际应用中可以根据业务情况灵活决定是在服务调用方还是在服务提供方执行限流,限流作用在网关处也是很常用的一种实践。在应用场景上,本地限流和全局限流也经常配合使用。

入口流量

概念

从网络或服务访问的视角,在集群入口一般需要提供入口网关将外部流量导入集群。

从应用或微服务的视角,为了独立的业务功能可以被外界使用,一般都会通过一个网关提供服务开放机制,把微服务化后分散的功能以一个内聚的应用视图API提供出来,供最终的服务消费者使用;同时作为应用的单一入口点,接收外部的请求并将流量转发到后端的服务,提供入口的监控、限流、镜像、路由管理等能力。另外,网关内外一般属于不同的安全信任域,网关要对入口流量进行严格管理,包括代理内部服务做证书认证或JWT校验,并在入口做访问授权管理。

Istio入口流量

在Istio中,服务网格外部服务通过Ingress-gateway访问服务网格内部服务。

Ingress-gateway 除了提供了类似Ingress的七层流量接入、入口处的TLS终结,还提供了更多的高级的流量治理功能,包括对匹配流量的重试、重定向、超时、头域操作等,还可以对目标服务进行熔断、限流、分流、镜像,并提供丰富的可观测性和认证授权等安全功能。作为入口代理,Ingress-gateway一般被发布为LoadBalancer类型的Service,接收外部访问,并在将请求转发到服务网格内部服务前执行相关治理功能。

Ingress-gateway和服务网格内部的Sidecar一样,都基于Envoy实现,从Istio的控制面接收配置,执行配置的规则。服务网格入口的配置通过定义一个Gateway的资源对象描述,定义将一个外部访问映射到一组服务网格内部服务上。在Istio早期版本中使用Kubernetes的Ingress来描述服务访问入口,因为Ingress的七层功能限制,Istio在0.8版本的v1alpha3流量规则中引入了Gateway资源对象。Gateway只定义接入点,只做四层到六层的端口、TLS配置等基本功能,VirtualService则定义七层路由等丰富的内容。

在服务网格中除了网关和Sidecar都基于Envoy这个统一的数据面构建能力,在流量配置上也采用统一的API。

Istio统一管理服务网格内外部的流量。forecast既可以通过Ingress-gateway被外部访问,也可以被内部的frontend访问。在forecast上配置的各种流量策略,包括负载均衡、服务熔断、故障注入、灰度发布等,不管流量是来自服务网格外部还是服务网格内部,都会执行统一的策略,经过Ingress-gateway和frontend的Sidear到forecast的流量会执行统一的动作。这时如果入口是另一种七层南北向代理或者负载均衡器入口,则可能会导致配置的流量规则只对内部流量生效,对外部流量不生效。

出口流量

当流量从一个内部的管理域流出到外部的管理域时,其安全、治理、监控的要求也会不同,需要在边界处进行特别的控制,比如控制允许哪些内部服务访问外部服务。

在Istio中,只要外部服务通过一种机制注册到服务网格中,成为服务网格管理对象的一部分,即可像对服务网格内部的普通服务一样,对服务网格外部服务的访问进行管理,实现大多数服务网格治理能力。

forecast对服务网格外部服务的出口流量,在经过Envoy时被拦截并执行配置的治理策略。在这种场景中,只有源服务在服务网格内部,目标服务并没有服务网格代理,所以双向认证、调用链埋点等依赖双端代理的能力对服务网格外部服务的访问不生效。对于有些没有外部治理需求的用户场景,也可以配置直接让服务网格内部服务访问服务网格外部服务,不用注册服务网格外部服务,不做治理。

通常情况下,在访问服务网格外部服务时,通过服务网格内部服务的Sidecar即可执行治理功能,但有时需要有一个专门的出口网关对外部访问流量进行统一管理,比如比较常见的出于对安全或者网络规划的考虑,服务网格内部的节点并不能直接访问服务网格外部服务,所有外发流量都必须经过一组专用节点。类似提供了Ingress-gateway管理服务网格的入口流量,Istio专门提供了Egress-gateway管理服务网格的出口流量。

通过这种方式可以方便地通过服务网格的授权策略控制对服务网格外部服务的访问,可以控制只有forecast访问服务网格外部服务,advertisement对服务网格外部服务的访问会被拒绝。不管这两个服务的IP地址如何变化,授权规则都会保持不变。