资讯动态

Kubernetes调度器深度解析:从过滤打分到亲和污点与抢占实战

发布时间:2026/10/28 3:51:14 来源:尧图企业网站定制
1. 为什么调度器值得单独拎出来聊刚接触容器编排那会儿我对 Scheduler 的印象就停留在“它会把 Pod 塞到某个节点上”这一句话。直到线上出现一批 Pod 长时间 Pending、某些节点 CPU 被打满而另一些节点闲得发慌、关键业务被普通批处理任务挤掉资源我才真正意识到调度器是整个集群资源分配的大脑它的每一个决策都直接决定了集群的稳定性和资源利用率。这篇笔记想做的事情很明确把 Scheduler 的调度策略从“黑盒”拆成“白盒”。我会从调度框架的整体设计讲起把过滤、打分、绑定这几个阶段掰开揉碎再深入到节点亲和、污点容忍、拓扑分布、优先级抢占这些高频策略的实际配置最后落到我踩过的坑和排查手法上。不管你是刚把集群跑起来、还在用默认配置的新手还是已经开始为资源碎片和调度倾斜头疼的进阶用户应该都能从里面找到能直接抄作业的东西。需要先说明一点调度策略的很多默认行为在不同版本之间有过调整我下面讲的内容以当前主流稳定版本的通用行为为准具体参数名和默认值建议你对照自己集群的实际版本来验证不要盲目照搬。这是我在实际运维中养成的习惯——任何配置上线前先在测试集群跑一遍看调度结果是否符合预期再推到生产。2. 调度框架的整体设计与核心思路拆解2.1 调度器到底在解决一个什么问题把集群想象成一栋写字楼节点是一间间办公室Pod 是需要工位的员工。调度器要做的就是在员工入职时给他分配一间合适的办公室。听起来简单但“合适”这两个字背后是一堆约束这间办公室的工位够不够资源是否充足、员工有没有特殊要求比如必须靠窗节点亲和、这间办公室是不是正在装修不能进人污点、同一团队的员工是不是要坐在一起亲和性与拓扑分布、紧急项目的人来了要不要把普通项目的人先请出去优先级抢占。调度器的核心工作就是在这堆约束里找一个可行解并且在可行解里挑一个“最优”的。可行解靠过滤阶段筛出来最优解靠打分阶段排出来。理解了这个二分结构后面所有的策略配置你都能对号入座——要么是在加过滤条件要么是在调打分权重。2.2 调度框架的扩展点设计早期调度器的逻辑是硬编码在代码里的想加一个自定义调度逻辑只能改源码重新编译这在生产环境里几乎不可接受。现在的调度框架把这套流程拆成了一串可插拔的扩展点每个扩展点对应调度流程中的一个阶段内置插件和自定义插件都挂在这些点上。整个调度周期大致是这样一条链路先有一批待调度的 Pod 进入调度队列调度器取出一个 Pod 进入调度周期先跑一轮 PreFilter 做前置检查然后 Filter 阶段把不满足硬性条件的节点全部剔除接着 PostFilter 在没有任何节点通过时做兜底处理比如触发抢占再进入打分环节NormalizeScore 把各项打分归一化最后选出得分最高的节点执行 Reserve 预留资源、Permit 准入检查全部通过后 Bind 绑定。中间还有 PreScore 用来在打分前做准备工作。这个设计的好处非常直接每个阶段职责单一插件之间互不干扰你可以只替换某一个扩展点的实现而不影响其他逻辑。我在给一个模拟项目做自定义调度插件时就只挂了一个 Filter 扩展点做机房级别的硬约束其余全部沿用默认插件改动量极小风险也可控。2.3 过滤与打分的分工逻辑过滤阶段是“一票否决”制。一个节点只要被任何一个 Filter 插件判定为不满足就直接出局不参与后续打分。所以过滤阶段处理的一定是硬性约束资源不够、端口冲突、节点选择器不匹配、污点不容忍、亲和性不满足这些都属于“绝对不能调度上去”的情况。打分阶段则是“优中选优”。所有通过过滤的节点都会被打分分数范围通常是 0 到 100多个打分插件各自给出分数再按权重加权求和。默认情况下资源均衡类插件权重较高因为让各节点资源使用率尽量平均能降低单点过载的风险。打分阶段处理的是软性偏好哪个节点资源更空闲、哪个节点已经有相同服务的镜像减少拉取时间、哪个节点和同服务的其他副本更靠近。这里有个容易被忽略的点过滤和打分的边界不是固定的。比如节点亲和性硬亲和走过滤软亲和走打分。同一个策略配置成硬还是软直接决定了它是“必须满足”还是“尽量满足”。我在实际配置时有个原则——能用软约束解决的绝不用硬约束因为硬约束越多可调度节点越少Pending 的风险越高。2.4 为什么默认策略不能一把梭很多人集群搭好之后就用默认配置一直跑小规模下确实没问题。但集群一旦上量默认策略的短板就暴露了默认打分偏向资源均衡可能导致同一服务的多个副本被分散到各个节点网络延迟敏感的服务反而吃亏默认没有拓扑分布约束机房级故障时可能整个服务全挂默认不区分业务优先级批处理任务可能把在线服务的资源挤占掉。所以进阶实战的核心不是背策略而是理解每个策略解决什么问题、在什么场景下该开、开了之后有什么副作用。下面几节我会逐个拆开讲。3. 核心调度策略的细节解析与实操要点3.1 节点选择器与节点亲和性从简单匹配到表达力升级节点选择器是最朴素的调度约束Pod 里写nodeSelector键值对完全匹配的节点才能被选中。它的优点是简单直观、性能好缺点是表达力太弱只支持精确匹配没法表达“大于”“存在”“不在”这类逻辑。节点亲和性是对节点选择器的表达力升级分requiredDuringSchedulingIgnoredDuringExecution硬亲和和preferredDuringSchedulingIgnoredDuringExecution软亲和两种。硬亲和走过滤阶段软亲和走打分阶段。硬亲和支持In、NotIn、Exists、DoesNotExist、Gt、Lt这些操作符能表达复杂得多的约束。我举个实际场景某类计算任务必须跑在带特定加速卡的节点上但这类节点数量有限不能随便占用。配置上我会用硬亲和锁定节点标签同时用软亲和优先选择当前负载较低的加速卡节点这样既保证了任务一定落在对的硬件上又尽量让负载均衡。affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: hardware-type operator: In values: - accelerator-a preferredDuringSchedulingIgnoredDuringExecution: - weight: 80 preference: matchExpressions: - key: load-level operator: In values: - low注意硬亲和的条件如果集群里没有任何节点满足Pod 会一直 Pending而且事件里只会提示“没有匹配的节点”排查时容易误以为是资源不足。配置硬亲和前务必确认对应标签的节点池有足够容量。3.2 污点与容忍节点侧的主动拒绝机制亲和性是 Pod 主动挑节点污点是节点主动拒绝 Pod方向正好相反。节点打上污点后只有声明了对应容忍的 Pod 才能调度上去。污点有三种效果NoSchedule表示新 Pod 不能调度上来但已运行的不管PreferNoSchedule是软性拒绝NoExecute最狠不仅拒绝新 Pod还会把不容忍的已运行 Pod 驱逐出去。污点容忍最典型的用法是节点池隔离。比如把一批节点专门留给在线服务打上dedicatedonline:NoSchedule的污点只有在线服务的 Pod 声明对应容忍才能上去批处理任务就算资源再空闲也挤不进来。这比单纯靠亲和性可靠得多因为亲和性是 Pod 侧自愿的污点是节点侧强制的。kubectl taint nodes node-a dedicatedonline:NoSchedule对应的 Pod 容忍配置tolerations: - key: dedicated operator: Equal value: online effect: NoScheduleNoExecute配合tolerationSeconds还能实现优雅驱逐节点出现问题时打上NoExecute污点容忍时间设为 300 秒的 Pod 会在 5 分钟后被驱逐给业务留出迁移窗口。这个参数在节点维护场景里特别好用我一般会给关键服务设一个较长的容忍时间避免节点短暂抖动就触发大规模迁移。3.3 Pod 间亲和与反亲和让副本“抱团”或“分散”节点亲和管的是 Pod 和节点的关系Pod 间亲和管的是 Pod 和 Pod 的关系。它解决的是“同一服务的副本该不该放在一起”这个问题。反亲和让副本尽量分散提升可用性亲和让相关服务尽量靠近降低网络延迟。这里有个关键概念叫拓扑域通过topologyKey指定。比如kubernetes.io/hostname表示以节点为域反亲和就是每个节点最多放一个副本topology.kubernetes.io/zone表示以可用区为域反亲和就是每个可用区最多放一个副本。选哪个拓扑键取决于你要防的是什么级别的故障——防单节点故障用 hostname防机房级故障用 zone。podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: app: web-frontend topologyKey: kubernetes.io/hostname提示Pod 间反亲和的硬约束在大规模集群里要慎用。假设一个服务有 100 个副本硬反亲和要求每个节点最多一个那集群至少得有 100 个可调度节点否则多出来的副本全部 Pending。副本数多的时候优先用软反亲和配合weight控制分散倾向。3.4 拓扑分布约束比反亲和更精细的分散控制Pod 间反亲和能实现分散但它有个局限只能表达“每个域最多几个”没法表达“各域之间数量要均衡”。拓扑分布约束补上了这块它通过maxSkew控制各拓扑域之间副本数量的最大差值whenUnsatisfiable控制不满足时是硬性拒绝还是软性倾向。举个例子三个可用区部署 9 个副本maxSkew设为 1那最终分布一定是 3/3/3 或者接近这个比例不会出现 7/1/1 这种极端倾斜。这个策略在多可用区架构里几乎是必配的因为反亲和只能保证“每个区都有”保证不了“每个区一样多”。topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: web-frontendwhenUnsatisfiable设成DoNotSchedule是硬约束设成ScheduleAnyway是软约束。我的经验是副本数少比如 3 个以内用硬约束容易 Pending用软约束更稳副本数多、可用区资源充足时用硬约束能真正保证均衡。3.5 优先级与抢占资源紧张时谁先上集群资源紧张时新来的高优先级 Pod 调度不上去怎么办优先级和抢占机制就是答案。给 Pod 配置priorityClassName调度器在过滤失败后会触发 PostFilter 阶段的抢占逻辑找一个低优先级的牺牲者 Pod把它驱逐掉腾出资源给高优先级 Pod。优先级数值越大优先级越高。系统关键组件通常占用很高的优先级段业务 Pod 用中低段。配置时要注意优先级类的数量不要太多层级太细反而增加管理成本我一般按“核心在线服务 普通在线服务 批处理任务 离线任务”分四档就够了。apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: high-priority value: 100000 globalDefault: false description: 核心在线服务使用注意抢占是有代价的。被驱逐的 Pod 会重新进入调度队列如果集群长期资源不足可能出现反复抢占、反复驱逐的震荡。抢占只能缓解燃眉之急根治还得靠扩容或资源配额治理。另外抢占默认会尽量选择影响面最小的牺牲者但配置不当仍可能误伤建议给关键服务设置PodDisruptionBudget兜底。3.6 资源请求与限制调度决策的底层依据前面所有策略都建立在资源这个基础之上。调度器判断节点能不能放下一个 Pod看的是 Pod 的requests而不是limits。节点可分配资源减去已分配 requests剩下的就是能继续调度的空间。所以 requests 配置得准不准直接决定了调度结果准不准。我见过太多人 requests 随手填个很小的值limits 填得很大结果节点上 Pod 数量远超实际承载能力运行时互相抢 CPU 导致集体变慢。正确的做法是 requests 尽量贴近实际使用量limits 作为上限保护。对于内存这种不可压缩资源requests 和 limits 设成相等是最稳妥的避免 OOM 时的连锁反应。resources: requests: cpu: 500m memory: 512Mi limits: cpu: 1000m memory: 512MiCPU 是可压缩资源limits 可以大于 requests超卖一部分提升利用率内存不可压缩超卖风险高。这个区别在配置时一定要心里有数。4. 实操过程与核心环节落地4.1 从零配置一套组合调度策略光看单个策略不够实际项目里往往是多个策略组合使用。我拿一个典型的三层架构服务举例把前面讲的策略串起来。假设有一个 Web 前端服务要求副本分散在三个可用区、每个可用区内部尽量分散到不同节点、必须跑在带特定标签的节点池、核心时段优先级要高。配置思路是拓扑分布约束管可用区均衡软反亲和管节点级分散节点亲和锁定节点池优先级类保证资源竞争时的地位。apiVersion: apps/v1 kind: Deployment metadata: name: web-frontend spec: replicas: 6 template: spec: priorityClassName: high-priority affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: node-pool operator: In values: - web-pool podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app: web-frontend topologyKey: kubernetes.io/hostname topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: web-frontend这套配置落地后我用kubectl get pods -o wide验证分布确认三个可用区各两个副本且同区内尽量落在不同节点。验证这一步不能省配置写完不代表生效一定要看实际调度结果。4.2 用调度器日志定位一次 Pending 问题有一次一个服务扩容后部分副本一直 Pending事件里只显示“0/20 nodes are available”。这种信息太笼统得看调度器日志才能知道具体是哪个过滤插件把节点全毙了。排查步骤是这样的先kubectl describe pod看事件摘要确认是调度失败而非镜像拉取失败然后翻调度器日志搜索这个 Pod 的名字日志里会逐条列出每个节点的过滤失败原因比如“node(s) didnt match Pods node affinity”“node(s) had untolerated taint”。那次问题就是节点亲和写错了一个标签值导致所有节点都不匹配改掉标签值后立刻恢复。kubectl logs -n kube-system scheduler-pod-name | grep pod-name提示调度器日志默认可能不是详细级别排查前可以临时调高日志级别但记得排查完调回去否则日志量会暴涨。生产环境建议把调度失败事件接入监控告警别等业务反馈才发现。4.3 资源碎片治理的一次实操集群跑久了经常出现“总空闲资源够但就是调度不上去”的情况这就是资源碎片。原因是各节点剩余资源零散单个节点放不下一个大 Pod。我遇到过一次一个需要 4 核 8G 的 Pod 一直 Pending但集群总空闲有 20 核 40G。治理思路分两步短期用优先级抢占或者手动腾挪把某个节点上的小 Pod 迁走凑出连续资源长期靠资源配额和调度策略优化比如给大规格 Pod 单独划一个节点池避免和小 Pod 混部。我最终的做法是给大内存任务单独建了一个节点池配合节点亲和锁定碎片问题基本消失。这个经验说明一个道理调度策略解决的是“怎么放”但“放不放得下”本质上是容量规划问题。策略再精妙容量不够照样 Pending。4.4 自定义调度插件的接入流程有些场景内置策略覆盖不到比如按机房电力成本调度、按网络拓扑距离调度。这时候就需要自定义调度插件。接入流程大致是实现调度框架的扩展点接口编译成二进制或者做成 sidecar 形式配置调度器加载这个插件然后在调度器配置里启用。我做过一个简化版的机房成本插件只实现 Filter 和 Score 两个扩展点Filter 剔除成本超预算的机房节点Score 按成本从低到高打分。核心逻辑不复杂难的是和默认插件的权重协调——自定义打分要和内置打分归一化到同一量纲否则权重配置会失真。apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - schedulerName: custom-scheduler plugins: filter: enabled: - name: CostFilter score: enabled: - name: CostScore weight: 50注意自定义插件上线前一定要在测试集群做压测观察调度吞吐和延迟。插件里的逻辑如果有阻塞操作会拖慢整个调度周期进而影响所有 Pod 的调度速度。5. 常见问题与排查技巧实录5.1 调度问题速查表现象可能原因排查方向Pod 一直 Pending事件提示节点不匹配节点亲和/选择器条件过严检查标签键值是否与节点实际标签一致Pod 一直 Pending事件提示污点不容忍节点有污点但 Pod 无对应容忍查看节点污点补容忍或换节点池副本分布严重倾斜未配拓扑分布或反亲和为软约束检查拓扑约束配置评估是否改硬约束高优先级 Pod 抢占后反复震荡集群长期资源不足扩容或治理低优先级任务资源配额调度延迟升高自定义插件阻塞或节点数量过大检查插件逻辑评估调度器性能大规格 Pod 无法调度但总资源充足资源碎片单独节点池或调整 Pod 规格这张表是我从多次排查里攒出来的遇到问题先对号入座能省不少时间。5.2 几个容易踩的坑第一个坑是标签拼写。节点亲和、污点容忍、拓扑约束全靠标签和键值匹配一个字母写错就是全量 Pending。我的习惯是把常用标签定义成常量统一管理配置里引用常量而不是手写字符串。第二个坑是软硬约束混用不当。硬约束用多了 Pending 风险高软约束用多了约束形同虚设。判断标准很简单这个条件不满足时业务能不能接受降级运行能接受就用软约束不能接受才用硬约束。第三个坑是忽略NoExecute的驱逐效应。给节点打NoExecute污点做维护时如果 Pod 没配容忍会被立即驱逐可能引发服务短暂不可用。维护前先确认关键服务都配了合理的tolerationSeconds。第四个坑是优先级类滥用。给太多服务配高优先级等于没配资源紧张时照样抢。优先级分层要克制层级之间留出足够数值间隔方便后续插入新层级。5.3 调度策略的验证方法配置写完一定要验证我常用的验证手段有三个一是kubectl get pods -o wide看实际分布直观但粗二是kubectl describe node看节点已分配资源和 Pod 列表确认资源账算得对不对三是模拟故障比如给一个节点打污点观察副本是否按预期迁移和重新分布。模拟故障这一步很多人跳过但它最能暴露配置问题。我在测试环境专门做过一轮演练逐个节点打NoExecute污点观察每个服务的副本迁移路径和恢复时间结果发现有两个服务的反亲和配置导致迁移后新副本无处可去及时调整了约束强度。这种问题不演练根本发现不了。6. 我个人在实际操作中的几点体会调度策略这东西配置本身不难难的是判断“该配什么”和“配到什么程度”。我现在的做法是新服务上线先只配最基础的资源请求和节点选择器跑一段时间观察实际调度分布和资源使用再根据暴露出来的问题逐步加策略。一上来就把所有策略配满往往过度约束反而制造 Pending。另外一个体会是调度问题的根因经常不在调度器本身。Pending 可能是容量不够分布倾斜可能是副本数太少抢占震荡可能是资源配额没治理好。遇到调度异常先别急着调策略参数先确认容量、配额、标签这些基础项是否健康能省掉大量无效调参。最后分享一个我常用的小技巧给集群的关键标签体系建一份文档记录每个标签的含义、取值、哪些节点池在用、哪些服务依赖它。调度策略的复杂度很大程度上来自标签体系的混乱标签管清楚了策略配置和维护的成本会直线下降。这份文档我一般放在团队共享的知识库里新人接手时能少走很多弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑