新闻详情

新闻详情

首页 / 资讯中心 / 详情

Kubernetes调度策略深度解析:从默认调度器到自定义插件实战

发布时间:2026/9/8 2:22:26来源:尧图网络
Kubernetes调度策略深度解析:从默认调度器到自定义插件实战
任何一个用过 Kubernetes 的人十有八九都遇到过 Pod 卡在 Pending 的场面。kubectl get pod一看状态列挂着 Pendingdescribe 一下事件里跳出一句0/3 nodes are available: 1 node(s) didnt match node selector, 2 node(s) had taint...这时候大多数人的第一反应是资源不够但根据我自己排查线上问题的经验这种调度失败里至少有三四成和资源没关系真正的原因藏在调度策略里。这次专门写一篇长文把调度策略这件事彻底讲透从默认调度器怎么选节点开始到怎么用调度框架、亲和性、污点、拓扑分布约束这些手段让 Pod 按你的规则落位最后整理一份线上排障和面试题速查。内容不是教科书式的概念堆砌而是按我实际排查问题时的思路来写。适合刚入门 Kubernetes、准备面试、以及已经被调度问题折磨过几次的运维朋友。1. 调度器工作机制Pod是怎么一步步被选中节点的1.1 从创建Pod到绑定的完整链路Kubernetes 里的默认调度器 kube-scheduler 是一个独立组件通过 API Server 的 Watch 机制监听所有还没有被分配节点的 Pod也就是spec.nodeName为空的 Pod。这些 Pod 会进入调度器的内部调度队列等待被处理。每个 Pod 从进入队列到最终绑定节点要经过两个阶段调度周期和绑定周期。调度周期内调度器从队列中取一个 Pod逐个运行注册好的插件过滤掉不满足条件的节点再给剩余节点打分选出最优节点绑定周期再把 Pod 和节点的绑定关系写回 etcd。kubelet 看到自己的节点名出现在 Pod 的 nodeName 字段后才开始拉镜像、创建容器。有个容易被忽略的细节调度周期是串行执行的同一时刻只会调度一个 Pod绑定周期则是异步并行的。为什么要串行因为每调度一个 Pod 都要基于当前集群资源视图做决策如果多个 Pod 并发调度很容易把同一个节点上的空闲资源重复分配给不同 Pod。等绑定完成后再更新缓存下一次调度就会把资源扣掉。这个设计本质上是在用串行换取资源账本的准确性代价是调度吞吐有上限所以在大规模集群里调度器本身的性能会成为一个瓶颈点。1.2 一次调度决策到底包含哪些动作一个完整的调度决策其实就是一条链路从调度队列中获取下一个待调度的 Pod通过 Filter 插件过滤节点把所有不满足条件的节点排除掉对剩余节点执行 Score 插件打分选出分数最高的节点执行绑定逻辑把 Pod 绑定到这个节点更新调度器内部的缓存供后续 Pod 使用。这里特别提一下缓存机制。调度器不会每次调度都去 API Server 拉取全量节点和 Pod 信息而是自己维护一份缓存。正常情况下缓存更新很快但如果你在一个大规模集群里频繁创建删除 Pod偶发会遇到一种现象调度器认为某个节点还有空闲资源实际节点上已经被其他任务占满了。这种缓存延迟可能造成短时间超卖。遇到这种问题通常不会去动调度器源码而是先排查是不是节点更新事件被 etcd 或 API Server 拖慢了或者干脆给调度器增加 CPU 资源和并发度。2. 默认调度策略拆解默认调度器到底在算什么2.1 过滤阶段不合格节点直接出局调度器第一步做的不是打分而是过滤。这就像招聘先筛简历硬性条件不满足的直接淘汰根本进不了面试环节。默认调度器的过滤插件覆盖了这些常见场景节点是否 Readykubelet 心跳是否正常节点可分配资源是否满足 Pod 的 requestsPod 的spec.nodeSelector是否匹配节点标签节点上的 hostPort 是否和 Pod 声明的端口冲突Pod 和节点之间是否有 NodeAffinity / NodeSelector 约束节点是否有 Pod 不能容忍的污点Pod 和集群里其他 Pod 之间是否有亲和性或反亲和性约束存储卷是否满足节点拓扑比如云盘只能在某个可用区挂载那其他可用区的节点直接被过滤。很多人在手算调度结果时会漏掉资源口径。注意调度器看的资源不是节点 Capacity 总容量而是 Allocatable。Allocatable Capacity - 系统预留(kube-reserved/system-reserved) - eviction-threshold 预留。有些刚接触 Kubernetes 的朋友看到节点内存 64Gi就以为 Pod 能用满 64Gi实际一调kubectl describe node才发现可分配内存只剩 58Gi。如果按 64Gi 去规划容量Pod 很容易落在超卖节点上运行起来就频繁 OOM。2.2 打分阶段怎么挑出“相对最优”节点过滤之后剩下的节点都是“能跑”的但调度器必须选一个最合适的。默认打分主要看这些维度NodeResourcesFit默认倾向于把 Pod 调度到资源使用率较低的节点上也就是负载分散BalancedResourceAllocation让 CPU 和内存的使用比例尽量均衡避免某个节点 CPU 快满了内存还剩一大半ImageLocality如果节点上已经存在 Pod 需要的镜像会得到额外加分因为省去了拉镜像时间NodeAffinity、PodAffinity、TaintToleration 里的“偏好”规则也会参与打分。默认打分是每个插件算出一个 0 到 100 的分数再乘以权重后加总。权重默认大多都是 1但不同版本里 NodeResourcesFit 的算法有差异有的版本还支持通过scoreStrategy配置资源维度。实践里如果你想让某个策略起决定性作用需要调大这个插件的 weight。不过别同时把多个插件权重都调大否则最后得分会变成一锅粥很难定位为什么某个节点中选。2.3 优先级抢占高优先级Pod怎么“挤掉”别人Pod 不是人人平等的Kubernetes 提供了 PriorityClass 来控制 Pod 优先级。调度队列默认按 Priority 从高到低排序高优先级 Pod 永远排在前面。这还不够如果高优先级 Pod 发现当前没有任何节点能容纳它并且启用了抢占Preemption调度器会尝试驱逐一些低优先级 Pod给高优先级 Pod 腾位置。具体流程是在 PostFilter 扩展点寻找“牺牲者”尽量挑优先级低、同时能释放足够资源的 Pod然后给它发送 Eviction 请求。低优先级 Pod 被驱逐后可能会被重新调度也可能一直 Pending。这里必须提醒一句抢占是一把双刃剑。如果高优先级 Pod 本身因为有污点、亲和性等原因一直无法被调度它会反复触发抢占导致集群里的低优先级 Pod 不断被杀死重建整个集群都在抖动。生产环境通常只给核心业务配置 PriorityClass而不是给所有 Pod 都提高优先级。这不是 Kubernetes 的功能问题是运维策略问题。2.4 默认调度器配置长什么样以上这些默认行为其实你都可以通过调度器配置文件显式控制。kube-scheduler 通过--config参数读取一个 KubeSchedulerConfiguration 类型的 YAML 文件。比如你可以禁用某个内置打分插件或者调整某个插件的权重apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - schedulerName: default-scheduler plugins: score: disabled: - name: ImageLocality enabled: - name: MyCustomScorer weight: 2 pluginConfig: - name: MyCustomScorer args: threshold: 10新手最容易搞混的一点这个配置文件是调度器进程自己的配置不是 Pod 的 YAML也不是 kubelet 的配置。经常有人问“我明明改了 Pod 的 affinity 为什么不生效”大概率就是把配置类型搞混了。Pod 里的 affinity 生效在调度器的过滤/打分阶段调度器配置文件里启用什么插件是控制调度底层的开关。3. 从默认到自定义调度框架与扩展点实操3.1 调度框架到底在哪儿“插一脚”Kubernetes 从 1.19 开始把调度器重构为 Scheduling Framework。调度流程被拆分成十几个扩展点你可以在每个扩展点上注册自定义插件。理解这些扩展点是自定义调度的基础。下面这张表可以帮你快速建立全局认知扩展点所在阶段作用典型实现QueueSort调度前决定 Pod 在队列里的排序PrioritySortPreFilter过滤前做前置检查或数据预处理NodeResourcesFit, PodTopologySpreadFilter过滤把不满足条件的节点剔除NodeName, NodeAffinity, TaintTolerationPostFilter过滤后没有可用节点时执行抢占等逻辑DefaultPreemptionPreScore打分前生成供打分阶段使用的相关信息PodTopologySpreadScore打分给每个候选节点打分NodeResourcesFit, ImageLocalityNormalizeScore打分后对插件分数做归一化修正RequestedToCapacityRatioReserve绑定前在节点上预留资源减少调度和实际运行之间的竞态自定义插件常用Permit绑定前可以挂起或批准 Pod 的绑定常用于多租户配额控制PreBind绑定前为绑定做准备比如分配网络 IP自定义插件Bind绑定真正写 Binding 对象DefaultBinderPostBind绑定后清理状态或触发后续动作自定义插件大多数自定义调度需求集中在 Filter、Score、PreScore、Permit 这几个扩展点上。你不需要全部实现只挑自己需要的注册就行。3.2 动手写一个自定义 Filter 插件举一个实际场景某个业务团队要求 Pod 只能被调度到带ssdtrue标签的节点上。虽然这个需求用 nodeSelector 就能实现但为了说明插件怎么写我以它为例。import ( context v1 k8s.io/api/core/v1 k8s.io/kubernetes/pkg/scheduler/framework ) type SsdOnly struct{} var _ framework.FilterPlugin SsdOnly{} func (s *SsdOnly) Name() string { return SsdOnly } func (s *SsdOnly) Filter( ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo, ) *framework.Status { node : nodeInfo.Node() if node.Labels[ssd] ! true { return framework.NewStatus( framework.UnschedulableAndUnresolvable, node missing ssdtrue label, ) } return nil }看到没有你不需要从零实现一个调度器只需要实现framework.Plugin接口再按需要实现对应扩展点接口。编译的时候注意调度器插件不是动态加载的动态库必须把插件代码编译进 kube-scheduler 二进制或者在扩展调度器镜像时一起编译进去。这是因为 Go 的动态插件机制在生产环境并不可靠官方也更推荐直接编译进二进制。3.3 把自定义插件配置到调度器 Profile插件写好后通过 KubeSchedulerConfiguration 的 plugins 字段注册。下面这个配置意思是过滤阶段加上 SsdOnly 插件保留其他默认插件。apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - schedulerName: default-scheduler plugins: filter: enabled: - name: SsdOnly这里有个很容易踩的坑不要随便禁用默认插件。比如你把 NodeResourcesFit 禁用了调度器就不再过滤资源不足的节点Pod 可能会被调度到一个没有足够 CPU 内存的节点上运行后频繁 OOM。默认插件之间存在依赖关系有些插件必须配合使用。你可以启动 kube-scheduler 时加--v6日志级别观察插件被调用的顺序和警告再决定要不要禁用。还有一个多 Profile 机制。KubeSchedulerConfiguration 里可以配置多个 profile每个 profile 拥有独立的调度策略并且通过schedulerName区分。如果某个 Pod 的spec.schedulerName指向了非默认的 schedulerName调度器就会使用对应的 profile。这种方式非常适合“对某一类工作负载使用特殊调度策略”而不是真的再部署一个调度器进程。3.4 什么时候需要独立的自定义调度器调度框架解决了大部分“扩展”需求但有些历史系统或者特殊场景会选择写一个完全独立的调度器自己写一个控制器监听 Pod按自己的算法决定节点然后创建 Binding 对象把 Pod 绑过去。这种做法的好处是完全解耦调度逻辑想怎么写就怎么写坏处是你需要自己处理节点缓存、调度器高可用、失败重试、抢占逻辑、与 kubelet 的兼容等一系列问题工程成本比写插件高一个量级。我个人的建议是优先用调度框架扩展点只有在现有扩展点表达力确实不够的时候再去写独立调度器。比如你想用遗传算法做全局资源编排或者在调度时依赖公司内部的一个资源预测系统这种场景独立调度器才有意义。4. 生产环境调度策略选型与应用组合4.1 什么场景需要动默认调度策略默认调度策略不是包治百病。根据我遇到过的情况下面这几类场景通常需要调整调度策略GPU 资源隔离只有几个节点有 GPU 卡普通任务不应该调度过去数据本地性Pod 最好调度到有对应缓存的节点上省去跨机拉取大文件合规要求某些数据只能运行在特定可用区或者只能在某个机房内成本控制把离线任务填到已经占用的低水位节点上而不是新开空节点高可用微服务多副本尽量打散到不同宿主机、不同可用区。策略选型上我的原则是能用原生字段解决的不要写插件。NodeSelector、亲和性、污点容忍度已经能解决一大半需求。只有当现有表达力不够比如“根据某个自定义业务指标打分”再考虑自定义插件。4.2 亲和性、污点与容忍度组合亲和性分为节点亲和性和 Pod 间亲和性每种又都有 required 和 preferred 两档。required 是硬条件不满足就过滤preferred 是软偏好只影响打分。很多人一开始会把这两档搞混特别是 required 写多了发现 Pod 永远调不上还在那查资源。下面是一个典型组合配置必须调度到带ssdtrue标签的节点同时优先选择某个地域并且尽量不和同 App 的 Pod 跑到同一个节点上。spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: ssd operator: In values: [true] preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 preference: matchExpressions: - key: region operator: In values: [shanghai] podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: topologyKey: kubernetes.io/hostname labelSelector: matchLabels: app: nginx污点Taint和容忍度Toleration的组合通常用来做节点隔离。给 GPU 节点加上gputrue:NoSchedule污点后只有带对应容忍度的 Pod 才能调度上去。这时候如果再配一个 nodeAffinity要求 Pod 必须选择 GPU 节点就能做到双重保险普通 Pod 没容忍度进不来带容忍度的 Pod 如果没有亲和性也不会被随机调度到 GPU 节点上。4.3 拓扑分布约束将多副本真正打散如果只是要求两个副本不在同一节点podAntiAffinity就够了。但要表达“每个可用区最多两个 Pod”就需要topologySpreadConstraints。这个功能非常实用尤其是做多可用区高可用的时候。topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: nginxtopologyKey指定按哪个节点标签划分拓扑域maxSkew1表示各拓扑域之间符合条件的 Pod 数量差距最多为 1whenUnsatisfiable有两种选择DoNotSchedule表示过滤阶段直接不调度ScheduleAnyway表示只打分调度不了也无所谓。这个能力比单纯的反亲和性更精细尤其适合在多个可用区之间均匀分布副本避免某个可用区故障时把所有副本都带走。注意一点topologySpreadConstraints 统计的是 labelSelector 匹配的 Pod 在同一个 topologyKey 下的分布它关心的是“符合条件的 Pod”是否均匀和节点的总负载无关。你在评估结果的时候要把这一点想清楚否则容易得出错误的结论。4.4 一个典型的多维组合案例假设我有一个在线服务要求如下第一只能跑在带ssdtrue的节点上第二必须和日志 Agent 同节点第三尽量不要和其他在线服务负载重叠第四多副本尽量打散到不同可用区。配置思路是这样nodeAffinity 用 required 选 ssd 节点podAffinity 用 required 绑定日志 AgentpodAntiAffinity 用 preferred 分散在线服务topologySpreadConstraints 用 DoNotSchedule 均匀打散到可用区。你会发现这些约束会在调度周期的不同阶段协作required 在 Filter 阶段决定“行不行”preferred 在 Score 阶段决定“好不好”拓扑分布约束在 Filter 或 Score 阶段取决于 whenUnsatisfiable。最终结果是一系列硬约束和软约束的组合而不是靠单个字段一把梭。5. 常见问题与排查技巧实录5.1 Pod一直Pending如何快速定位遇到 Pod Pending我通常按照这个顺序排查kubectl describe pod pod-name -n namespace kubectl get nodes -o wide kubectl get nodes -o custom-columnsNAME:.metadata.name,LABELS:.metadata.labels,TAINTS:.spec.taints kubectl describe node node-name第一步永远是kubectl describe pod看事件里的FailedScheduling信息。如果事件为空说明调度器可能还没处理这个 Pod那就去查 kube-scheduler 的日志和状态如果事件里有0/N nodes are available再看后面跟的具体原因。第二步是检查节点标签、污点、资源可分配量。很多问题到这一步就能定位了。5.2 0/N nodes available 错误信息速查把最常见的几种报错含义整理成了一张速查表排障时直接对照报错片段含义关键排查点node(s) didnt match node selectornodeSelector 或 required NodeAffinity 不满足检查 Pod 的 selector 和节点标签Insufficient cpu / memory请求的资源在节点 Allocatable 上不足看 requests 而不是 limits检查节点资源压力node(s) had taint ... didnt tolerate pod节点有污点但 Pod 没有对应容忍检查 taint 和 tolerationsnode(s) didnt match pod anti-affinity rules反亲和性规则挡住了检查已有 Pod 的标签和 Pod 自身的 affinity 配置node(s) didnt match pod topology spread constraints拓扑分布约束 DoNotSchedule 拦截检查 topologySpreadConstraints 和同类 Pod 分布unbound PersistentVolumeClaimsPVC 还没绑定 PV检查 PV 供给和存储类尤其注意云盘可用区拓扑你可能会发现很多调度失败并不只是资源不够。所以以后看到 Pending 不要先慌先看事件再对照这张表逐项排查。5.3 自定义配置不生效怎么自检如果自定义调度策略没有生效按这几步查确认 kube-scheduler 配置文件确实被加载启动参数里能看到--config...用kubectl -n kube-system logs scheduler-pod --tail200看启动时有没有报错把日志级别调到--v5或--v6看每个扩展点实际调用了哪些插件确认 Pod 的scheduleName和 profile 里的 schedulerName 是否一致如果是自定义插件确认镜像版本和 API Server 版本兼容。实际踩坑中最常见的两个原因是配置文件改了但没重启 kube-scheduler或者 Pod 没有指定 schedulerName默认走了default-scheduler而自定义插件配置在另一个 profile 上。这两点能解决一半“没生效”的问题。5.4 面试里调度策略必问题这里顺手整理几个 Kubernetes 调度相关的面试高频题答案也一并写了你可以拿来自测Pod 的调度顺序由什么决定默认情况下调度队列通过 PrioritySort 插件按 PriorityClass 排序优先级相同再按进入队列的顺序。NodeSelector 和 NodeAffinity 有什么区别NodeSelector 只能做标签精确匹配NodeAffinity 支持 In、NotIn、Exists 等表达式还有 required 和 preferred 两档表达能力更强。为什么调度器只看 Pod 的 Request而不是 LimitLimit 是可突发上限实际资源使用可能远低于 Limit如果用 Limit 判断集群里会留下一大堆无法利用的碎片资源。只有 Guaranteed QoS 场景下 Request 等于 Limit。什么是抢占式调度高优先级 Pod 无法被调度时调度器会尝试驱逐低优先级 Pod给高优先级 Pod 腾出资源。自定义调度器有几种实现方式调度框架插件、多 Profile 配置、独立调度器三种。调度周期和绑定周期是串行还是并行调度周期串行绑定周期并行。这些题目本身不难但如果你能把 Filter 和 Score 阶段的常见插件名说出来面试官通常会认为你有实战经验而不是只背过概念。我自己在线上处理调度问题的时间不算短最大的感受是调度策略的坑八成不是“默认策略不够好”而是配置组合太随心所欲。每加一个约束都要想清楚它到底是在过滤阶段起作用还是在打分阶段起作用。否则你就会见到一种情况Pod 一直没被调度而事件里只留下一句没头没尾的0/N。真正的解法是建立一套复用规范把基础硬约束放到节点亲和性和污点里弹性偏好放在 preferred 和权重里自定义插件只控制在少数几个必要场景。这样集群既好理解也好排障。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

全志平台GT9xx触摸屏驱动适配与调试全攻略 2026/9/8 3:01:32

全志平台GT9xx触摸屏驱动适配与调试全攻略

简介:面向嵌入式Linux开发者及触控驱动维护者,全志平台GT9XX触摸屏驱动程序资源包聚焦全志R16平台与input子系统,覆盖驱动加载、设备树匹配、触摸数据上报、电源管理等多个开发环节,重点解决触摸芯片驱动移植与调试难题。资源共25…

阅读更多 →
《创新者的窘境》核心拆解:为什么大公司会被颠覆? 2026/9/8 3:01:32

《创新者的窘境》核心拆解:为什么大公司会被颠覆?

先把丑话说在前面:这本书我翻过不下五遍,每次以为自己读懂了,过一阵子再翻一页,还是会冒出冷汗。1997年出版,二十多年过去,书里的案例从硬盘换成了手机、汽车、芯片,但剧本几乎没变过——大公司…

阅读更多 →
Android属性服务PropertyService源码解析:从setprop到Binder全链路 2026/9/8 3:01:32

Android属性服务PropertyService源码解析:从setprop到Binder全链路

1. PropertyService 是什么,为什么值得读源码先交代一个背景:PropertyService(属性服务)是 Android 系统里最“不起眼”却最核心的系统服务之一,运行在 system_server 进程中,通过 Binder 对外提供系统属性…

阅读更多 →
SD卡选购与验证:UHS-I、U3、V30与4K视频写入需求全解析 2026/9/8 3:01:32

SD卡选购与验证:UHS-I、U3、V30与4K视频写入需求全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
HackBGRT 1.5.1完全指南:用UEFI方式替换Windows开机Logo 2026/9/8 3:01:32

HackBGRT 1.5.1完全指南:用UEFI方式替换Windows开机Logo

简介:这是一份用于个性化修改Windows 10开机LOGO的工具包,面向希望自定义启动画面的系统爱好者、开发者以及日常用户。HackBGRT 1.5.1可替换默认的BGRT启动标志,让开机过程呈现个人风格。资源共20个文件,结构清晰,包含…

阅读更多 →
嵌入式驱动岗秋招面试十连问:字符设备、总线、中断与并发全覆盖 2026/9/8 2:58:32

嵌入式驱动岗秋招面试十连问:字符设备、总线、中断与并发全覆盖

秋招进入密集面试期,嵌入式软件岗里“驱动开发”方向因为门槛高、坑位稳定,一直是不少人的主战场。面试官问的问题往往不追求背概念,而是看你能不能把内核机制和真实硬件关联起来。这篇文章整理了近期嵌入式驱动岗面试中最常出现的十连问&…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞