【服务网格】最佳实践在哪里-2:多集群流量管理

简介: 各位,多集群这个场景在服务网格这一块也算是越来越热了。早在1.4版本,istio社区就已经提出了多集群环境下istio的部署模型,提供统一的控制面管理多集群中服务的能力。而最近随着服务网格的配套可观测项目kiali推出v1.69版本,我们更是可以在一个kiali实例中就纵览多集群中的流量规则、流量拓扑与服务详情,多集群的使用体验逐渐完善,利用这个场景玩法的用户也是越来越多了。不过,任何新事物的引入

各位,多集群这个场景在服务网格这一块也算是越来越热了。早在1.4版本,istio社区就已经提出了多集群环境下istio的部署模型,提供统一的控制面管理多集群中服务的能力。而最近随着服务网格的配套可观测项目kiali推出v1.69版本,我们更是可以在一个kiali实例中就纵览多集群中的流量规则、流量拓扑与服务详情,多集群的使用体验逐渐完善,利用这个场景玩法的用户也是越来越多了。

不过,任何新事物的引入都需要有其存在意义,否则便只能说是画蛇添足。要使用服务网格的多集群管理能力,首先要明白:我们为什么需要服务网格来统一管理多集群,多集群能给我们带来什么?

我们可以分步骤地思考这个问题:

(1)多集群业务之间是否存在关联?

我们使用Kubernetes集群+服务网格,最普遍的使用场景当然是在集群中部署我们的业务应用,并对外提供服务访问。那么当两个集群中的业务之间并无关联的情况下,是否有必要去将两个集群都加入到一个服务网格之中呢?

答案应该是“否”,也就是说,如果要将多个集群中的服务用一个服务网格的控制面统一管理,则集群中的服务互相之间应该存在业务上的关联,要么是服务之间存在调用关系,要么是同一个服务在两个集群中都有对应的工作负载。

为什么呢?因为服务网格的控制面是统一的、中心化的。如果将两个集群A和B都加入同一个服务网格实例进行管理,服务网格中部署的流量规则就会在所有集群中生效。

举个极端的例子:集群A和集群B中存在一个相同命名空间、相同名称的Service,但实际上两个同名Service提供的并不是一个服务,二者并无业务上的关联。在这种情况下,服务网格的控制面会将两个Service识别成同一个在多集群环境中部署的Service,针对此Service的调用将会被负载均衡,流量均匀地发往两个集群中的工作负载。这就很可能造成一半的业务流量出现错误。因此如果集群中的服务之间并无业务联系,还是推荐将不同集群分别用不同服务网格的控制面进行管理,以免出现上述的尴尬情况。

(2)服务的工作负载应该如何部署?

在确认了多集群中部署的服务之间应该存在业务关联后,我们需要确认部署方式,即一个业务应用相关的多个服务应该如何在多集群中部署。

一个直观的想法是将一部分服务部署在集群A,一部分服务部署在集群B,并配置服务之间的互相访问连通性,在统一控制面的管理下,多集群也可以实现单集群部署的效果,多集群的服务互相之间可以进行服务发现,并互相进行调用。

但此用法的问题在于:相比单集群部署的服务,这种方式并没有带来更多显而易见的好处,反而徒增了业务架构的复杂性,属实画蛇添足。实际上,由于跨集群调用的延迟肯定是赶不上集群内部调用,用这种方式部署反而会给业务的性能造成拖累。

另一个想法听上去要有意义的多:提供服务的冗余部署,在两个集群中各存在一套相同的微服务部署,每个微服务都在两个集群中部署了自己的工作负载。通过这种方式,两个集群可以互为主备,当一个集群中的某个服务挂掉、甚至整个集群都挂掉的时候,依赖这个服务的工作负载还可以转而去调用另一个集群中的工作负载。

社区中也有着类似的思考,在istio社区的《部署模型》一文中,就提出了多集群部署能够带来哪些新的特性:

您可以将单个网格配置为包括多集群。 在单一网格中使用多集群部署, 与单一集群部署相比其具备以下更多能力:

  • 故障隔离和故障转移:当  cluster-1  下线,业务将转移至  cluster-2
  • 位置感知路由和故障转移:将请求发送到最近的服务。
  • 团队或项目隔离:每个团队仅运行自己的集群。

总结一下可以得出:环境的隔离和故障转移是多集群部署带来的两个重要特性。

在默认情况下,虽然多集群部署带来了环境隔离和故障转移的好处,但集群间调用仍会影响部分请求在调用链上的性能。比如,服务1调用服务2,而服务2在集群A和集群B中都部署了工作负载。由于现在集群A和B都被统一的服务网格控制面管理,实际调用时服务1就会在服务2的多集群工作负载之间做负载均衡,也就是说,将有约一般的请求会是跨集群的调用,对应请求的延时自然也就不可避免地上升了。

集群内流量保持

如上所述,很多时候,跨集群调用其实并不是一个我们在多集群部署下的预期行为,甚至可以说是多集群带来的一种副作用。从1.14版本开始,istio社区提供了集群内流量保持的功能,只需要简单的配置就可以让发往服务的流量保持在集群之中,避免出现非预期的跨集群调用。

服务网格ASM同样支持对集群内流量保持的配置,有关具体配置方法及效果,可以参考https://developer.aliyun.com/article/1172558

鉴于之前已经写过了集群内流量保持的配置方法,这回我们简单看看它是怎么实现的。

集群内流量保持实际对应着MeshConfig中一个名为serviceSettings的选项,其中保存了需要将调用流量保持在集群之内的服务的域名:

serviceSettings:
- settings:
    clusterLocal: true
  hosts:
  - "mysvc.myns.svc.cluster.local"

在代码实现上,Istio实现一个ClusterLocalProvider interface,提供GetClusterLocalHosts方法来随时获取配置了集群内流量保持的服务域名,并通过监听MeshConfig实现域名的实时更新。

// ClusterLocalHosts is a map of host names or wildcard patterns which should only
// be made accessible from within the same cluster.
type ClusterLocalHosts struct {
	specific map[host.Name]struct{}
	wildcard map[host.Name]struct{}
}

// IsClusterLocal indicates whether the given host should be treated as a
// cluster-local destination.
func (c ClusterLocalHosts) IsClusterLocal(h host.Name) bool {
	_, _, ok := MostSpecificHostMatch(h, c.specific, c.wildcard)
	return ok
}
...
// ClusterLocalProvider provides the cluster-local hosts.
type ClusterLocalProvider interface {
    // GetClusterLocalHosts returns the list of cluster-local hosts, sorted in
    // ascending order. The caller must not modify the returned list.
    GetClusterLocalHosts() ClusterLocalHosts
}

这个interface被用于控制面推送服务的cluster和endpoint配置的过程中。以服务endpoint的推送简单举例,Istio会为集群中的每个服务新建一个EndpointBuilder,负责构建该服务的端点信息配置,在新建时会调用推送上下文中的IsClusterLocal方法,这个方法实际就是利用ClusterLocalProvider来判断当前的服务是否被配置了集群内流量保持,返回布尔值并保存在EndpointBuilder中。

func NewEndpointBuilder(clusterName string, proxy *model.Proxy, push *model.PushContext) EndpointBuilder {
    ...
	b := EndpointBuilder{
		clusterName:     clusterName,
		network:         proxy.Metadata.Network,
		proxyView:       proxy.GetView(),
		clusterID:       proxy.Metadata.ClusterID,
		locality:        proxy.Locality,
		service:         svc,
		clusterLocal:    push.IsClusterLocal(svc),
		destinationRule: dr,
		nodeType:        proxy.Type,

		push:       push,
		proxy:      proxy,
		subsetName: subsetName,
		hostname:   hostname,
		port:       port,
		dir:        dir,
	}

在实际构建服务的端点配置时,Istio根据EndpointBuilder中的这个字段来判断是否要选择性地跳过该服务在其它集群中的端点的配置下发。

// build LocalityLbEndpoints for a cluster from existing EndpointShards.
func (b *EndpointBuilder) buildLocalityLbEndpointsFromShards(
	shards *model.EndpointShards,
	svcPort *model.Port,
) []*LocalityEndpoints {
    ...
	// Determine whether or not the target service is considered local to the cluster
	// and should, therefore, not be accessed from outside the cluster.
	isClusterLocal := b.clusterLocal

	shards.Lock()
	// Extract shard keys so we can iterate in order. This ensures a stable EDS output.
	keys := shards.Keys()
	// The shards are updated independently, now need to filter and merge for this cluster
	for _, shardKey := range keys {
		if shardKey.Cluster != b.clusterID {
			// If the downstream service is configured as cluster-local, only include endpoints that
			// reside in the same cluster.
			if isClusterLocal || b.service.Attributes.NodeLocal {
				continue
			}
		}
		endpoints := shards.Shards[shardKey]
    ...

这篇文章中曾提到,Istio本身并不进行服务发现,而是通过对接现成的服务注册中心来获取服务的端点信息,目前最常见的就是对接Kubernetes集群的服务注册中心,获取其中的Service和Endpoint资源。在多集群部署模式下,一个服务网格实例自然是对接了多个集群的服务注册中心,Istio在实现上会将来自不同注册中心的服务信息进行分组,也就是不同的shard,shard的key就是集群的id,一个shard自然就代表了一个集群。

可以看到上面的集群内流量保持实现就是利用了shard的机制。正常情况下,两个集群中的同命名空间、同名服务会被服务网格认为是同一服务,其在多集群中的所有端点会被汇总在一起,下发给多集群中的每个网格代理。而集群内流量保持实际就是在此基础上进行了一个小小改造——只有端点的shard与网格代理的shard是同一个时,才为网格代理添加此端点信息,即:

当为集群1中的网格代理下发服务A的端点时

如果此端点位于集群1:向端点信息列表中追加此端点信息

否则:跳过处理此端点

—————————————— ☕️休息一下的分割线 ——————————————

我们简单回顾一下集群内流量保持机制。其优点显而易见:配置简单、代价小,只需要MeshConfig的一个字段即可实现服务的集群内流量保持效果,利用服务网格对不同服务注册中心的天然区分来大量简化配置。

不过这种做法也可以称之为一把双刃剑。前面提到,故障隔离和故障转移是服务网格统一管理多集群部署的一个重要优势,然而这个优势却无法与当前的集群内流量保持配置进行兼容。原因就在于集群内流量保持实际上是为网格代理抛弃了其它集群中服务的端点信息,从而使得网格代理只能访问本集群内的服务端点;当本集群内的服务发生故障时,网格代理并无能力迅速将服务调用故障转移到该服务在其它集群中的端点,因为这些端点早在下发的时候就被抛弃了!

这也就引出我们的下一个议题——

以泳道模式实现集群内流量保持,并增加故障转移能力

实际上,Istio提供的集群内流量保持配置并不是实现这一能力的唯一手段。从另一个角度来想,集群内流量保持实际上就是以不同集群这个天然的区隔为依据,将应用服务的多套环境进行了隔离,并让流量的调用链只能在相同的环境中传递。实际上,这和我们在《以服务网格实现无侵入的全链路灰度:最佳实践与方法》中讨论的微服务全链路灰度场景是高度相似的:都是在做服务环境的隔离,只不过一个是隔离了服务的不同版本、一个则是隔离了服务在不同集群中的部署。

在全链路灰度场景中,我们曾提到使用最基础的VirtualService和DestinationRule资源,就可以实现泳道模式:通过pod 的 label对两套应用服务的环境进行逻辑上的隔离。在多集群部署下,我们当然也可以做到同样的事情。

首先,我们为多集群中的每个工作负载打上标签,让同一服务在多个集群中的不同工作负载可以互相区分,我们以mocka和mockb两个服务为例(两个服务都在两个集群中各部署一个工作负载),配置这样的DestinationRule,为每个服务手动创建分区:

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: dr-mocka
spec:
  host: mocka
  subsets:
    - labels:
        cluster: cluster-a
      name: cluster-a
    - labels:
        cluster: cluster-b
      name: cluster-b
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: dr-mockb
spec:
  host: mockb
  subsets:
    - labels:
        cluster: cluster-a
      name: cluster-a
    - labels:
        cluster: cluster-b
      name: cluster-b

此例中,我们为服务的工作负载都打上cluster的标签,标签值代表其所属的集群(cluster-a或cluster-b)。

然后我们配置VirtualService,以流量规则的方式限制流量只能在本集群之内流动:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: vs-mockb
spec:
  hosts:
    - mockb
  http:
    - match:
        - sourceLabels:
            cluster: cluster-a
      route:
        - destination:
            host: mockb
            subset: cluster-a
    - match:
        - sourceLabels:
            cluster: cluster-b
      route:
        - destination:
            host: mockb
            subset: cluster-b

可以看到,上述流量规则和我们之前讨论过使用泳道模式进行全链路灰度时配置的规则是几乎一致的,区别只是将代表版本的version标签换成了代表所属集群的cluster标签。

—————————————— ☕️休息,休息一下 ——————————————

上面这个实践同样实现了集群内流量保持的效果,与这个方案相比,网格自带的集群内流量配置的优势就又体现出来了:只需要配置MeshConfig即可,无需给工作负载添加标签,也不用写这么多流量规则。那么这种泳道模式的方案就一无是处了吗?也不尽然。

与集群内流量保持相比,泳道模式有一个优点在于:泳道模式只是做了服务在逻辑上的隔离,实际上、每个网格代理中仍然存储着一个服务在所有集群中的端点信息,可以很方便迅速地实现故障转移。这对于集群内流量保持来说是办不到的,因为服务在其它集群的端点信息从一开始就没有被下发到网格代理中。

在全链路灰度最佳实践一文中我们已经提到:阿里云服务网格ASM已经在VirtualService中支持了开箱即用的流量降级配置能力,我们只需要修改上面的VirtualService,增加一段回退配置即可轻松实现故障转移。

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: vs-mockb
spec:
  hosts:
    - mockb
  http:
    - match:
        - sourceLabels:
            cluster: cluster-a
      route:
        - destination:
            host: mockb
            subset: cluster-a
          fallback:
            target:
              host: mockb
              subset: cluster-b
    - match:
        - sourceLabels:
            cluster: cluster-b
      route:
        - destination:
            host: mockb
            subset: cluster-b
          fallback:
            target:
              host: mockb
              subset: cluster-a

在正常调用情况下,对服务的调用仍然遵循集群内流量保持原则。而当集群内服务发生不可用情况时、后续针对该服务的调用则会迅速转移到服务在另一个集群的端点上。例如:这里配置了针对mockb服务的调用,如果调用方来自cluster-a,则调用mockb在cluster-a中的端点,而如果cluster-a中的mockb服务不可用,则会转而调用cluster-b中的mockb服务端点。

泳道模式+流量降级的方案,在实现了集群内流量保持效果的前提下,也仍然保持了服务网格统一管理多集群部署的各种优势,可说是一种功能范围更为全面的最佳实践。不过,该方案也显而易见地存在配置复杂的缺点,最典型的场景例如:当我们需要为服务配置另一个维度的子集时(比如配置服务的不同版本),这个方案将会让需要配置的服务子集数量膨胀一倍。

比如:

  • 服务A在cluster-a和cluster-b中各部署一个子集
  • 服务A又存在v1和v2两个版本,cluster-a和cluster-b中都部署了服务A的两个版本
  • 此时我们需要为服务A定义4个子集:cluster-a-v1、cluster-a-v2、cluster-b-v1、cluster-b-v2

小结

本文中,我们简要探讨了在使用服务网格统一管理多集群部署时,能够充分利用多集群和服务网格特性的服务部署方式和流量管理最佳实践。并针对实践中的重要部分:集群内流量保持的实现进行了简要梳理。简单来说,集群内流量保持可以通过服务网格的集群内流量保持配置或是通过泳道模式的流量规则来实现。二者的对比如下

集群内流量保持配置

泳道模式

版本支持

有版本支持限制,从社区1.14版本开始支持

无版本限制,是个服务网格基本都能配

配置复杂度

低,只需要简单修改MeshConfig

高,需要针对服务配置流量规则

功能全面性

低,只能保证流量在集群内保持,无法兼容多集群故障转移等自定义场景

高,通过流量规则配置、自定义程度高,可以轻松配置集群之间的故障转移或是其他流量管理场景。

在实际实践过程中,还需要根据两种方案的优缺点以及自身需求对方案进行选择。一般来说,如果我们需求对多集群流量进行诸如故障转移这样的精细化控制,则推荐选择以泳道模式区隔多集群流量;否则,可以选择使用集群内流量保持配置这一简单方法。

可能是东半球最好用的服务网格——阿里云服务网格ASM!https://www.aliyun.com/product/servicemesh

相关文章
|
4月前
|
Prometheus Kubernetes 监控
打造无缝灾备新境界:运用服务网格ASM,将集群外服务无缝融入集群内服务,铸就高可用性坚盾!
【8月更文挑战第2天】随着微服务架构的应用,服务的高可用性变得至关重要。服务网格如阿里巴巴的ASM提供流量管理、服务发现等功能,支撑高可靠服务系统。本文介绍如何利用ASM实现集群外服务作为集群内服务的灾备方案,确保服务连续性。先决条件包括已部署ASM的Kubernetes集群环境及内外部的关键服务副本。通过定义服务条目、配置虚拟服务和目的地规则,可实现自动或手动故障转移。借助ASM的流量管理能力,确保服务高可用性和业务连续性。
54 10
|
4月前
|
Perl
如何利用服务网格ASM使用集群外服务做集群内服务的灾备
本文档指导您如何配置阿里云服务网格(ASM)以实现在多集群环境下,服务间的优先访问及故障转移策略。
116 2
|
6月前
|
Cloud Native 容器 Kubernetes
基于阿里云服务网格流量泳道的全链路流量管理(三):无侵入式的宽松模式泳道
本文简要讨论了使用流量泳道来实现全链路流量灰度管理的场景与方案,并回顾了阿里云服务网格 ASM 提供的严格与宽松两种模式的流量泳道、以及这两种模式各自的优势与挑战。接下来介绍了一种基于 OpenTelemetry 社区提出的 baggage 透传能力实现的无侵入式的宽松模式泳道,这种类型的流量泳道同时具有对业务代码侵入性低、同时保持宽松模式的灵活特性的特点。同时,我们还介绍了新的基于权重的流量引流策略,这种策略可以基于统一的流量匹配规则,将匹配到的流量以设定好的比例分发到不同的流量泳道。
73533 16
基于阿里云服务网格流量泳道的全链路流量管理(三):无侵入式的宽松模式泳道
|
5月前
|
Cloud Native 测试技术 开发者
阿里云服务网格ASM多集群实践(二):高效按需的应用多环境部署与全链路灰度发布
介绍服务网格ASM提出的一种多集群部署下的多环境部署与全链路灰度发布解决方案。
|
域名解析 存储 网络协议
【服务网格】最佳实践在哪里-2.5:多集群服务发现
在《最佳实践在哪里-2:多集群流量管理》中,我们主要探讨了同一套微服务在多集群环境下的冗余部署以及集群内流量保持实现。不过,多集群部署的使用方式也并非只有这一种;根据实际情况,我们也有可能以其它的方式在多集群中部署服务。比如,我们选择多集群部署可能并非是需要它的故障隔离和故障转移能力,而只是希望服务和其依赖的数据库等外部资源部署在同一地域,以缩短调用时延;在这种情况下,我们可能会将包含多个微服务的
【服务网格】最佳实践在哪里-2.5:多集群服务发现
|
7月前
|
负载均衡 安全 网络协议
如何通过计算巢在ACK集群上使用Istio服务网格
本文主要介绍怎么通过计算巢部署Isito服务网格,并介绍了使用示例。
|
Kubernetes API 容器
基于阿里云服务网格流量泳道的全链路流量管理(二):宽松模式流量泳道
基于阿里云服务网格流量泳道的全链路流量管理(二):宽松模式流量泳道
10976 16
|
Kubernetes Cloud Native 安全
基于阿里云服务网格流量泳道的全链路流量管理(一)严格模式流量泳道
灰度发布是一种常见的对新版本应用服务的发布手段,其特点在于能够将流量在服务的稳定版本和灰度版本之间时刻切换,以帮助我们用更加可靠的方式实现服务的升级。
29972 18
|
负载均衡 安全 Cloud Native
[大厂实践] 零配置服务网格与按需集群发现
[大厂实践] 零配置服务网格与按需集群发现
98 0
|
监控 安全 Cloud Native
构建无缝的服务网格体验:分享在生产环境中构建和管理服务网格的最佳实践
构建无缝的服务网格体验:分享在生产环境中构建和管理服务网格的最佳实践
68 0