理解 K8s 多集群(上):构建成熟可扩展云平台的核心要素

理解 K8s 多集群(上):构建成熟可扩展云平台的核心要素

理解 K8s 多集群(下):解决方案对比与演进趋势

本文(分上下两部分)介绍了 K8s 多集群的由来以及实现多集群所面临的核心问题,之后分析并探讨了现有的 K8s 多集群方案,最后根据目前实现方案的痛点与挑战,设想了未来的演进趋势。

本篇是上半部分,主要讨论 K8s 多集群的目的、实现多集群所面临的核心问题以及方案。

1. 为什么需要 K8s 多集群?

在探讨为什么需要 K8s 多集群之前,我们首先定义一下什么是 K8s 多集群:

所谓 K8s 多集群,顾名思义就是多个 K8s 集群。企业或组织可能根据自身的需求,例如为了满足隔离性、可用性、合规性或使用成本等,将其应用程序运行在任意一个或多个集群中。此外,在更加成熟的 K8s 多集群中,应用程序实际运行的集群能够动态配置,不同集群间的应用程序也应该支持相互访问。

定义讲完,我们会发现,多集群的概念是被 K8s 限定的,那么问题来了:

1.1 在 K8s 之前,还存在多集群吗?

不论 K8s 是否存在,对应用的隔离性、可用性、合规性或使用成本等需求是持续存在的,因此我们总能听到企业购买或构建的所谓云管平台或称多云管理平台(示例架构如下图)。云管平台正是为了满足企业对应用部署的复杂需求,对接各种不同公/私有云的 API,并尝试对云资源进行抽象,进而尝试将应用运行所需的环境与真实云资源解耦。

然而,由于各种云提供商的 API 都不尽相同,云管平台的主要工作更多的是做 API 集成,并且每多一种云,就需要多进行一次集成(Terraform Provider 的出现极大的缓解了这一问题)。与此同时,随着企业逐步习惯在云管平台上交付应用,也就逐步依赖了云管平台的供应商。

K8s 简化了多云适配

回到现在。在 K8s 为云原生应用提供了统一交付体验的同时,也很大程度上消除了云管平台集成 API 的苦力活。如果企业没有太过复杂的动态调度需求,只需要在每个云上部署一套 K8s,应用就很容易通过原有的流水线在不同云的 K8s 上进行交付。

因此,K8s 多集群的一个主要应用场景就是:多云适配。

1.2 多集群的应用场景

虽然不同企业面临的问题不同,对应用交付的需求也不同,但总体来看可能存在以下几种多集群的常见应用场景:

隔离性:在多租户隔离,开发/测试/生产环境隔离,地区合规性隔离等场景下,相互隔离的应用需要部署在相互隔离的 K8s 集群中。

高可用与故障转移:为了保证应用的高可用性,当主集群出现严重故障时,集群上运行的应用能够迁移至备用集群。集群之间可以互为主备,并通过接入不同的云厂商来降低风险。

单集群规模:在某些大规模场景下,单集群节点数逐步到达上限,集群控制面性能成为瓶颈,将大集群拆分为小集群可以缓解集群性能问题。

弹性突发:将集群也视为弹性资源,在非业务高峰期,应用运行在成本更低的私有云上,一旦出现业务高峰,私有云资源不足时,可以快速在公有云创建集群并调度应用副本,高峰过后再销毁。

地理亲和性:顾名思义,应用部署在多个集群上,而不同集群处于不同的地理位置,这样就能将用户请求根据实际发出的位置,路由到距离最近的应用上。

总结上述场景,落地 K8s 多集群,不外乎是通过赋予业务应用更灵活的弹性,而实现业务的安全性、高可用性与可迁移性,若能叠加集群本身的弹性,则可以真正做到云原生的所谓 “无限资源”。

2. 实现 K8s 多集群管理的核心要素

上一节介绍了企业对 K8s 多集群(后文简称 “多集群”)需求的现实性,假如企业已经决定开始尝试采纳多集群技术,那么势必会面临多集群的管理问题。

对于多集群的管理,存在哪些问题需要解决呢?这一节我们就来探讨实现多集群管理的核心要素:

  • 多集群部署模型
    • 控制面模型:多集群管理控制面所处的位置。
    • 网络模型:集群间如何交互,以及底层的网络连通性设计。
    • 服务注册与发现:集群 A 的应用,依赖集群 B 的服务,这需要实现跨集群的服务注册与发现。
  • 跨集群应用调度
    • 多集群的各种使用场景,本质上都是考虑如何将应用部署在合适的集群上。
    • 跨集群应用调度,需要基于某些调度策略,对应用属性与集群属性进行匹配,以期找到最佳的匹配关系。
  • 应用模型扩展
    • 多集群环境下的应用,其模型需要在规格与状态上进行扩展,将多集群特有的信息加入其中。
    • 需要考虑与单集群应用模型的前向兼容性,以及自定义资源在多集群环境下的扩展。
  • 集群即资源
    • 为了实现更高程度的自动化调度,集群本身也可作为资源来弹性扩缩。需要构建集群生命周期管理的模块,负责集群的创建与删除,并管理集群的加入、逐出。
    • 集群内的资源状态是集群扩缩的前提,通过定义集群资源状态模型,来汇报资源总量以及碎片情况。

2.1 多集群部署模型

企业落地多集群之前,最先要考虑的就是:多集群的部署架构在整体上是什么样子?

控制面模型

想要对多集群进行管理,需要有特定的管理软件来负责集群的加入/逐出,状态管理以及应用调度等,这种管理软件,我们可以称之为多集群的控制面。相应的,实际运行应用的 K8s 集群,我们称之为多集群的数据面。

控制面与数据面是逻辑上的概念,在实际的部署层面看,可以有如下两种情形:

  1. 控制面独占集群资源

如图所示,多集群管理控制面组件,独占整个集群作为 “控制集群”,可以存在多个控制集群以确保控制面高可用。数据面则包含了数个 “工作集群”,每个工作集群内只运行实际的业务应用,所有的多集群管理决策,都在控制集群进行,实际的操作则通过控制集群连接到工作集群来进行。

这种部署架构下,控制面与数据面物理隔离,相互之间影响很小,但控制集群也占用了相对较多的资源,这种架构适用于复杂的,对隔离性、稳定性要求高的大型多集群。

  1. 控制面与数据面共享集群资源

上图所表述的是另一种部署架构,集群之间不区分控制集群还是工作集群,都视为通用集群。控制面组件与业务应用都部署在同一个通用集群内,控制面组件之间通过选举机制来决定主从,主控制面在某个通用集群上发出对所有通用集群的管理决策。

这种部署架构下,控制面与数据面之间存在相互影响的可能,但总体资源成本更低。这种架构适用于小型多集群。

网络模型

实际的场景下,多个集群不论是基于相同的还是不同的云提供商,集群与集群之间通常都存在网络隔离,各自在各自的子网内工作。那么在多集群协作的过程中,网络连通性就成为一大需要解决的问题。

集群之间的网络到底是连通还是隔断,与使用场景高度相关。如果是租户隔离场景,不同租户的集群资源之间网络隔离比逻辑隔离更安全,而在高可用或是弹性突发等场景下,由于业务应用之间存在东西向网络访问,就必须要确保不论应用运行在哪里,互相都能访问通。另外,多集群控制面必须要能和所有集群连通,否则根本无法实施管理决策。

集群之间的互联互通一般可以通过如下两种方式来实现:

  1. 网关路由

    如图所示,网关路由的形态很简单,首先通过在集群中安装网关来打通集群内外,之后任何涉及对其他集群的访问,网关都应当了解访问目的集群的地址(网关地址),并路由数据包至目的集群。通过控制面设置合理的路由策略就可以满足细粒度的隔离场景。

    网关路由的模型非常类似于 Istio 的多网络模型,跨集群业务应用间的网络活动被网关转发代理实现通信,由于跨集群网络其 IP 地址可能存在重复,因此需要通过合理的服务发现和治理机制确保服务名的唯一性。另外,由于服务可能会被暴露在公网,安全性也是非常重要的一个考量点,例如 Istio 的多网络模型中跨网通信要求必须通过 Istio Proxy 并建立 mTLS 连接。

  2. Overlay 网络

    如图所示,多集群间的节点,虽然处于不同的云、不同的 VPC,但都被接入在同一个 Overlay 网络内,以实现网络直连。

    我们知道很多 CNI 插件的工作原理就是在集群内构建 Overlay 网络,如果能对单集群的 Overlay 网络进行扩展,使得多个集群之间共享同一个子网,也就实现了集群间整体的互联互通。通过创建不同的子网,还可满足不同的隔离要求。

    Overlay 网络的本质是隧道技术,通常是在三层网络上构建隧道传输二层网络包来实现虚拟网络。Overlay 网络的优势在于它构建的虚拟扁平网络让上层通信不再依赖复杂的路由策略,但类似 VxLan 的技术,只对网络数据包进行了再封装,并没有任何安全性可言,因此当用于公网间建立隧道的时候会采用加密协议传输,如 IPSecWireGuard 等。

服务发现与治理

当实现了跨集群网络连通后,就需要考虑在应用层面的服务发现与治理问题。在单集群场景下,K8s 通过 DNS + Service 实现了对应用地址的解析和应用访问的治理,因此在多集群场景,最简单直接的方式就是扩展现有的 DNS + Service 模式。

考虑到不同集群内运行的应用是动态变化的,因此假设cluster-0 中的 app-0 创建了 svc-0 ,该 Service 会被 cluster-1 中的应用 app-1 所依赖,那么多集群管理软件就应当及时的将 cluter-0.svc-0 的信息同步到cluster-1 从而让 app-1 能访问 app-0

Multi-Cluster Services API 是 K8s “Multicluster SIG(多集群特别兴趣小组)” 发起的一项标准,它将单集群的 Service 概念扩展到多集群,以实现跨集群的服务发现治理,Multi-Cluster Services API 定义了 “集群组 ClusterSet” 的概念,并定义了如下的两种 CRD 来描述跨集群服务(以下假设集群组中存在两个集群 cluster-0cluster-1):

  • ServiceExport:若cluster-0中的 Service svc-0 期望被暴露给集群组,则在cluster-0 中创建一个与 svc-0 同名的 ServiceExport,这代表svc-0变成了一个 “集群组服务(Clusterset Service)”,在集群组内的所有集群中公开。
  • ServiceImport:多集群管理软件应当及时发现上述创建的 ServiceExport,进而在集群组所有集群中创建 ServiceImport(包括svc-0 所在的cluster-0),这代表一个集群组服务被导入到当前集群。与此同时,多集群管理软件还应在 cluster-1 中创建名为 svc-0 的 Service 并创建绑定实际clustrer-0.svc-0 地址的 EndpointSlice。

因此,cluster-1 中的应用能够直接通过访问 cluster-1.svc-0 来访问到 cluster-0.svc-0 实现了对外部集群服务的访问。

上述方案通过 ServiceExport 和 ServiceImport 的概念,实现了对单集群 DNS + Service 的多集群扩展。

2.2 跨集群应用调度

多集群本质上是为了让应用运行在更合适的位置。应用到底运行在哪里,要考虑的决策点可以有很多,例如高可用、成本、合规性、地理位置等等。因此多集群管理的另一大需要解决的问题就是,跨集群应用该如何灵活的进行调度。

实现跨集群调度的方式有很多,最简单的,可以将不同应用需要运行的位置提前划定好,那么随着 CI/CD 流水线的发布,应用自然而然就部署好了。当然这种调度策略属于一种全静态的人工调度,能用但不够灵活。

就像 K8s 一样,自动化且灵活的调度,依赖运行在集群内的调度器组件,多集群管理也需要考虑设计自动化的调度器。

如下图所示,不同领域的调度器,其调度模型实际上是高度一致的:

调度的过程就是根据规则任务分配资源。这里涉及到三个概念:任务,资源,规则。在多集群管理的语境下,任务就是应用,资源就是集群,而规则是一系列能够影响调度器决策的调度策略。应用与集群属于 K8s 的既有概念,因此设计跨集群应用调度器,调度策略是核心。

调度策略

通过调度策略,我们期望能解决 ”什么样的应用” 需要被调度到 “哪类集群” 的问题。显然,应用有其自身独特的属性集,集群也一样。从属性集的角度看,调度策略问题就可以转化为应用与集群属性集之间的最优匹配问题。

举例说明,应用的属性集可能包括:命名空间、资源依赖、副本数、镜像名、租户归属、应用亲和性/反亲和性、最小资源需求等等,集群的属性集可能包括 AZ、地区(Region)、节点数、已分配 Pod 数、资源总量/余量、污点(Taint)等等。

基于上述属性集的的匹配策略可能包括:

  • 应用的命名空间必须与集群一致
  • 应用需要与亲和性应用绑定在同一个集群
  • 有污点的集群不接受新应用
  • 如有多个集群都满足要求,则平均分配总副本数
  • 部署偏好尽可能平衡集群间的资源
  • 高优先级的应用可以抢占低优先级的应用
  • 应用如果依赖其他资源(例如 Secrets、Volume 等),需要先调度依赖的资源,之后再调度应用本身

借鉴 K8s 的调度实现模型,上述每一种匹配策略都可作为一种决策器,通过检查它所关注的属性来做出调度决策。而决策器又可分为过滤型和打分型,过滤型决策器给出成功或失败的决策结果,打分型决策器给出分数。

进行调度决策时,待调度应用与待选集群的属性集依次通过所有过滤型决策器和打分型决策器,最终找到一个(或一组,考虑多副本高可用)分数最高的集群,调度完成。

2.3 应用模型扩展

传统单集群场景下,应用的定义通常会直接使用 K8s 的原生 API(例如通过 helm 定义的应用包),也正因为 K8s 原生 API 的通用性,从侧面造就了 K8s 成为事实上的行业标准。通过前面两小节,我们了解到在多集群的场景下,对控制与调度以及互联互通都提出了更高的要求。同样的,为了满足多集群管理的需求,传统 K8s 应用的模型也需要进行扩展。

扩展的应用模型设计,有如下三点考量:

规格扩展与状态扩展

应用规格扩展的是应用本身的属性。

根据前文我们了解到,应用调度策略会将与跨集群相关的应用属性纳入决策,典型的例子就包括应用与应用/地域之间的亲和性或反亲和性,以及不同关键等级的应用对资源成本的优先级等等。而在跨集群网络连通性上,应用是否依赖跨集群的其他应用,以及应用自身是否暴露给其他集群等等,也是重要的扩展属性。

规格的扩展可分为两类:限制(constrains)和提示(hints)。

  • 限制(constrains):代表了应用对跨集群管理的强制性要求,如亲和性/反亲和性,最小副本数,污点容忍性(Taints Tolerations)等等
  • 提示(hints):代表了对多集群管理决策与动作的非强制性提示,如优先级,副本分配偏好,资源需求等等

状态扩展主要扩展的是应用在多个集群上的状态。这包括应用实际在每个集群上的副本数,运行健康状况,曾经被调度的历史等等。

前向兼容性

在应用多集群之前,企业通常已经在单集群上拥有了成规模的稳定业务,因此在设计多集群应用模型时,一个重要的考量点就是如何更简单的兼容并迁移原有业务。

假如多集群的应用模型相比传统 K8s API 是破坏性的,这将导致需要修改现存的应用定义才能过渡到多集群下,这种对稳定业务的迁移流程,其成本和风险都是较大的。

因此,多集群应用模型的设计,应该尽量避免修改原有的应用定义,前文提到的规格与状态扩展,都通过独立的 API 进行管理,并通过合理的 Selector 与原始应用进行绑定。

自定义资源对象

现代的 K8s 集群中通常都会运行着大量自定义资源对象来实现各种基础设施层的功能。多集群中对自定义资源对象的模型扩展,可以考虑如下两点要求:

  1. 自定义资源对象的调度分发应该与原生资源对象保持一致
    • CRD 被注册后,可以根据策略自动分发到其他集群
    • CR 可以被多集群管理软件识别,并按需进行调度
  2. 由于自定义资源对象灵活的结构,需要有方法获取到自定义资源对象的规格和状态来实施调度
    • 通过 “惯例” 获取 CR 的规格与状态(如资源要求,副本数,健康度等)
    • 通过用户扩展插件更精准的获取 CR 的规格和状态

2.4 集群即资源

K8s 为应用层提供了标准的容器编排调度能力,而在基础设施层提供了对节点资源的纳管能力,因此随着应用层需求的波动,节点资源也可以灵活的弹性扩缩。

成熟的多集群管理环境,弹性扩缩的不只是节点,集群本身也可以作为一种资源进行灵活的扩缩容。结合前面提到的多集群调度、网络连通和应用资源模型,一旦拥有了集群即资源能力,不论是在业务拓展、合法合规还是成本优化等场景,都可以快速的完成多集群部署与回收,集群即资源是复杂多集群调度的自动化基础。

集群生命周期

既然集群也作为一种资源,那么集群的生命周期也就可以基于 K8s 风格的 API 来进行操作和管理。

例如定义了一个类型为 WorkerCluster 的资源对象以代表一个集群资源(见如下代码段),其中定义了该集群的属性,例如云厂商名,控制面节点数,数据面节点数等等。这时集群即资源的相关控制器就应该基于该描述在对应的云厂商中创建出一个集群。

1
2
3
4
5
6
7
8
9
apiVersion: multi-cluster.demo.io/v1
kind: WorkerCluster
metadata:
name: demo-worker-cluster
spec:
cloudProvider: "aws",
controlPlaneNodes: 3,
dataPlaneNodes: 20,
... ...

而当上述资源描述发生变化(如对数据面节点进行了扩容)时,实际的集群也应该发生变化。显然,如果上述资源被删除,那么对应的集群也就需要被销毁掉。

通过上述例子我们可以发现,对集群生命周期的管理,仍旧存在整合不同云提供商的困难:不同云提供商、以及各种私有云方案,其基础设施的操作 API 都不同,并且除了 K8s 自管方案,公有云都存在代管方案,如 AKS,GKE 等,创建集群时也需要区分。

ClusterAPI 是 K8s “Cluster Lifecycle SIG(集群生命周期特别兴趣小组)” 发起的项目,Cluster API 尝试通过定义标准基础设施 API 来统一集群生命周期管理,各类云厂商自行提供实现了标准 API 的 “Provider” 来支持自动化操作集群资源,由于其官方背景,目前已有数十种 Provider 可供选择(不仅包含 aws 等公有云,还包含了 OpenStack,OCI 等其他方案)。

也许未来 Cluster API 可能会成为集群声明周期管理的标准。

集群资源模型

对集群进行扩缩容的前提是调度器能够准确的获悉集群当前的资源分配情况,不同的集群可能拥有完全不同的资源,其分配偏好差异也很大(例如云上集群与边缘集群),因此需要一种通用的模型来描述集群自身的资源,从而指导调度决策。

资源模型中除了传统的 cpu、memory 外,需要支持多样的自定义资源,如 gpu,storage,ip 配额等等,另外资源模型不能只考虑上报资源汇总,还需考虑可能存在的资源碎片问题。

如下是一种可能的资源模型定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: multi-cluster.demo.io/v1
kind: WorkerCluster
metadata:
name: demo-worker-cluster
... ...
status:
resource:
total:
cpu: "20"
memory: "100GiB"
gpu: "10"
ipPool: "127"
available:
cpu: "3.5"
memory: "1200m"
gpu: "2"
ipPool: "10"
continous:
cpu: "2"
memory: "550m"
gpu: "1"
ipPool: "10"

可以看到,集群资源状态被分成了三类:总数(total),可用数(available),连续可用数(continuous)。其中连续可用数代表资源可分配的最大粒度。对超过最大粒度的资源需求,虽然总可用数能满足需求,但由于资源碎片的原因,实际上已经无法进行调度了。通过这种划分方式,多集群管理软件就能够根据实际的资源情况更精确的下发调度策略,或是对集群资源进行扩缩容。

3. 小结

通过前面的内容,我们了解到:

  • 企业出于隔离性、可用性、合规性等多种原因,会考虑将应用部署在不同的云上,但由于云提供商之间整合成本高,因此多云管理的成本和复杂性都不低。
  • 随着 K8s 逐渐成为云原生的事实标准,基于 K8s 的多集群方案极大降低了多云管理在云资源整合上的成本,因此企业在构建 K8s 多集群方案时,可以更多的从应用需求的角度进行考量。

要更好的使用 K8s 多集群,多集群的管理必不可少,实现多集群管理,其核心要素涉及多集群的部署模型、跨集群应用调度、应用模型的扩展和集群即资源等。

下一篇,我们将通过介绍实际的案例来探究目前开源方案是怎么解决多集群核心问题的,另外也会讨论目前多集群管理遇到的新问题以及可能的演进趋势。

理解 K8s 多集群(上):构建成熟可扩展云平台的核心要素

理解 K8s 多集群(下):解决方案对比与演进趋势