在讲解 VirtualService 时,我们注意到,在路由的目标对象 Destination 中大多包含表示 Service子集的 subset字段,这个服务子集就是通过 DestinationRule定义的。本节讲解DestinationRule的用法,不但会讲解如何定义服务子集,还会讲解在DestinationRule上提供的丰富的治理功能。
下面同样以一个简单的配置示例来直观认识 DestinationRule,其中定义了 forecast 服务的两个版本子集v1和v2,并对两个版本分别配置了随机和轮询的负载均衡策略:
DestinationRule经常和VirtualService结合使用,VirtualService用到的服务子集subset在 DestinationRule 上也有定义。同时,在 VirtualService 上定义了一些规则,在DestinationRule上也定义了一些规则。那么,DestinationRule和VirtualService都是用于流量治理的,为什么有些定义在VirtualService上,有些定义在DestinationRule上呢?
为了更好地理解两者的定位、差别和配合关系,我们观察下面一段 Restful 服务端代码,在前面的 Resource 部分将“/forecast”的 POST 请求路由到一个 addForecast 的后端方法上,将“/forecast/hangzhou”的GET请求路由到一个getForecast的天气检索后端方法上:
VirtualService也是一个虚拟Service,描述的是满足什么条件的流量被哪个后端处理。可以对应这样一个Restful服务,每个路由规则都对应其中Resource中的资源匹配表达式。只是在 VirtualService 中,这个匹配条件不仅仅是路径方法的匹配,还是更开放的 Match条件。
而 DestinationRule 描述的是这个请求到达某个后端后怎么去处理,即所谓目标的规则,类似以上Restful服务端代码中addForecast()和getForecast()方法内的处理逻辑。
理解了这两个对象的定位,就不难理解其规则上的设计原理,从而理解负载均衡和熔断等策略为什么被定义在DestinationRule上。DestinationRule定义了满足路由规则的流量到达后端后的访问策略。在 Istio 中可以配置目标服务的负载均衡策略、连接池大小、异常实例驱除规则等功能。在前面Restful的例子中服务端的处理逻辑是服务开发者提供的,类似地,在DestinationRule中这些服务管理策略也是服务所有者维护和管理的。
上面说到的几个配置在DestinationRule中都不仅仅是对一个后端服务的配置,还可以配置到每个子集Subset甚至每个端口上。这也不难理解,在计算机世界里,服务是一个到处被使用的术语,从 IaaS、PaaS、SaaS 这种业务上的服务,到微服务化术语中功能实现模块的服务,其最本源的定义应该是监听在某个特定端口上对外提供功能的服务。DestinationRule配置的策略正是配置到这种端口粒度的服务上。
DestinationRule的数据定义如图3-43所示。
图3-43 DestinationRule的规则定义
DestinationRule上的重要属性如下。
(1)host:是一个必选字段,表示规则的适用对象,取值是在服务注册中心中注册的服务名,可以是网格内的服务,也可以是以ServiceEnrty方式注册的网格外的服务。如果这个服务名在服务注册中心不存在,则这个规则无效。host如果取短域名,则会根据规则所在的命名空间进行解析,方式同3.2.3节 VirtualService的解析规则。
(2)trafficPolicy:是规则内容的定义,包括负载均衡、连接池策略、异常点检查等,是本节的重点内容。
(3)subsets:是定义的一个服务的子集,经常用来定义一个服务版本,如VirtualService中的结合用法。
(4) exportTo:Istio 1.1 在 DestinationRule 上还添加了一个重要字段,用于控制DestinationRule跨命名空间的可见性,这样就可以控制在一个命名空间下定义的资源对象是否可以被其他命名空间下的 Sidecar 执行。如果未赋值,则默认全局可见。“.”表示仅应用到当前命名空间,“*”表示应用到所有命名空间。在Istio 1.1中只支持“.”和“*”这两种配置。
1.流量策略(TrafficPolicy)
从图 3-44 中流量策略的规则定义可以看出,整个 DestinationRule 上的主要数据结构集中在流量策略方面。
图3-44 流量策略的规则定义
流量策略包含以下4个重要配置,后面将依次介绍。
◎ loadBalancer:LoadBalancerSettings类型,描述服务的负载均衡算法。
◎ connectionPool:ConnectionPoolSettings类型,描述服务的连接池配置。
◎ outlierDetection:OutlierDetection,描述服务的异常点检查。
◎ tls:TLSSettings类型,描述服务的TLS连接设置。
此外,流量策略还包含一个PortTrafficPolicy类型的portLevelSettings,表示对每个端口的流量策略。
2.负载均衡设置(LoadBalancerSettings)
本节重点讲解如何配置和使用 Istio 的负载均衡。负载均衡设置的规则定义如图 3-45所示。
图3-45 负载均衡设置的规则定义
simple字段定义了如下几种可选的负载均衡算法。
◎ ROUND_ROBIN:轮询算法,如果未指定,则默认采用这种算法。
◎ LEAST_CONN:最少连接算法,算法实现是从两个随机选择的服务后端选择一个活动请求数较少的后端实例。
◎ RANDOM:从可用的健康实例中随机选择一个。
◎ PASSTHROUGH:直接转发连接到客户端连接的目标地址,即没有做负载均衡。
只要进行如下配置,就可以给一个服务设置随机的负载均衡策略:
一致性哈希是一种高级的负载均衡策略,只对 HTTP有效,因为在实现上基于 HTTP Header、Cookie的取值来进行哈希。负载均衡器会把哈希一致的请求转发到相同的后端实例上,从而实现一定的会话保持。下面通过几个字段描述一致性哈希。
◎ httpHeaderName:计算哈希的Header。
◎ httpCookie:计算哈希的Cookie。
◎ useSourceIp:基于源IP计算哈希值。
◎ minimumRingSize:哈希环上虚拟节点数的最小值,节点数越多则负载均衡越精细。如果后端实例数少于哈希环上的虚拟节点数,则每个后端实例都会有一个虚拟节点。
通过如下配置可以在cookie:location上进行一致性哈希的会话保持:
Istio的数据面Envoy其实提供了更多的负载均衡算法的支持,Istio当前只支持以上几种。
3.连接池设置(ConnectionPoolSettings)
通过连接池管理可以配置阈值来防止一个服务的失败级联影响到整个应用,如图3-46所示是对Istio连接池设置的规则定义,可以看到Istio连接池管理在协议上分为TCP流量和HTTP流量治理。
1)TCP连接池配置(TCPSettings)
TCP连接池可以配置如下三个属性。
◎ maxConnections:表示为上游服务的所有实例建立的最大连接数,默认是 1024,属于 TCP层的配置,对于 HTTP,只用于 HTTP/1.1,因为 HTTP/2对每个主机都使用单个连接。
◎ connectTimeout:TCP连接超时,表示主机网络连接超时,可以改善因调用服务变慢导致整个链路变慢的情况。
◎ tcpKeepalive:设置TCP keepalives,是Istio 1.1新支持的配置,定期给对端发送一个keepalive的探测包,判断连接是否可用。这种0长度的数据包对用户的程序没有影响。它包括三个字段:probes,表示有多少次探测没有应答就可以断定连接断开,默认使用操作系统额配置,在Linux系统中是9;time,表示在发送探测前连接空闲了多长时间,也默认使用操作系统配置,在 Linux 系统中默认是两小时;interval,探测间隔,默认使用操作系统配置,在Linux中是75秒。
图3-46 连接池设置的规则定义
如下所示为forecast服务配置了TCP连接池,最大连接数是80,连接超时是25毫秒,并且配置了TCP的Keepalive探测策略:
2)HTTP连接池配置(HTTPSettings)
对于七层协议,可以通过对应的HTTPSettings对连接池进行更细致的配置。
◎ http1MaxPendingRequests:最大等待 HTTP 请求数,默认值是 1024,只适用于HTTP/1.1 的服务,因为 HTTP/2 协议的请求在到来时会立即复用连接,不会在连接池等待。
◎ http2MaxRequests:最大请求数,默认是 1024。只适用于 HTTP/2 服务,因为HTTP/1.1使用最大连接数maxConnections即可,表示上游服务的所有实例处理的最大请求数。
◎ maxRequestsPerConnection:每个连接的最大请求数。HTTP/1.1和HTTP/2连接池都遵循此参数。如果没有设置,则没有限制。设置为 1 时表示每个连接只处理一个请求,也就是禁用了Keep-alive。
◎ maxRetries:最大重试次数,默认是3,表示服务可以执行的最大重试次数。如果调用端因为偶尔抖动导致请求直接失败,则可能会带来业务损失,一般建议配置重试,若重试成功则可正常返回数据,只不过比原来响应得慢一点,但重试次数太多会影响性能,要谨慎使用。不要重试那些重试了也总还是失败的请求;不要对那些消耗大的服务进行重试,特别是那些不会被取消的服务。
◎ idleTimeout:空闲超时,定义在多长时间内没有活动请求则关闭连接。
HTTP 连接池配置一般和对应的 TCP 设置配合使用,如下配置就是在刚才的 TCP 连接池管理基础上增加对HTTP的连接池控制,为forecast服务配置最大80个连接,只允许最多有800个并发请求,每个连接的请求数不超过10个,连接超时是25毫秒:
3)Istio连接池配置总结
表3-4对比了Istio和Envoy的连接池配置,可以看到两者的属性划分维度不一样:在Istio中根据协议划分为TCP和HTTP;在Envoy中根据属性的业务划分为不同的分组,一部分属于 circuit_breakers 分组,另一部分属于 cluster 分组。另外,Istio 在属性名上区分标识了HTTP/1.1和HTTP/2的配置。
表3-4 Istio和Envoy的连接池配置对比
考虑到如图3-47所示的HTTP/1.1和HTTP/2在语义上的差别,在控制最大请求数时,对 HTTP/1.1使用 maxConnections参数配置,对HTTP/2则使用http2MaxRequests参数配置。
图3-47 HTTP/1.1和HTTP/2在语义上的差别
4.异常实例检测设置(OutlierDetection)
异常点检查就是定期考察被访问的服务实例的工作情况,如果连续出现访问异常,则将服务实例标记为异常并进行隔离,在一段时间内不为其分配流量。过一段时间后,被隔离的服务实例会再次被解除隔离,尝试处理请求,若还不正常,则被隔离更长的时间。在模型上,Istio的异常点检查符合一般意义的熔断模型。
在 Istio 中,其实可以将异常点检查理解成健康检查,但是与传统的健康检查不同。在传统的健康检查中,都是定期探测目标服务实例,根据应答来判断服务实例的健康状态,例如 Kubernetes上的 Readiness或者一般负载均衡器上的健康检查等。这里的健康检查是指通过对实际的访问情况进行统计来找出不健康的实例,所以是被动型的健康检查,负载均衡的健康检查是主动型的健康检查。
可以通过如下参数配置来控制检查驱逐的逻辑。
◎ consecutiveErrors:实例被驱逐前的连续错误次数,默认是 5。对于 HTTP 服务,返回 502、503 和 504 的请求会被认为异常;对于 TCP 服务,连接超时或者连接错误事件会被认为异常。
◎ interval:驱逐的时间间隔,默认值为10秒,要求大于1毫秒,单位可以是时、分、毫秒。
◎ baseEjectionTime:最小驱逐时间。一个实例被驱逐的时间等于这个最小驱逐时间乘以驱逐的次数。这样一个因多次异常被驱逐的实例,被驱逐的时间会越来越长。默认值为30秒,要求大于1毫秒,单位可以是时、分、毫秒。
◎ maxEjectionPercent:指负载均衡池中可以被驱逐的故障实例的最大比例,默认是10%,设置这个值是为了避免太多的服务实例被驱逐导致服务整体能力下降。
◎ minHealthPercent:最小健康实例比例,是Istio 1.1新增的配置。当负载均衡池中的健康实例数的比例大于这个比例时,异常点检查机制可用;当可用实例数的比例小于这个比例时,异常点检查功能将被禁用,所有服务实例不管被认定为健康还是不健康,都可以接收请求。参数的默认值为50%。
注意:在Istio 1.1的异常点检查中增加了minHealthPercent配置最小健康实例。当负载均衡池中的健康实例数的比例小于这个比例时,异常点检查功能将会被禁用。
如下所示为检查4分钟内forecast服务实例的访问异常情况,连续出现5次访问异常的实例将被隔离 10分钟,被隔离的实例不超过 30%,在第 1次隔离期满后,异常实例将重新接收流量,如果仍然不能正常工作,则会被重新隔离,第 2 次将被隔离 20 分钟,以此类推:
5.端口流量策略设置(PortTrafficPolicy)
端口流量策略是将前面讲到的 4 种流量策略应用到每个服务端口上。如图 3-48 所示是端口流量策略的规则定义,可以发现总体和本节前面讲到的TrafficPolicy没有很大差别,一个关键的差别字段就是port,表示流量策略要应用的服务端口。关于PortTrafficPolicy,只要了解在端口上定义的流量策略会覆盖全局的流量策略即可。
图3-48 端口流量策略的规则定义
如下所示为 forecast 服务配置了最大连接数 80,但是为端口 3002 单独配置了最大连接数100:
6.服务子集(Subset)
Subset的一个重要用法是定义服务的子集,包含若干后端服务实例。例如,通过Subset定义一个版本,在 VirtualService 上可以给版本配置流量规则,将满足条件的流量路由到这个 Subset 的后端实例上。要在 VirtualService 中完成这种流量规则,就必须先通过DestinationRule对Subset进行定义。
Subset包含以下三个重要属性。
◎ name:Subset的名字,为必选字段。通过VirtualService引用的就是这个名字。
◎ labels:Subset上的标签,通过一组标签定义了属于这个Subset的服务实例。比如最常用的标识服务版本的Version标签。
◎ trafficPolicy:应用到这个Subset上的流量策略。
前面讲的若干种流量策略都可以在Subset上定义并作用到这些服务实例上。如下所示为给一个特定的Subset配置最大连接数:
将上面的例子稍微修改一下:给forecast服务全局配置100的最大连接数,给它的一个子集v2配置80的最大连接数。那么,forecast服务在v2版本的子集的最大连接数是多少呢?有了前面 PortTrafficPolicy 的覆盖原则,不难理解,根据本地覆盖全局的原则,v2版本的Subset上的配置生效:
当然,这里只定义了流量策略,只有真正定义了流量规则且有流量到这个Subset上,流量策略才会生效。比如在上面这个示例中,如果在VirtualService中并没有给v2版本定义流量规则,则在DestinationRule上给v2版本配置的最大连接数80不会生效,起作用的仍然是在forecast服务上配置的最大连接数100。
关于DestinationRule的典型应用有以下几种。
1.定义Subset
使用DestinationRule定义Subset是比较常见的用法。如下所示为forecast服务定义了两个Subset:
如图3-49所示,通过DestinationRule定义Subset,就可以配合VirtualService给每个Subset配置路由规则,可以将流量路由到不同的Subset实例上,这个过程对服务访问方透明,服务访问方仍然通过域名进行访问。
图3-49 通过DestinationRule定义Subset
2.服务熔断
尽管 Istio 在功能上有异常点检查和连接池管理两种手段,但在使用中一般根据场景结合使用,如下所示为forecast服务配置了一个完整的熔断保护:
假设 forecast 服务有 10 个实例,则以上配置的效果是:为 forecast 服务配置最大 80个连接,最大请求数为800,每个连接的请求数都不超过10个,连接超时是25毫秒;另外,在4分钟内若有某个forecast服务实例连续出现5次访问异常,比如返回5xx错误,则该forecast服务实例将被隔离10分钟,被隔离的实例总数不超过3个。在第1次隔离期满后,异常的实例将重新接收流量,如果实例工作仍不正常,则被重新隔离,第2次将被隔离20分钟,以此类推。
3.负载均衡配置
我们可以为服务及其某个端口,或者某个 Subset 配置负载均衡策略。如下所示为给forecast服务的两个版本Subset v1和v2分别配置RANDOM和ROUND_ROBIN的负载均衡策略:
如图 3-50 所示,这个策略通过 Pilot下发到各个 Envoy,frontend 服务的 Envoy在代理访问forecast服务时对不同的版本执行不同的负载均衡策略:对v1版本随机选择实例发起访问;对v2版本按照轮询方式选择实例发起访问。
图3-50 分版本定义负载均衡策略
4.TLS认证配置
我们可以通过DestinationRule为一个服务的调用启用双向认证,当然,前提是服务本身已经通过Config开启了对应的认证方式。以上配置可以使对于forecast服务的访问使用双向TLS,只需将模式配置为ISTIO_MUTUAL,Istio便可自动进行密钥证书的管理:
如图3-51所示,frontend服务对forecast服务的访问会自动启用双向认证,对forecast服务和frontend服务的代码都无须修改,而且对双方的证书密钥也无须维护。
图3-51 通过DestinationRule配置TLS双向认证