Karmada 多调度组(Multiple Scheduling Group)深度解析:从多组亲和到分级弹性调度的完整实现指南
2026/9/18 6:11:16 网站建设 项目流程

Karmada 多调度组(Multiple Scheduling Group)深度解析:从多组亲和到分级弹性调度的完整实现指南

【免费下载链接】karmadaOpen, Multi-Cloud, Multi-Cluster Kubernetes Orchestration项目地址: https://gitcode.com/GitHub_Trending/ka/karmada

导读

Karmada 是面向多云、多集群场景的 Kubernetes 编排系统,其PropagationPolicy通过.spec.placement.clusterAffinity声明候选集群集合。但单一亲和组在"主集群优先、备集群兜底"的实际诉求面前力不从心。本文以仓库内 multi-scheduling-group 设计提案 为骨架,完整讲解clusterAffinities多调度组 API 的设计动机、调度语义、状态追踪字段与组件改造方案,并结合 propagation_types.go 与 binding_types.go 等源码给出实现级佐证,同时延伸介绍其后续演进——分级弹性溢出的 Overflow Cluster Affinities 能力。读完本文,你将掌握如何用多调度组实现"首选组 + 灾备组"的调度策略,以及多调度组在调度器中的真实执行链路。

一、背景与动机:为什么需要多个调度组

1.1 现有clusterAffinity的局限

在引入多调度组之前,PropagationPolicy只能声明一组候选集群,即.spec.placement.clusterAffinity。如下示例声明了member1member2两个候选集群:

apiVersion: policy.karmada.io/v1alpha1 kind: PropagationPolicy metadata: name: foo spec: resourceSelectors: - apiVersion: apps/v1 kind: Deployment name: foo placement: clusterAffinity: clusterNames: - member1 - member2

clusterAffinity将一组候选集群交给karmada-scheduler,调度器再依据spreadConstraint、过滤插件(filter plugins)等限制条件在候选集群中做出调度决策,结果只有两种:

  • 成功:为引用资源选出一组集群(通常为候选集的子集);
  • 失败:无法选出满足所有限制条件的集群组。

1.2 现实诉求:主备分组与成本优先

集群管理员通常会按分类维度(如云厂商、用途)将集群划分为不同分组,期望把工作负载优先部署到首选分组,当首选分组不满足调度限制(如资源不足)时,再回退到备用分组。Karmada 社区从最终用户处收到了大量类似反馈,例如 issue #780 与 #2085 所描述的诉求。

提案中给出了两个典型用户故事:

  • 成本优先:用户在本地数据中心拥有私有集群,同时也购买了云厂商(如 AWS、Google Cloud)的托管集群。由于托管集群成本更高,用户希望优先部署在私有集群上,把托管集群作为备份;
  • 灾备优先:用户拥有主集群与备份集群,希望应用默认部署在主集群,当主集群因数据中心断电、维护等原因不可用时,自动迁移到备份集群。

1.3 目标与非目标

Goals

  • 扩展PropagationPolicyAPI,使其能够持有多个亲和组声明;
  • 扩展ResourceBindingAPI,用于标注调度器当前正在评估的亲和组;
  • karmada-controller-managerkarmada-webhookkarmada-scheduler等组件提出实现思路。

Non-Goals(明确不在范围内):

  • 同一组内集群的相对优先级:本提案聚焦于引入多个亲和组;同一组内集群的优先级问题由weightPreference.spec.placement.replicaScheduling.weightPreference)或为调度器定义集群打分策略来解决;
  • 调度再平衡(re-balance):用户期望调度器出于其他目的执行额外调度的场景(如讨论 #3069),应由另一份独立提案跟踪。

1.4 兼容性与风险

本提案保持向后兼容:旧版本 Karmada 构建的系统可无缝迁移到新版本,旧配置(YAML)可直接应用于新版 Karmada 且行为不变。

二、API 设计:ClusterAffinitiesClusterAffinityTerm

2.1Placement新增ClusterAffinities字段

提案在.spec.placement中新增字段ClusterAffinities,用于声明多个亲和项(affinity term)。该字段在源码 pkg/apis/policy/v1alpha1/propagation_types.go 中已落地,核心注释完整继承了提案语义:

// Placement represents the rule for select clusters. type Placement struct { // ClusterAffinity represents scheduling restrictions to a certain set of clusters. // If not set, any cluster can be scheduling candidate. // +optional ClusterAffinity *ClusterAffinity `json:"clusterAffinity,omitempty"` // ClusterAffinities represents scheduling restrictions to multiple cluster // groups that indicated by ClusterAffinityTerm. // // The scheduler will evaluate these groups one by one in the order they // appear in the spec, the group that does not satisfy scheduling restrictions // will be ignored which means all clusters in this group will not be selected // unless it also belongs to the next group(a cluster could belong to multiple // groups). // // If none of the groups satisfy the scheduling restrictions, then scheduling // fails, which means no cluster will be selected. // // Note: // 1. ClusterAffinities can not co-exist with ClusterAffinity. // 2. If both ClusterAffinity and ClusterAffinities are not set, any cluster // can be scheduling candidates. // // +optional ClusterAffinities []ClusterAffinityTerm `json:"clusterAffinities,omitempty"` // ClusterTolerations represents the tolerations. // +optional ClusterTolerations []corev1.Toleration `json:"clusterTolerations,omitempty"` // SpreadConstraints represents a list of the scheduling constraints. // +optional SpreadConstraints []SpreadConstraint `json:"spreadConstraints,omitempty"` // ReplicaScheduling represents the scheduling policy on dealing with the number of replicas // when propagating resources that have replicas in spec (e.g. deployments, statefulsets) to member clusters. // +optional ReplicaScheduling *ReplicaSchedulingStrategy `json:"replicaScheduling,omitempty"` }

关键约束有两点:

  1. ClusterAffinitiesClusterAffinity不能共存
  2. 若两者都未设置,则任意集群都可作为调度候选。

2.2ClusterAffinityTerm:命名亲和项

每个亲和项本质上是一个命名的ClusterAffinity

// ClusterAffinityTerm selects a set of cluster. type ClusterAffinityTerm struct { // AffinityName is the name of the cluster group. // +required AffinityName string `json:"affinityName"` ClusterAffinity `json:",inline"` }

ClusterAffinity以内联方式嵌入,因此每个亲和项天然支持clusterNamesexcludedClusterslabelSelectorfieldSelector等所有ClusterAffinity既有的选择子。调度阶段,调度器按亲和项在 spec 中出现的先后顺序逐一评估,若某项不满足限制则继续评估下一项。

2.3 完整配置示例:三个亲和项

提案给出声明 3 个亲和项的配置示例:

apiVersion: policy.karmada.io/v1alpha1 kind: PropagationPolicy metadata: name: nginx spec: resourceSelectors: - apiVersion: apps/v1 kind: Deployment name: nginx placement: clusterAffinities: - affinityName: dc-shanghai clusterNames: - unavailable - affinityName: dc-beijing clusterNames: - member1 - affinityName: dc-hongkong clusterNames: - member2

调度过程如下:

  1. 调度器首先评估名为dc-shanghai的亲和项,尝试从中选出可行集群集合;
  2. 若该亲和项中找不到可行集群,则转向下一项dc-beijing;一旦成功选出可行集群集合,不再继续评估后续项
  3. member1不可用时,借助 Karmada 的 Failover 故障转移能力,调度器先在当前亲和项内寻找替代集群;若失败,再转向dc-hongkong亲和项。

注意:每个亲和项完全独立,调度器在每次调度中只选取一个亲和项;但允许同一个集群出现在多个亲和项中。

三、状态追踪:ResourceBinding新增SchedulerObservedAffinityName

3.1 为什么需要记录"正在评估的亲和项"

多调度组按序评估时,一旦发生重新调度(re-schedule),调度器必须知道上一轮调度基于哪个亲和项,才能从该亲和项继续,而不是每次从头评估。为此提案在ResourceBinding.status中新增字段SchedulerObservedAffinityName

3.2 字段定义与示例

该字段已在 pkg/apis/work/v1alpha2/binding_types.go 中实现:

// ResourceBindingStatus represents the overall status of the strategy as well as the referenced resources. type ResourceBindingStatus struct { // SchedulerObservedGeneration is the generation(.metadata.generation) observed by the scheduler. // If SchedulerObservedGeneration is less than the generation in metadata means the scheduler hasn't confirmed // the scheduling result or hasn't done the schedule yet. // +optional SchedulerObservedGeneration int64 `json:"schedulerObservedGeneration,omitempty"` // SchedulerObservedAffinityName is the affinity terms that // scheduler looking at. // +optional SchedulerObservedAffinityName string `json:"schedulerObservingAffinityName,omitempty"` // Conditions contain the different condition statuses. // +optional Conditions []metav1.Condition `json:"conditions,omitempty"` // AggregatedStatus represents status list of the resource running in each member cluster. // +optional AggregatedStatus []AggregatedStatusItem `json:"aggregatedStatus,omitempty"` }

一个真实的ResourceBinding状态示例如下:

status: aggregatedStatus: - applied: true clusterName: member1 health: Healthy status: availableReplicas: 2 readyReplicas: 2 replicas: 2 updatedReplicas: 2 conditions: - lastTransitionTime: "2023-02-04T09:38:20Z" message: All works have been successfully applied reason: FullyAppliedSuccess status: "True" type: FullyApplied - lastTransitionTime: "2023-02-04T09:38:20Z" message: Binding has been scheduled reason: BindingScheduled status: "True" type: Scheduled schedulerObservedGeneration: 2 SchedulerObservedAffinityName: ds-hongkong

其中SchedulerObservedAffinityName: ds-hongkong表示当前调度结果基于名为ds-hongkong的亲和项,重新调度时调度器应从该亲和项继续评估。

3.3 调度器如何消费该字段

从源码 pkg/scheduler/core/common.go 可以看到,调度器在调度前会检查该字段,并定位到对应的亲和项,从而实现在失败/重调度场景下从"上次停下的位置"继续:

if spec.Placement.ClusterAffinity != nil || spec.Placement.ClusterAffinities == nil || len(status.SchedulerObservedAffinityName) == 0 { // ... } for _, affinity := range spec.Placement.ClusterAffinities { if affinity.AffinityName == status.SchedulerObservedAffinityName { // 从该亲和项继续调度 } }

值得注意的是,ClusterAffinities相关的判断逻辑也出现在 event_handler.go、cluster_affinity.go 及 group_clusters.go 中——多调度组并非只在调度入口生效,而是贯穿了事件处理、亲和过滤与集群分组等多个调度环节。

四、组件改造方案

4.1 karmada-controller-manager

在创建或更新ResourceBinding/ClusterResourceBinding时,需要将PropagationPolicy/ClusterPropagationPolicy中新增的ClusterAffinities(即提案所说的OrderedClusterAffinities同步到 Binding 中,确保调度器拿到的是有序的亲和项列表。

4.2 karmada-scheduler

当前karmada-scheduler只运行单一循环:每次调度只接受一个亲和项,对应 ScheduleAlgorithm 接口 的实现。引入多调度组后,ScheduleAlgorithm接口将被多次调用,每次喂入不同的亲和项,直到调度成功:

  • 第 1 次调用传入第 1 个亲和项,若调度成功则结束;
  • 失败则传入第 2 个亲和项继续,以此类推;
  • 所有亲和项均失败,则本次调度失败。

4.3 karmada-webhook

webhook 需要承担两类额外校验,防止误导性配置:

  1. 互斥校验ClusterAffinities与旧字段ClusterAffinity同时存在没有意义,必须拒绝二者共存的配置;
  2. 唯一性校验:所有亲和项的affinityName必须互不相同。

4.4 适用范围说明

提案主体聚焦于PropagationPolicyResourceBinding的改动,但同样适用于ClusterPropagationPolicyClusterResourceBinding

4.5 测试计划

  • 所有现有测试必须通过,本特性不引入破坏性变更;
  • 新增 E2E 测试覆盖以下场景:
    • Duplicated调度类型下的工作负载传播;
    • Divided调度类型下的工作负载传播;
    • Failover 故障转移场景。

五、备选方案对比:为什么不用propagatePriority

提案记录了一个最早被提出的替代思路(由 PR #842 跟踪):在clusterAffinity中引入新字段propagatePriority来声明集群优先级,并复用 Kubernetes Pod affinity 的术语。示例配置如下:

apiVersion: policy.karmada.io/v1alpha1 kind: PropagationPolicy metadata: name: foo spec: resourceSelectors: - apiVersion: apps/v1 kind: Deployment name: foo placement: clusterAffinity: clusterNames: - member1 - member2 propagatePriority: - weight: 30 preference: matchExpressions: - key: topology operator: In values: - us - weight: 20 preference: matchExpressions: - key: topology operator: In values: - cn

该方案通过复用 Kubernetes 的PreferredSchedulingTerm来表达集群偏好。最终被否决的原因是:PreferredSchedulingTerm依赖 label selector 与 field selector 对集群分组,matchExpressions嵌套层级过深,很容易让配置难以维护。

而最终选定的ClusterAffinities方案语义直观(每个亲和项就是一个命名的ClusterAffinity),配置扁平、可读性强,也便于调度器按序执行。

六、能力演进:Overflow Cluster Affinities(分级弹性溢出)

多调度组方案落地后,社区又在其基础上演进出了Overflow Cluster Affinities能力,详见 overflow-affinities 子提案。二者形成了互补:

  • ClusterAffinities(多调度组):多个候选集群组互斥,用于多集群组隔离(区域/厂商)与故障转移,每次调度最终只选择其中一个候选组(或其子集);
  • Overflow Cluster Affinities(溢出亲和):只有一个候选集群组,组内集群按层级依次使用,用于成本优化与弹性伸缩。

6.1 典型场景

用户同时在自建 IDC GPU 集群与云上 GPU 集群运行推理负载,希望降低 GPU 成本——只有流量峰值时才使用云 GPU,而非常态负载。当流量变化触发 FHPA(FederatedHPA)伸缩时,Karmada 应始终按"IDC 优先扩容、云上优先缩容"的顺序执行,保证 GPU 使用成本效益最大化。

6.2 API 扩展

ClusterAffinityTerm新增可选字段OverflowAffinities,它是一个有序数组,层级越靠后调度优先级越低:

// ClusterAffinityTerm selects a set of cluster. type ClusterAffinityTerm struct { // AffinityName is the name of the cluster group. // +required AffinityName string `json:"affinityName"` ClusterAffinity `json:",inline"` // OverflowAffinities defines additional cluster groups that the scheduler // can progressively include when the primary group (defined by the inline // ClusterAffinity) has insufficient resources. Groups are expanded in order // and contracted in reverse during scale-down. // Can only be used together with the inline ClusterAffinity (the inline // ClusterAffinity serves as the primary/preferred group). // If a cluster appears in multiple OverflowClusterAffinity entries, it is // scheduled according to the first entry in which it appears; subsequent // occurrences of the same cluster are ignored. // +optional OverflowAffinities []OverflowClusterAffinity `json:"overflowAffinities,omitempty"` } // OverflowClusterAffinity represents an overflow tier of candidate clusters. type OverflowClusterAffinity struct { // AffinityName is the name of the cluster group. // +required AffinityName string `json:"affinityName"` ClusterAffinity `json:",inline"` }

6.3 配置示例:本地集群优先、公有云溢出

apiVersion: policy.karmada.io/v1alpha1 kind: PropagationPolicy metadata: name: nginx spec: resourceSelectors: - apiVersion: apps/v1 kind: Deployment name: nginx placement: clusterAffinities: - affinityName: "on-prem" clusterNames: - cluster1 overflowAffinities: - affinityName: "public-cloud" clusterNames: - cluster2 - cluster3 replicaScheduling: replicaDivisionPreference: Weighted replicaSchedulingType: Divided weightPreference: dynamicWeight: AvailableReplicas

调度语义:

  • 调度器先尝试把 Deployment 的全部副本放入最高优先级层级on-premcluster1);
  • 若该组集群无法容纳全部副本,剩余副本按声明顺序逐级溢出overflowAffinities定义的集群组:第一个溢出亲和为第二优先级层级,第二个为第三优先级层级,依此类推;
  • cluster1不可用,则直接使用下一个可用溢出层级(cluster2cluster3)调度;
  • cluster1恢复可用时,不会自动触发工作负载迁移(避免副本抖动),用户需显式使用WorkloadRebalancer资源触发重新调度,或通过调整工作负载副本数间接触发。

上图完整展示了溢出调度的流程:副本优先填满高优先级层级,层级资源耗尽后依次溢出到下一层级,所有层级仍无法容纳全部副本时调度失败。

6.4 调度器五阶段改造要点

溢出能力对karmada-scheduler的改造集中在schedule(Cluster)ResourceBindingWithClusterAffinities流程的 Filter / Select / Assign 三个阶段:

  1. 入口:调度入口schedule(Cluster)ResourceBindingWithClusterAffinities无需改动;
  2. Filter 阶段ClusterAffinity插件需调整为支持溢出亲和——只要集群满足ClusterAffinityTerm任意亲和组的调度条件即可通过过滤;
  3. Score 阶段:无需改动;
  4. Select 阶段:原逻辑优先选择得分最高的集群;溢出场景下需调整为优先选择属于主组及更早溢出层级的集群,保证"高优先级层级被完全利用后,副本才溢出到低优先级层级";
  5. Assign 阶段:按亲和组层级对集群分组;按层级顺序迭代分配剩余副本,每层尽量分配直至资源耗尽或副本分配完毕;层内分配逻辑与现有实现保持一致;全部副本分配完成即成功,遍历完所有层级仍有未分配副本则调度失败。

6.5 webhook 新增校验

  • 限制overflowAffinities必须与内联ClusterAffinity同时使用,不能独立设置;
  • 限制overflowAffinities只能配合Divided副本调度类型与dynamicWeight(如AvailableReplicas)使用——因为只有该模式在分配副本时会考虑成员集群的可用资源,这是分级溢出行为的前提。

6.6 设计取舍:为何不用其他三种方案

子提案还比较了三种被否决的备选方案:

  • Approach 1(在ClusterAffinity上加Supplements:该字段会隐式传导到ClusterAffinities,增加配置与决策复杂度;且溢出调度本质是多集群组场景,嵌入单个ClusterAffinity在语义上错位;
  • Approach 2(引入AffinityStrategy.Mode切换互斥/溢出语义):会破坏现有ClusterAffinities固有的互斥契约,模式开关可能让既有用户困惑;
  • Approach 3(新增与ClusterAffinities平级的PreferredClusterAffinities:溢出亲和只是多调度组能力的补充与子集,新增顶层 API 会过度扩大功能范围、碎片化 API 表面。

最终方案选择在ClusterAffinityTerm内部扩展OverflowAffinities,既保持了多调度组互斥语义不变,又以最小 API 增量补齐了分级弹性伸缩能力。

七、实践要点总结

  1. 语义区分clusterAffinities各亲和项互斥,按声明顺序"选中即止";overflowAffinities各层级共存,按顺序"用完即溢";
  2. 配置约束clusterAffinityclusterAffinities不可共存;affinityName全局唯一;溢出亲和必须与Divided+dynamicWeight搭配;
  3. 状态恢复ResourceBinding.status.schedulerObservingAffinityName记录调度器当前评估的亲和项,是重调度继续执行的关键指针;
  4. 故障转移:多调度组与 Failover 能力 协同——当前亲和项内找不到替代集群时,调度器会转向下一个亲和项;
  5. 版本兼容:本特性向后兼容,旧 YAML 配置无需改动即可在新版本上以原有行为运行;
  6. 源码参照:API 定义见 propagation_types.go 与 binding_types.go,调度器消费逻辑见 common.go、event_handler.go 及 cluster_affinity.go,设计文档见 multi-scheduling-group/README.md 与 overflow-affinities/README.md。

【免费下载链接】karmadaOpen, Multi-Cloud, Multi-Cluster Kubernetes Orchestration项目地址: https://gitcode.com/GitHub_Trending/ka/karmada

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询