Kubernetes 集群调度打分机制是如何运作的?

来源:CDN教程作者:菲律宾程序员头衔:程序员
导读:本期聚焦于菲律宾程序员创作的《Kubernetes 集群调度打分机制是如何运作的?》,敬请观看详情。Kubernetes 调度器把 Pod 分配到节点时,分数并不是凭空出现的。它先通过一组过滤条件筛掉不符合硬性要求的节点,然后对剩余节点逐项打分,最后加权汇总。理解这个打分过程,能帮你解释为什么 Pod 总被调度到看似不忙的节点,或者为什么某些节点一直拿不到高优先级工作负载。打分阶段通常包含资源剩余量、资源均衡度、镜像本地化、Pod 亲和性等维度,每个维度由独立插件计算得分,再乘以配置的权重得到最终排名。如果某一项权重设置不当,即使节点资源充裕也可能落选。掌握这些机制后,你可以在调度器配置中调整插件权重,甚至编写自定义打分插件,让集群按照自己的业务策略分配工作负载。

Kubernetes 调度器并不是简单地把 Pod 随机丢到一个节点上。kube-scheduler 的工作分为两个核心阶段:过滤(Filter)和打分(Score)。过滤阶段会剔除那些资源不足、端口冲突、污点不匹配或者不满足亲和性规则的节点,留下一组候选节点。而打分阶段则负责给这些候选节点排名,最终选择得分最高的节点来运行 Pod。这个打分环节直接决定了集群资源的分配效率和业务负载的分布情况。

Kubernetes 集群调度打分机制是如何运作的?

调度打分在整个流程中的位置

在 kube-scheduler 的调度周期中,每个待调度的 Pod 都会经历一次完整的过滤加打分流程。过滤函数在代码中称为 Filter 插件,它们返回布尔结果,只有所有过滤插件都通过的节点才会进入候选列表。随后调度器对候选节点并行执行所有启用的 Score 插件,每个插件返回一个 0 到 100 之间的整数分数。调取器会根据插件配置中的 weight 对分数进行加权求和,得到节点最终得分。得分相同的情况下,调度器会按照节点的名称字典序进行二次排序,以保证结果可预测。

打分插件并不是一个固定集合,而是通过调度框架(Scheduling Framework)动态注册的。你可以在 KubeSchedulerConfiguration 中显式启用或禁用某个打分插件,也可以调整它的权重。比如默认配置中 NodeResourcesFit 插件的 LeastAllocated 策略权重为 1,而 ImageLocality 的权重也为 1,但如果你希望更重视镜像本地化,可以把 ImageLocality 的权重提高到 2 甚至 3。下面是一个简化后的调度器配置片段,展示了 score 扩展点的插件列表和权重设置:

apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
  plugins:
    score:
      enabled:
      - name: NodeResourcesFit
        weight: 1
      - name: ImageLocality
        weight: 2
      - name: InterPodAffinity
        weight: 1
      disabled:
      - name: NodeResourcesBalancedAllocation

从这个配置可以看出,打分阶段是插件化的。每个插件只负责计算一个维度的得分,最后统一加权。这种设计让调度器的扩展变得非常灵活。如果你发现默认打分行为不符合业务预期,可以调整权重,甚至替换某些插件,而不用修改调度器源码。

核心打分策略及其计算逻辑

在默认调度器配置中,NodeResourcesFit 是最常用的资源打分插件。它根据节点的可分配资源(Allocatable)和已请求资源(Requested)来计算分数。它支持两种策略:LeastAllocated 和 MostAllocated。LeastAllocated 倾向于把 Pod 调度到资源剩余较多的节点,以实现负载均衡;MostAllocated 则相反,倾向于把 Pod 集中到已经使用较多资源的节点,以节省成本或方便缩容。计算公式通常为:对于 CPU 和内存分别计算 (allocatable - requested) / allocatable 的比例,然后取平均值乘以 100。这个比例越高,说明节点剩余资源越多,得分就越高。

NodeResourcesBalancedAllocation 插件则关注 CPU 和内存使用比例之间的差异。如果某个节点 CPU 已经用了 80% 而内存只用了 20%,这个节点就不够均衡,得分会偏低。它的计算方式基于两个比例的绝对差值,差值越小得分越高。这个插件在默认配置中可能被禁用,但对于需要精细化资源管理的集群很有价值。

ImageLocality 插件会根据节点上是否已经存在 Pod 所需的容器镜像来打分。如果镜像已经下载到节点本地,Pod 启动时就不需要重新拉取镜像,从而减少启动延迟。该插件会检查节点本地磁盘上的镜像缓存,对于每个已存在的镜像给予一定加分,总分数不能超过 100。下面用一段 Go 伪代码展示 LeastAllocated 策略的核心计算逻辑:

func leastAllocatedScore(node *v1.Node, pod *v1.Pod) int64 {
    allocatableCPU := node.Status.Allocatable.Cpu().MilliValue()
    allocatableMem := node.Status.Allocatable.Memory().Value()
    requestedCPU := getPodRequestedCPU(pod) + getNodeRequestedCPU(node)
    requestedMem := getPodRequestedMemory(pod) + getNodeRequestedMemory(node)

    cpuRatio := float64(allocatableCPU-requestedCPU) / float64(allocatableCPU)
    memRatio := float64(allocatableMem-requestedMem) / float64(allocatableMem)

    // 平均比例映射到 0-100
    score := int64((cpuRatio + memRatio) / 2 * 100)
    return score
}

这段伪代码忽略了权重和边界处理,但核心思想是计算剩余资源比例并映射到百分制。实际实现中还会考虑节点上已经存在的所有 Pod 的资源请求总和,而不仅仅是当前待调度的 Pod。因此打分结果会随着集群负载的变化而动态变化,这也是为什么同一个 Pod 在不同时间调度可能会落到不同节点上。

除了上述资源类插件,InterPodAffinity 和 NodeAffinity 等插件也会影响打分。InterPodAffinity 会根据 Pod 的亲和性和反亲和性规则,给更符合规则的节点加分。如果 Pod 声明了必须与某些 Pod 同一节点,过滤阶段就会处理硬性要求;而打分阶段处理的是软性偏好,通过 score 权重体现。NodeAffinity 插件则根据节点的标签匹配程度给出分数,不再赘述。

自定义打分插件与调度框架扩展

Kubernetes 调度框架定义了一组扩展点,其中 Score 扩展点就是用于打分。要编写自定义打分插件,需要实现 framework.ScorePlugin 接口,该接口包含 Name、Score 和 ScoreExtensions 三个方法。Name 返回插件名称,Score 返回给定节点对于待调度 Pod 的分数,ScoreExtensions 可以返回一个 NormalizeScore 方法,用于在加权前对原始分数做归一化处理。由于所有插件的原始分数最终会乘以权重并求和,归一化可以避免某些插件天然返回过大数值导致权重失真。

下面是一个极简的自定义打分插件示例,它只根据节点标签 preferred 是否为 true 来给 100 分或 0 分。虽然简单,但完整展示了插件的注册和实现结构:

package main

import (
    "context"
    "fmt"

    v1 "k8s.io/api/core/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/kubernetes/pkg/scheduler/framework"
)

type PreferredNodePlugin struct {
    handle framework.Handle
}

var _ framework.ScorePlugin = &PreferredNodePlugin{}

func (p *PreferredNodePlugin) Name() string {
    return "PreferredNodePlugin"
}

func (p *PreferredNodePlugin) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
    nodeInfo, err := p.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
    if err != nil {
        return 0, framework.NewStatus(framework.Error, err.Error())
    }
    if nodeInfo.Node().Labels["preferred"] == "true" {
        return 100, nil
    }
    return 0, nil
}

func (p *PreferredNodePlugin) ScoreExtensions() framework.ScoreExtensions {
    return nil
}

func New(ctx context.Context, obj runtime.Object, h framework.Handle) (framework.Plugin, error) {
    return &PreferredNodePlugin{handle: h}, nil
}

实现完成后,需要将插件注册到调度器中。通常的做法是在调度器启动时通过一个独立的 main 函数创建 scheduler,并传入自定义插件的注册表。在 KubeSchedulerConfiguration 中,将插件名称添加到 score 扩展点的 enabled 列表,并指定权重,即可让调度器使用自定义打分逻辑。如果插件需要读取配置参数,可以通过 Decode 方法解析自定义的 runtime.Object。

自定义打分插件适合那些无法用标签选择器或资源请求表达的调度策略。例如你可能希望根据节点的实时网络延迟、硬件型号或者特殊安全区域来影响调度,这些信息默认调度器无法感知,但通过扩展插件可以轻松实现。需要注意的是,插件代码运行在调度器进程内,任何性能问题都会直接影响调度吞吐量,因此 Score 方法应当尽量轻量,避免在每个调度周期中执行耗时操作。

调整权重时的调试技巧与常见误区

很多人在调整打分插件权重时会陷入一个误区:把某个插件权重设置得非常大,以为可以完全决定调度结果。实际上由于每个插件分数范围是 0 到 100,权重只是一个乘数,最终得分是所有插件加权后的总和。如果一个插件对大部分候选节点都给出接近 100 的分数,而另一个插件在节点间差异很大,那么高权重的小差异插件可能仍然无法抵消大面积近似相等的分数。正确做法是结合归一化机制,让每个插件的有效区分度均衡。

调试打分行为最直接的方法是查看调度器日志。可以临时提高 kube-scheduler 的日志级别,观察每个候选节点的各插件得分和最终加权总分。调度器在日志中会输出类似 Node scored 的记录,包含插件名称和分数。但生产环境开启详细日志可能产生大量数据,建议只在测试集群中进行。此外还可以使用 scheduler simulator 等工具模拟调度,提前评估参数调整的影响。

还有一个常见误区是忽略了节点资源表示中的可分配资源与实际可用资源的区别。节点可能会预留一部分资源给系统守护进程,通过 kubelet 的 SystemReserved 和 KubeReserved 配置。调度器打分用的 allocatable 已经扣除了这些预留,但如果你手动计算资源比例时使用了节点总容量,就会得到错误的预期。因此理解调度器内部使用的数据口径非常重要。

最后需要注意,打分阶段不是孤立的。过滤阶段已经保证了节点满足硬性条件,打分只需要在符合条件的节点中选出最优者。所以不要试图通过打分插件去实现硬性约束,那样会导致不可预测的行为。硬性约束应该放在 Filter 扩展点或者 Pod 的 affinity、nodeSelector 中。只有软性偏好才适合用打分来表达,这也是调度框架设计时的核心原则。

Kubernetes调度调度打分Node评分修改时间:2026-09-22 20:52:34

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0922/60623.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。