ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Karmada 传播策略优先级与抢占机制深入解析:PropagationPolicy Preemption 的设计与实现

Karmada 传播策略优先级与抢占机制深入解析:PropagationPolicy Preemption 的设计与实现 Karmada 传播策略优先级与抢占机制深入解析PropagationPolicy Preemption 的设计与实现【免费下载链接】karmadaOpen, Multi-Cloud, Multi-Cluster Kubernetes Orchestration项目地址: https://gitcode.com/GitHub_Trending/ka/karmada导读在多集群编排场景中Karmada 的PropagationPolicyPP与ClusterPropagationPolicyCPP承担着哪些资源被调度到哪些集群的核心决策。传统实现中策略的priority只在资源模板首次匹配时生效一旦资源被某个策略认领即使后续出现更高优先级的策略也无法接管该资源。本文以 Karmada 仓库中的官方设计提案 docs/proposals/scheduling/policy-preemption/README.md 为骨架结合当前仓库中已经落地的 API 定义与 detector 源码实现完整剖析策略抢占Policy Preemption特性的设计动机、API 变更、抢占规则、边界情况、组件改造与可观测性设计。读完本文你将能够理解 Karmada 策略抢占的完整运行机制并掌握preemption: Always字段的正确配置方式与适用约束。为什么需要策略抢占从首次匹配到运行时接管现状优先级只在首次匹配生效当前 Karmada 的PropagationPolicy与ClusterPropagationPolicy已经具备priority概念。当多个策略同时匹配同一个工作负载时Karmada 会选择优先级最高的那个。但关键在于一旦资源模板被某个策略选中它就不能再被后续出现的更高优先级策略抢占。apiVersion: policy.karmada.io/v1alpha1 kind: PropagationPolicy metadata: name: propagation-high-explicit-priority spec: resourceSelectors: - apiVersion: apps/v1 kind: Deployment labelSelector: matchLabels: app: nginx priority: 2 # priority indicates the importance of a policy placement: clusterAffinity: clusterNames: - member1上述策略匹配所有带app: nginx标签的 Deployment并将其调度到member1。假设之后管理员又创建了一个priority: 3的精确匹配策略期望把 nginx 调度到其他集群——在无抢占特性的旧版本中这一期望无法实现。核心动机管理员无法预知未来的扩展场景集群管理员在配置策略时通常无法预见到未来的扩容需求。常见的演进路径是先部署一个宽泛的通用策略设定基础调度策略基线当某个业务需要特殊配置时创建一个个性化策略来接管该应用期望高优先级策略能够抢占低优先级策略完成运行时接管。特性目标Goals该提案明确了三个核心目标扩展 API扩展PropagationPolicy/ClusterPropagationPolicy的 API使高优先级策略能够抢占低优先级策略落地组件实现给出涉及组件主要是karmada-controller-manager的实现思路向后兼容抢占能力默认关闭从旧版本升级到新版本不会产生任何行为变化也不需要额外的适配工作。两个典型的用户故事故事一高优先级 PP 抢占低优先级 PP。管理员负责多集群调度策略的统一管理而工作负载由各业务团队负责。同一命名空间下管理员部署了一个匹配所有资源的通用策略当某业务需要变更调度策略时创建一个更高优先级的策略期望其抢占通用策略。故事二PP 抢占同命名空间下已被 CPP 匹配的资源。管理员部署了一个 CPP 来匹配所有命名空间级资源当某一命名空间下的业务需要变更策略时创建仅匹配该业务的 PP期望 PP 能够接管已被 CPP 匹配的资源。风险与缓解完全向后兼容提案强调抢占特性的向后兼容性使用旧版本 Karmada 构建的系统可以无缝迁移到新版本之前的所有配置YAML都能直接应用到新版本且行为不变——原因就在于抢占功能默认关闭只有显式开启 feature gate 并声明preemption: Always的策略才会触发抢占行为。抢占规则优先级等级与 scope 边界总体优先级顺序文档给出了抢占判断的总体优先级等级Overall priority: high-priority pp low-priority pp high-priority cpp low-priority cpp即高优先级 PP 低优先级 PP 高优先级 CPP 低优先级 CPP。这一规则在 pkg/detector/preemption.go 的注释中被明确为两条抢占规则handlePropagationPolicyPreemption抢占规则为 high-priority PP low-priority PP CPP对应源码注释The preemption rule: high-priority PP low-priority PP CPPhandleClusterPropagationPolicyPreemption抢占规则为 high-priority CPP low-priority CPP。关键语义一相同 priority 不参与抢占基于抢占的优先级不包含隐式优先级——当策略的.spec.priority相同时不触发抢占。以下两个策略分别通过 labelSelector 匹配和 name 精确匹配优先级相同互不抢占# labelselectormatch.yaml apiVersion: policy.karmada.io/v1alpha1 kind: PropagationPolicy metadata: name: propagation-labelselectormatch spec: resourceSelectors: - apiVersion: apps/v1 kind: Deployment labelSelector: matchLabels: app: nginx placement: clusterAffinity: clusterNames: - member2 --- # namematch.yaml apiVersion: policy.karmada.io/v1alpha1 kind: PropagationPolicy metadata: name: propagation-namematch spec: resourceSelectors: - apiVersion: apps/v1 kind: Deployment name: nginx placement: clusterAffinity: clusterNames: - member3注意文档同时强调在抢占启用时对于命名空间级资源PP 会无条件抢占 CPP无论二者优先级高低详见下文PreemptAlways 语义。关键语义二抢占 scope 的收紧重要约束文档明确指出抢占preemption是危险操作容易导致不可预期的后果且在实现过程中面临可扩展性挑战——例如响应抢占时需要列出所有资源模板会对系统性能产生显著影响。同时大多数真实场景是用户希望用 PP 抢占某个特定资源因此资源往往是明确指定的。为此提案将抢占范围严格收窄引入如下约束对于 PPResourceSelector中必须指定name对于 CPP命名空间级资源必须指定namespace和name集群级资源必须指定name。apiVersion: policy.karmada.io/v1alpha1 kind: PropagationPolicy metadata: name: foo spec: resourceSelectors: - apiVersion: apps/v1 kind: Deployment name: nginx这一约束的意义在于抢占候选资源模板可以被精确定位从而避免为响应抢占而遍历全量资源模板显著降低性能开销。从源码看fetchResourceTemplate 正是按ResourceSelector精确抓取单个资源模板若资源不存在已被删除或处于删除中DeletionTimestamp非零则直接跳过抢占。Feature gate实验特性的全局开关该特性是实验特性通过 feature gate 控制全局开关。在 pkg/features/features.go 中定义如下// PolicyPreemption indicates if high-priority PropagationPolicy/ClusterPropagationPolicy could preempt // resource templates which are matched by low-priority PropagationPolicy/ClusterPropagationPolicy. PolicyPreemption featuregate.Feature PropagationPolicyPreemption其默认值为falsePreRelease: featuregate.AlphaAlpha 阶段。在 preemptionEnabled 函数中即使策略声明了preemption: Always只要PropagationPolicyPreemptionfeature gate 未开启也会打印警告日志并拒绝执行抢占func preemptionEnabled(preemption policyv1alpha1.PreemptionBehavior) bool { if preemption ! policyv1alpha1.PreemptAlways { return false } if !features.FeatureGate.Enabled(features.PolicyPreemption) { klog.Warningf(Cannot handle the preemption process because feature gate %q is not enabled., features.PolicyPreemption) return false } return true }边界情况优先级下调从 5 降到 3时的抢占触发文档提出了一个值得注意的 corner case假设有两个策略同时匹配某个资源模板——策略 A 关闭抢占、priority: 5策略 B 开启抢占、priority: 4。当前资源模板被优先级更高的 A 使用。当 A 的优先级从 5 下调到 3 时用户期望 B 能够抢占该资源模板。为了解决这个问题当策略优先级从 A 降到 B 时需要确保优先级落在区间 (B, A] 内的抢占策略获得一次抢占触发。这可以拆解为两个子问题如何识别优先级变化可选方案是通过updateEvent缺点大量计算会影响 list-watch 性能另一个可选方案是通过 webhook缺点需要在策略上额外添加 annotation。如何确定哪些策略需要入队可选方案是维护一个内部 map记录资源模板 ↔ 策略的关系key 可以是开启了抢占的策略value 是匹配到的资源模板。当前仓库的落地实现采用了另一条更直接的路径在 HandleDeprioritizedPropagationPolicy 与HandleDeprioritizedClusterPropagationPolicy中当检测到策略优先级下降时列出同命名空间CPP 为全量的策略筛选出显式声明了preemption: Always且ExplicitPriority()落在 (newPriority, oldPriority) 区间的策略放入按优先级降序排列的优先队列priorityqueue再通过requeuePotentialKeys重新入队以触发后续的抢占流程——用优先级降序队列排序是为了确保更高优先级的策略先被处理避免发生多次抢占if potentialPolicy.Spec.Priority ! nil potentialPolicy.Spec.Preemption policyv1alpha1.PreemptAlways potentialPolicy.ExplicitPriority() newPolicy.ExplicitPriority() potentialPolicy.ExplicitPriority() oldPolicy.ExplicitPriority() { ... sortedPotentialKeys.Enqueue(PriorityKey{ Object: potentialPolicy, Priority: potentialPolicy.ExplicitPriority(), }) }API 变更Preemption字段与PreemptionBehavior枚举PropagationPolicy API 扩展提案在PropagationSpec中新增Preemption字段默认值为Nevertype PropagationSpec struct { // kubebuilder:default0 Priority *int json:priority,omitempty // Preemption declares the behaviors for preempting. // Valid options are Always and Never. // // kubebuilder:defaultNever // kubebuilder:validation:EnumAlways;Never // optional Preemption PreemptionBehavior json:preemption,omitempty }该设计已经在当前仓库的 pkg/apis/policy/v1alpha1/propagation_types.go 中完整落地字段定义与上述提案完全一致Priority类型为*int32默认 0Preemption类型为PreemptionBehavior默认Never枚举约束Always;Never。PreemptionBehavior 语义详解// PreemptionBehavior describes whether and how to preempt resources that are // claimed by lower-priority PropagationPolicy(ClusterPropagationPolicy). // enum type PreemptionBehavior string const ( // PreemptAlways means that preemption is allowed. // // If it is applied to a PropagationPolicy, it can preempt any resource as // per Priority, regardless of whether it has been claimed by a PropagationPolicy // or a ClusterPropagationPolicy, as long as it can match the rules defined // in ResourceSelector. In addition, if a resource has already been claimed by a // ClusterPropagationPolicy, the PropagationPolicy can still preempt it // without considering Priority. // // If it is applied to a ClusterPropagationPolicy, it can only preempt from // ClusterPropagationPolicy, and from PropagationPolicy is not allowed. PreemptAlways PreemptionBehavior Always // PreemptNever means that a PropagationPolicy(ClusterPropagationPolicy) never // preempts resources. PreemptNever PreemptionBehavior Never )PreemptAlways的语义可以拆解为两条作用于 PP 时只要资源能被其ResourceSelector匹配到无论该资源当前被 PP 还是 CPP 认领都能按优先级抢占特别地如果资源已被 CPP 认领PP 可以直接抢占而无需考虑优先级。作用于 CPP 时只能从其他 CPP 手中抢占不允许抢占 PP 已认领的资源。这一点在源码的抢占函数命名与实现上也有清晰对应preemptPropagationPolicyPP 抢占 PP要求policy.ExplicitPriority() claimedPolicy.ExplicitPriority()否则放弃preemptClusterPropagationPolicyDirectlyPP直接抢占 CPP 认领的资源不比较优先级directly preempts resource template claimed by ClusterPropagationPolicy regardless of prioritypreemptClusterPropagationPolicyCPP 抢占 CPP同样要求严格大于时放弃。此外preemptPropagationPolicy 还处理了资源已被策略自身认领的情况通过比对 annotation 中的认领策略名与当前策略名相同则直接返回避免重复抢占自身管理的资源。抢占的判定依据优先级与认领记录三个抢占函数均依赖两个信息源认领记录资源模板上的 annotation。Karmada 用propagationpolicy.karmada.io/name、propagationpolicy.karmada.io/namespace记录认领该资源的 PP用clusterpropagationpolicy.karmada.io/name记录认领的 CPP优先级通过 client-go 的Get从缓存中直接获取被认领策略对象再比较ExplicitPriority()。其中 ExplicitPriority 的实现很简洁若.spec.priority未设置则返回 0否则返回其值。提案还对比了三种记录策略↔资源模板映射关系的可选实现内部 Map 数据结构 / label-annotation 记录 / 用 client-goGet获取策略对象最终出于性能考虑选择了方案三——因为抢占的资源对象已被严格限定必须指定 name从缓存中获取策略的开销很小。完整示例从 nginx 被抢占到重新调度假设策略nginx-propagation已绑定工作负载nginxpriority: 2调度到 member1/member2对应ResourceBinding上的认领标签如下apiVersion: work.karmada.io/v1alpha2 kind: ResourceBinding metadata: labels: propagationpolicy.karmada.io/name: nginx-propagation propagationpolicy.karmada.io/namespace: default name: nginx-deployment namespace: default现在创建一个允许抢占的高优先级策略apiVersion: policy.karmada.io/v1alpha1 kind: PropagationPolicy metadata: name: nginx-propagation-preemption spec: resourceSelectors: - apiVersion: apps/v1 kind: Deployment name: nginx priority: 3 preemption: Always placement: clusterAffinity: clusterNames: - member3nginx-propagation-preemption将抢占nginx-propagationnginx Deployment 最终被调度到member3。抢占完成后ClaimPolicyForObject 会为新策略在资源模板上打上认领标记覆盖旧的认领标签detector 随后根据策略变更驱动资源按新规则重新传播。组件改造karmada-controller-manager 与 karmada-webhookdetector 原有流程回顾提案指出当前当新策略创建/更新时detector的处理顺序为先检查现有对象是否不再被当前策略匹配处理resourceSelectors的变化若是则清理不匹配对象上的标签找到对应的ResourceBinding确保 PropagationPolicy 的更新能同步到 ResourceBinding尝试找到尚未被调度的匹配资源模板。新增抢占逻辑后整体流程如下图中的关键流程为入口为Policy Event先判断是否allow preemption允许抢占Y 分支依次执行查找已被策略绑定的匹配资源模板 → 获取策略类型与优先级 → 比较优先级等级Compare Grade依据 high-priority pp low-priority pp high-priority cpp low-priority cpp 的规则若可以抢占Able to Preempt则覆盖资源模板中的标签并完成抢占Preemption Finish若不可以抢占Disable to Preempt则回落到原流程不允许抢占N 分支直接走原流程——清理未匹配的资源绑定Clean up unmatched resource bindings、更新相关资源绑定Update related resource bindings、尝试查找匹配的孤儿模板Try to find matched orphan template。该流程在当前仓库 pkg/detector/detector.go 中的集成点清晰可见在handlePropagationPolicy/handleClusterPropagationPolicy的处理末尾当preemptionEnabled(policy.Spec.Preemption)为真时分别调用handlePropagationPolicyPreemption/handleClusterPropagationPolicyPreemption而在原有清理与同步逻辑之前也通过 feature gatefeatures.FeatureGate.Enabled(features.PolicyPreemption)判断是否可能触发抢占相关的重排处理。可观测性指标与事件文档要求每次抢占操作都需要有对应的指标记录记录资源模板被抢占的总次数并用事件反映每次抢占操作的细节。例如policy_preemption_total{resultsuccess} 4该设计已在仓库中落地为 Prometheus 计数器 policy_preemption_total// CountPolicyPreemption records the numbers of policy preemption. func CountPolicyPreemption(err error) { policyPreemptionCounter.WithLabelValues(utilmetrics.GetResultByError(err)).Inc() }计数器的 label 为result取值success/error。在 pkg/metrics/resource_test.go 的单元测试中可以看到其预期输出# HELP policy_preemption_total Number of preemption for the resource template. By the result, error means a resource template failed to be preempted by other propagation policies. Otherwise success. # TYPE policy_preemption_total counter policy_preemption_total{resulterror} 1 policy_preemption_total{resultsuccess} 1与指标配套抢占成功与失败都会在资源模板上产生 Kubernetes 事件。相关事件原因定义在 pkg/events/events.go// EventReasonPreemptPolicySucceed indicates policy preemption of resource template succeed. EventReasonPreemptPolicySucceed PreemptPolicySucceed // EventReasonPreemptPolicyFailed indicates policy preemption of resource template failed. EventReasonPreemptPolicyFailed PreemptPolicyFailed在 preemptPropagationPolicy 等函数中通过defer统一处理成功时记录EventTypeNormalEventReasonPreemptPolicySucceed消息中包含抢占双方策略的命名空间/名称失败时记录EventTypeWarningEventReasonPreemptPolicyFailed消息附带失败原因并同步累加policy_preemption_total指标。karmada-webhook 的额外校验由于抢占的资源对象已被严格限定PP 必须指定 name 等webhook 需要执行额外的校验工作以防止误导性配置例如未指定 name 却声明了preemption: Always的策略。从当前仓库 pkg/webhook/clusterpropagationpolicy/validating.go 的结构可以看出Karmada 为 PropagationPolicy / ClusterPropagationPolicy 均提供了 ValidatingAdmission 级别的准入校验确保非法配置在写入 API Server 之前即被拦截。测试计划如何验证抢占特性提案给出的测试计划包含三个层次回归保障所有现有测试必须通过本特性不引入任何破坏性变更break change新增 E2E 测试覆盖范围包括高优先级 PP/CPP 与低优先级 PP/CPP 之间的抢占PP 与 CPP 之间的抢占抢占被禁用preemption: Never的场景。这些测试覆盖点与源码实现一一对应PP↔PP 抢占对应preemptPropagationPolicyPP 抢占 CPP 对应preemptClusterPropagationPolicyDirectlyCPP↔CPP 对应preemptClusterPropagationPolicy而抢占禁用场景则由preemptionEnabled的双重判断枚举值 feature gate保证。此外pkg/apis/policy/v1alpha1/propagation_helper_test.go 中的TestPropagationPolicy_ExplicitPriority/TestClusterPropagationPolicy_ExplicitPriority用例也验证了优先级读取逻辑的正确性。实战配置要点速览配置项取值说明spec.priority整数默认0策略优先级值越大优先级越高抢占判定仅比较显式优先级spec.preemptionAlways/Never默认Never是否允许该策略抢占已被低优先级策略认领的资源Feature gatePropagationPolicyPreemptionAlpha默认关闭抢占特性的全局开关必须开启且preemption: Always才会生效抢占 scope 约束PP 必须指定nameCPP 命名空间级资源需指定namespacename用于精确锁定候选资源模板避免全量遍历的性能开销抢占等级high-pp low-pp high-cpp low-cppPP 可无条件抢占 CPP命名空间级资源CPP 不能抢占 PP可观测性指标policy_preemption_total{resultsuccess/error} 事件PreemptPolicySucceed/PreemptPolicyFailed每次抢占操作都会记录指标并产生事件总结Karmada 的策略抢占特性通过新增preemption字段与PropagationPolicyPreemptionfeature gate在保持完全向后兼容的前提下赋予了高优先级传播策略运行时接管低优先级策略的能力。其设计在灵活性与安全性之间做了精心权衡抢占范围被严格限定必须指定 name、抢占判定基于显式优先级与类型等级PP CPP、抢占过程全程可观测指标 事件。从当前仓库源码可以看到从 API 类型定义 到 detector 抢占实现再到 指标埋点 与 事件定义该提案的设计已经完整落地。对于希望为不同业务提供差异化调度策略的多集群管理员而言这一特性是构建基线策略 个性化覆盖治理模型的重要基石。【免费下载链接】karmadaOpen, Multi-Cloud, Multi-Cluster Kubernetes Orchestration项目地址: https://gitcode.com/GitHub_Trending/ka/karmada创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表