尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Kubernetes调度策略实战:从Pod乱跑到精准分布与混部排错

发布时间:2026/9/26 4:34:19

资讯中心
01
ARTICLE

Kubernetes调度策略实战:从Pod乱跑到精准分布与混部排错

Kubernetes调度策略实战:从Pod乱跑到精准分布与混部排错
先说一个我实际经手的集群。那是大促前第三周的晚上业务方反馈核心下单链路响应明显变慢我登上去看节点分布线上服务 A 的 8 个副本有 5 个挤在同一台物理节点上另外两台配置相当的节点各自只承载了 1 个和 0 个副本。负载本来就高的节点雪上加霜空闲节点却闲着没事。我第一反应是调度器挂了但 kube-scheduler 日志里全是正常的调度记录。折腾到后半夜才彻底想明白——不是调度器出了问题是我的集群从建好那天起就没有告诉过调度器业务对分布和故障域的真实要求那它当然只会按照默认的资源均衡逻辑把 Pod 一个个发到当前看起来比较空的节点上。它不理解你的业务自然谈不上按需调度。这不是极端案例我后来接手过的很多集群都有类似的乱跑现象。而绝大多数团队第一次解决这个问题都是靠临时手动删 Pod 期望它重新调度或者干脆给节点打污点把所有 Pod 赶走。这种方法能撑一时治不了根。这篇文章我打算系统地把 Kubernetes 调度策略的核心原理、五类常用调度配置和实际排错思路完整梳理一遍适合正在维护自建集群的运维、经常被 Pod 分布问题困扰的开发以及想把单集群混部效率做上去的平台团队。1. Pod 为什么会乱跑调度器到底在做什么决策1.1 默认调度器的世界观里只有资源kube-scheduler 是控制平面里负责给待调度 Pod 选节点的组件。它不是 AI也不是随机抽签器它的判断逻辑就是读取当前所有节点的资源水位执行过滤和打分然后把 Pod 绑定到得分最高的节点。这里的关键点在于默认配置下它对业务一无所知。它不知道你这个工作负载是下单链路还是离线清理任务不知道多副本之间需不需要跨机柜也不知道哪台机器实际利用率已经逼近上限。它唯一掌握的硬指标是节点的 CPU、内存、GPU 这类可分配资源的数字。所以当你完全不配置任何调度策略时调度结果的看起来乱其实是必然的。调度器会倾向于把新 Pod 放在当前剩余资源较多的节点上以实现负载均衡但这是一种资源视角的均衡和你的业务视角完全是两码事。举个最简单的例子你有两台节点分别属于不同的机柜资源水位差不多调度器并不会刻意让同一服务的副本分散到不同机柜它只看数字。于是前面那种场景就发生了副本扎堆、单点风险、白白浪费其他区域的容量。1.2 一次 Pod 调度的完整时间线要真正理解策略配置得先搞懂调度器在整个 Pod 生命周期里站在哪个位置。用一条时间线来看客户端调用 API Server 创建 Pod 对象写入 etcd此时 Pod 的 spec.nodeName 是空的。kube-scheduler 通过 watch 机制感知到新的待调度 Pod把它放进内部调度队列。调度器执行调度周期所有过滤插件依次检查候选节点不合格的直接淘汰再进入打分环节给剩余节点打分排序。调度器执行绑定周期通过 API Server 把选中的节点名写回 Pod 的 spec.nodeName 字段。目标节点的 kubelet watch 到这次绑定开始调用容器运行时创建 Pod 的 sandbox 和业务容器。容器启动成功后Pod 进入 Running 状态kubelet 持续上报心跳和状态。第 5 步很关键kubelet 创建的是沙箱。沙箱是 CRI 实现containerd、CRI-O、docker为 Pod 准备的隔离环境相当于先把房间打扫干净再让业务容器住进去。这一步如果失败Pod 的表现就是一直 ContainerCreating比如你经常在社区里看到的 failed to create pod sandbox 这种报错。这背后往往是容器运行时、CNI 网络插件或磁盘状态出问题和调度器没有直接关系。但很多平台团队习惯性把一切 Pod 异常都归为调度问题导致排查方向从一开始就偏了。明白这条时间线之后你能自然地区分两种状态Pending 说明 Pod 还没有被绑定到任何节点是调度没完成或者没开始ContainerCreating 说明绑定已经完成但节点侧没能成功拉起容器。这两个状态的排查方向截然不同第 5 章我会专门展开。2. 调度决策的两段式流程过滤与打分到底怎么跑2.1 过滤阶段一票否决制的硬门槛调度周期第一步是过滤。调度器会把集群里所有满足基本条件的节点收集起来逐个交给注册好的过滤插件审查。任何插件返回不合格这个节点就直接出局。这种机制保证了 Pod 永远不会被调度到一个它跑不起来的节点上。常见的过滤插件和它们检查的内容NodeResourcesFit判断节点剩余可分配资源是否满足 Pod 的 requests。注意这里看的是可分配资源不是总资源也不是实时使用率。NodeName只有 spec.nodeName 指定的节点才通过。NodePorts检查节点上已分配的服务端口是否有冲突。NodeAffinity如果配置了 nodeAffinity 的 required 项不满足匹配条件的节点直接淘汰。TaintToleration节点上有 Pod 不容忍的污点例如 NoSchedule 效果则淘汰。PodTopologySpread如果配置了硬性拓扑分布约束DoNotSchedule不满足 skew 限制的节点会被过滤掉。这种一票否决机制决定了任何一条硬性约束写得过宽或过严都会立刻反映到调度结果里。最常见的失误是 nodeSelector 或 requiredDuringScheduling 里写了一个集群中根本不存在的标签值结果 Pod 一直 PendingEvents 里反复出现类似 0/n nodes are available: n node(s) didnt match node selector 的报错。2.2 打分阶段在合格名单里挑最优过滤完之后剩下的节点都是能跑的。接下来打分阶段给每个合格节点算一个分数选最高分。这个分数是多个打分插件权重叠加后的加权和。默认配置下比较核心的几个打分插件NodeResourcesBalancedAllocationCPU 和内存使用率越均衡的节点得分越高。NodeResourcesLeastAllocated已分配的 request 总量越少的节点得分越高也就是把新 Pod 尽量放到空闲节点。ImageLocality节点本地已经存在 Pod 所需镜像的话加分。NodeAffinitynodeAffinity 里配置的 preferred 项每满足一条加对应的权重分。PodTopologySpread节点所在拓扑域里的分布越均匀得分越高。InterPodAffinitypodAffinity 和 podAntiAffinity 的软性偏好会体现在这里。要特别说明的是默认打分用的是已分配资源requests而不是实时资源使用率来评估空闲程度。实际使用率可能是波动的比如一台节点的 CPU request 只占了 20%但业务峰值期间实际用了 90%。kube-scheduler 看不到这些它只看调度时刻的请求值水位。这就是为什么资源监控面板上明明有个节点很空闲调度器却偏向其他节点的原因——监控面板上的空闲是针对实际用量调度器参考的是 requests 维度。另一个常见的理解偏差大家以为调度器在多个待调度 Pod 之间做全局最优规划实际上并不是。调度器是一个一个处理队列里的 Pod每个 Pod 独立做决策。一组 Service 副本一次性创建时由于队列里排着队逐个处理加上 LeastAllocated 拉平效应结果看起来像均匀撒开但本质上不是一次统筹规划。理解这一点你就能接受为什么某些极端状态下同一服务的两个副本会落在同一个节点上。2.3 调度框架插拔式的扩展点从 Kubernetes 1.19 开始调度器重构成了调度框架Scheduling Framework。之前写死的算法逻辑被拆成了一组插件插在调度周期和绑定周期的不同扩展点上。主要扩展点包括 QueueSort、PreFilter、Filter、PostFilter、PreScore、Score、NormalizeScore、Reserve、Permit、PreBind、Bind、PostBind。简单说过滤插件挂在 Filter 扩展点打分插件挂在 Score 扩展点自研调度逻辑则可以在对应扩展点实现。了解框架的意义不光是写自定义调度器更重要的是排查问题调度器日志里一个 Pod 从入队到绑定会记录每个阶段的耗时。如果看到过滤阶段就花费了很长时间可能是某个 Filter 插件太慢比如自研的 webhook 式过滤插件网络延迟高如果看到反复出现 failed to bind 但调度本身很快则要怀疑 API Server 的写入或者 etcd 性能。这些细节在默认黑盒调度器时代很难定位现在插件化之后路径清晰多了。3. 让 Pod 待在该待的地方五类调度策略的配置用法接下来是重点实操按从简单到复杂的顺序逐个讲清楚配置方式、背后语义和容易踩的坑。实际项目中它们经常组合使用第 4 章我会给一个统一场景示例。3.1 nodeSelector先学会给节点贴标签nodeSelector 是最朴素的调度约束Pod 只允许调度到包含指定标签的节点上。用法分两步。第一步给节点打标签kubectl label node k8s-node-01 disktypessd kubectl get nodes --show-labels第二步在 Pod 里声明apiVersion: v1 kind: Pod metadata: name: nginx-pod spec: nodeSelector: disktype: ssd containers: - name: nginx image: nginx:1.27这个配置的含义是节点必须包含 disktypessd 这个键值对。如果写多个键值对它们是 AND 关系全部满足才能调度。没有节点满足时Pod 会一直 Pending事件里能看到 0/n nodes are available: n node(s) didnt match node selector。我的建议是nodeSelector 适合做大方向的节点隔离比如区分 GPU 节点、机器类型、存储类型。它不适合做精细调度因为语义是硬匹配标签拼写稍微错一点就是 Pending维护成本不低。很多团队一上来就在几十个服务里用了不同风格的 nodeSelector后面加节点标签时牵一发动全身。更好的做法是把 nodeSelector 当作分类工具的兜底把更灵活的匹配交给 nodeAffinity。3.2 nodeAffinity从必须到尽量的弹性nodeAffinity 可以理解成 nodeSelector 的增强版差别在于两点一是支持 In、NotIn、Exists、DoesNotExist、Gt、Lt 多种运算符表达能力强很多二是区分硬约束和软约束调度失败时软约束不会导致 Pending。硬约束的配置长这样spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: topology.kubernetes.io/zone operator: In values: - zone-arequired 的意思是调度时必须满足否则不调度。注意 nodeSelectorTerms 下的多个 matchExpressions 是 OR 关系同一个 matchExpressions 里的多个表达式是 AND 关系。这个逻辑很容易记反我见过不止一次有人把多个 matchExpressions 当成 AND结果 Pod 跑到了完全不符合预期的节点上。软约束用 preferredDuringSchedulingIgnoredDuringExecutionspec: affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 preference: matchExpressions: - key: disktype operator: In values: - ssdweight 取值范围 1-100每满足一条偏好就按对应权重加分。软约束的好处是即使没有 ssd 节点Pod 也会调度到其他节点不会卡住只是 ssd 节点优先级更高。这里分享一个实操经验用 NotIn 排除节点时先确认节点标签是否一定存在。比如你想写不要调度到带 gputrue 标签的节点用 NotIn 的话任何没有 gpu 标签的节点也会匹配成功这没问题但如果你的意图是不要调度到 GPU 节点同时不排斥普通节点那 NotIn 反而会缩小范围——它会把根本没有 gpu 标签的节点也排除掉因为它们不满足标签存在且值不在列表的条件。这个细节光看 yaml 看不出毛病只有 Pod 卡住才会暴露。3.3 podAffinity 与 podAntiAffinity管理 Pod 之间的邻居关系前面两类策略管的是Pod 和节点的关系podAffinity 管的是Pod 和 Pod 的关系。它按照 topologyKey 定义的拓扑域把一组 Pod 尽量放在一起或者尽量分开。核心配置项是 topologyKey它必须是一个节点标签键。常见值kubernetes.io/hostname以节点为单位也就是同一节点topology.kubernetes.io/zone以可用区为单位topology.kubernetes.io/region以地域为单位常见的反向亲和示例让同一服务的多个副本尽量不在同一节点上spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app: web topologyKey: kubernetes.io/hostname这里的含义是在新 Pod 调度时如果某个节点上已经运行了 appweb 的 Pod就把该节点打一点负分。副本越多这种 preference 对分布的帮助越大。正向亲和的一个典型场景是缓存类的服务比如 Redis和数据消费服务希望离得近一些减少网络 RTT。可以配置 podAffinity要求消费服务的 Pod 落在和数据服务相同的节点或可用区。如果配置为 required必须保证目标拓扑域内有足够资源放得下新 Pod否则 Pod 会 Pending。这个功能名字听着简单真正用起来有几个坑。第一个坑是硬性反亲和required下如果副本数大于同域节点数必然有副本无处可去会 Pending。比如集群只有 2 个可用区你却要求 3 个副本都分布在不同的可用区那第三个副本永远调度不起来。第二个坑是反亲和和拓扑分布约束同时配置时容易互相打架表现是明明还有其他节点事件里却提示 topology spread constraints 不满足。第三个坑是 labelSelector 的作用范围是整个命名空间跨命名空间选择需要用 namespaceSelector。3.4 Taints 与 Tolerations给节点装上门禁节点污点Taint是一种排斥机制节点被打了某个污点之后默认情况下新 Pod 不允许调度上去。要想进去Pod 必须显式声明容忍Toleration。这套机制和亲和性的方向正好相反——亲和性是 Pod 主动选择节点污点是节点被动拒绝 Pod。打污点的命令kubectl taint nodes k8s-node-01 dedicatedcmdb:NoSchedule污点由 key、value、effect 三部分组成。effect 决定排斥的强度NoSchedule默认拒绝调度新 Pod 上来已存在的 Pod 不受影响。PreferNoSchedule尽量不调度上去但没有硬性拒绝没有其他选择时仍会调上来。NoExecute不仅拒绝新 Pod还会驱逐节点上已有的、没有对应容忍的 Pod。Pod 声明容忍的方式spec: tolerations: - key: dedicated operator: Equal value: cmdb effect: NoSchedule关于 operator如果不写默认是 Equal如果想容忍这个 key 的所有值用 Exists 且不写 value。两种写法语义差异很大建议每次写 yaml 时都明确写出 operator避免默认行为理解偏差。实际工作中污点最常用的场景包括给独立规划的高性能节点打污点防止普通业务流上来给 GPU 节点打污点只让声明了 GPU 资源申请的 Pod 容忍并调度上去给控制面节点打 NoSchedule 已经是 kubeadm 默认行为不需要自己处理。关于 NoExecute 和驱逐有一个值得记住的细节容忍时间是可选的通过 tolerationSeconds 控制。比如配置 60 秒意味着 Pod 可以继续在节点上待 60 秒之后被驱逐。这在节点维护场景里很有用提前给节点打 NoExecute 污点并设置宽限期让工作负载优雅迁移而不是立刻驱逐导致所有 Pod 同时重启。3.5 topologySpreadConstraints把副本均匀撒开topologySpreadConstraints 是 Kubernetes 1.19 之后才稳定v1 可用的功能它专门解决按故障域均匀分布的问题。以前你要实现6 个副本在 3 个可用区里各放 2 个得靠硬性反亲和加上精心设计的拓扑域配置过程非常别扭。现在直接声明拓扑分布约束spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: webmaxSkew 表示不同拓扑域之间 Pod 数量差的最大值1 是最严格任意两个可用区的 Pod 数差不能超过 1。whenUnsatisfiable 有两个选项DoNotSchedule 是硬约束找不到满足条件的节点就不调度ScheduleAnyway 是软约束调度器会尽量满足但实在不行也能接受。这个功能的语义很容易误解我解释一下。调度时计算的是已经运行的和当前调度队列中同标签的 Pod 在某个拓扑域的分布然后判断待调度的目标节点所在的域新增一个副本后会不会让域间差值超过 maxSkew。如果会这个节点在硬约束下就会被过滤掉。实际落地的一个建议如果业务对高可用要求高且节点数量足够可以把 topologySpreadConstraints 和 podAntiAffinity 同时配置前者保证故障域级别的均匀后者保证节点级别的互斥。两者视角不同配合使用能兼顾区域分散和节点隔离。但要注意的是多个约束同时使用时相互独立计算如果约束之间矛盾会出现事件里提示某个约束无法满足导致 Pending。这时候要回头检查是否有副本数大于可用拓扑域数量的情况。3.6 PriorityClass 与抢占资源不够时谁先上最后是优先级机制。前面所有策略解决的是怎么选节点优先级解决的是实在没节点时谁能抢到调度机会。默认集群里所有 Pod 优先级相同靠 FIFO 队列排序配置了 PriorityClass 之后调度队列按优先级从高到低排序高优先级的 Pod 会排前面。PriorityClass 示例apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: high-priority value: 1000000 globalDefault: false在 Pod 里引用spec: priorityClassName: high-priorityvalue 越大优先级越高。系统默认自带两个system-cluster-critical 和 system-node-critical值在 20 亿量级用于保证 kube-system 里的核心组件优先调度。当高优先级 Pod 在所有过滤后的节点上都找不到位置时调度器会执行抢占流程找一组占用资源的低优先级 Pod把它们驱逐走腾出空间给高优先级 Pod。这是一个尽力而为的过程不保证成功而且被驱逐的 Pod 会被重新创建落到其他节点上。我的个人建议是抢占功能在生产环境要慎用。尤其是平台团队给业务统一设置高优先级时一旦资源和优先级错配可能出现低优先级的核心业务反复被高优先级任务挤来挤去。最好只在明确区分在线高优和离线低优的情况下使用并且配合资源配额一起治理。4. 混合部署实操在线业务和离线任务如何共用一个集群策略单独讲完总要落到实际组合。这几年我做过一个比较典型的混部集群同一个集群里同时运行在线微服务、定时离线任务、GPU 推理服务。这三类对调度的需求完全不一样不加约束的话一定会互相挤兑。我的方案是这样设计的。4.1 节点规划与标签设计首先给节点打一套统一的分类标签kubectl label node cn-online-01 workloadonline kubectl label node cn-offline-01 workloadoffline kubectl label node gpu-01 workloadgpu gpu-typea100同时给在线节点和 GPU 节点打上污点防止离线任务这一类低优、可抢占的工作负载偷跑上去kubectl taint node cn-online-01 workloadonline:NoSchedule kubectl taint node gpu-01 workloadgpu:NoSchedule离线任务的 Pod 默认没有 toleration自然会被挡在外面。这一步是混部集群所有策略的地基先靠污点做隔离再靠亲和性和拓扑分布做精细化分布。4.2 在线服务的完整调度配置带一个 replicas6 的 Deployment要求跨可用区均匀分布、副本之间互斥同时容忍在线节点的污点apiVersion: apps/v1 kind: Deployment metadata: name: web-service spec: replicas: 6 template: spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: workload operator: In values: - online podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app: web-service topologyKey: kubernetes.io/hostname tolerations: - key: workload operator: Equal value: online effect: NoSchedule topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: web-service containers: - name: web-service image: registry.example.com/web-service:v1.2.3 resources: requests: cpu: 1 memory: 2Gi limits: cpu: 2 memory: 4Gi这段配置同时包含四层语义nodeAffinity 保证只调度到在线节点podAntiAffinity 让同一服务副本尽量不落同一节点topologySpreadConstraints 保证跨可用区均匀tolerations 让 Pod 能进入打过污点的在线节点。四层策略组合以后在线业务的基本分布就能达到不串到离线节点、节点级分散、可用区级均衡。4.3 离线任务与 GPU 服务的配置要点离线任务的优先级通常定得低并且只允许调度到离线节点spec: priorityClassName: low-priority nodeSelector: workload: offline affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 10 preference: matchExpressions: - key: workload operator: In values: - offline tolerations: - operator: Exists effect: NoExecute tolerationSeconds: 60GPU 推理服务的关键点在于声明 GPU 资源、指定 GPU 节点、容忍 GPU 节点的污点spec: nodeSelector: workload: gpu tolerations: - key: workload operator: Equal value: gpu effect: NoSchedule containers: - name: inference image: registry.example.com/llm-infer:v2.1.0 resources: requests: nvidia.com/gpu: 1 limits: nvidia.com/gpu: 1GPU 设备数量是稀缺资源除了 nodeSelector 之外资源 requests 里的 nvidia.com/gpu 字段也是调度过滤的一部分调度器只会把 Pod 放到剩余 GPU 数量足够的节点。这个字段不是装饰写错或漏写会导致 Pod 被调度到 GPU 节点后迟迟创建不出来。另外要注意 Device Plugin 上报的 GPU 拓扑和 nodeSelector 范围保持一致否则会出现节点有 GPU 但调度器不认的情况。4.4 混部方案的效果与踩坑总结这套方案上线后在线服务副本在三个可用区的分布比例基本稳定在 2-2-2离线任务始终被钉在 offline 节点上GPU 服务不再出现被普通业务抢占的情况。踩过的坑有两个值得记住一是第一次给在线节点打污点后忘记给在线服务加对应 toleration导致新发布的副本全部 Pending排查时看 describe 发现 Events 里有明显的 taint 不匹配提示才反应过来二是拓扑约束的 labelSelector 一开始写的是 Deployment 的 name 而不是 Pod 的 label导致约束根本不生效看起来还是随机分布后来检查 selector 匹配目标才修正。这种混部方案不要求所有团队都这么做但思路值得复用先污点隔离大类再亲和性细化最后拓扑分布保证容灾。5. 从 Pending 到沙箱崩溃调度与运行时问题的完整排查链路配置和策略说完但做运维的都知道配置一时爽排错两行泪。我单独开一章把排查调度问题的主线逻辑讲清楚。5.1 先用两个状态把问题归类拿到一个异常 Pod第一件事不是看配置而是看状态。kubectl get pods -o wide 输出里Pending 和 ContainerCreating 是两个必须严格区分的状态。PendingPod 还没被绑定到节点也就是调度决策没完成。这是真正的调度问题。ContainerCreatingPod 已经绑定了节点但节点上的 kubelet 没能把容器跑起来。这是运行时或应用侧问题。一旦把问题归到 Pending继续看具体原因归到 ContainerCreating就要转向 kubelet、containerd、CNI 等方向。很多人一看到 Pod 状态不对就进调度器日志翻翻了半小时发现调度器这边根本没记录完全是在浪费时间。5.2 查看 Events 里的 FailedScheduling 信息对于 Pending 状态kubectl describe pod 几乎已经把答案写在 Events 里了。关键是看最后几条kubectl describe pod web-service-xxx事件里常见的原因和解法0/n nodes are available: n node(s) didnt match node selector节点标签不匹配检查 nodeSelector / nodeAffinity 的 label 和节点实际标签。n node(s) had taint {xxx: }, that the pod didnt tolerate污点阻挠检查 Pod tolerations 是否匹配节点 taints。n Insufficient cpu / n Insufficient memory资源不足看节点剩余 request 水位。n node(s) didnt match pod anti-affinity rulesPod 反亲和硬约束无法满足。n node(s) didnt match topology spread constraints拓扑分布约束无法满足。这类事件信息准确率高到可以直接当结论用。唯一的陷阱是事件会滚动覆盖如果 Pod 反复创建要看最后一条的时间戳确认不是旧事件的残留。5.3 资源水位与节点状态双确认Events 给出方向后还需要确认节点侧的数据。常用命令kubectl top nodes kubectl describe node k8s-node-01注意 describe node 里 Non-terminated Pods 显示的是已分配 requests 的累计值Capacity 是节点总容量Allocatable 是 kubelet 预留系统组件之后还可用于调度的量。排查 Insufficient memory 时比对的是 Pod 请求总量和 Allocatable 之间的差值不是监控面板上的实时使用率。实时使用率高但 request 没打满调度器依然认为资源足够反之监控看很空闲但 request 早被打满调度器会老实告诉你说不够。节点状态也要看Node NotReady 的节点通常会被调度器过滤掉但如果网络分区刚恢复调度器对节点信息的缓存可能有延迟需要结合调度器日志确认。5.4 调度器日志的正确打开方式如果 Events 里没有 FailedScheduling或者 Pod 一直 Pending 但事件相当平静就要去调度器日志里看kubectl logs -n kube-system kube-scheduler-cp-01 --tail200 | grep web-servicekube-scheduler-cp-01 是实际集群里控制平面节点上的调度器 Pod 名称需要根据环境替换。一般关注几个关键字pod waiting for scheduling、schedule result、Unable to schedule pod。日志里如果一直显示 schedule result: success 但 Pod 状态没变化说明调度器可能已经给出决策但在绑定环节卡住这时候要检查 API Server 写路径或 etcd。这里面的链路比较深但先看 Events 和调度器日志两层基本能覆盖八成问题。5.5 当 Pod 卡在 ContainerCreatingfailed to create pod sandbox 的真相现在专门说开头提到的这个错误。你会在 kubelet 或容器运行时日志里看到类似这样failed to create pod sandbox: rpc error: code Unknown desc failed to create containerd task: ... context deadline exceeded我第一次遇到时也以为是调度配置有问题但它和调度毫无关系。这一步发生在 kubelet 已经拿到绑定信息、开始调用容器运行时创建沙箱之后。沙箱创建失败常见原因排序CNI 网络插件异常。Pod 沙箱创建过程中会请求 CNI 插件配置网络calico / flannel / cilium 任何一个组件出问题都会导致 sandbox 创建直接失败。排查方法看 kube-system 里网络插件 Pod 是否 Running看 kubelet 日志里的 network plugin 相关报错。容器运行时本身异常。containerd 或 docker 服务崩溃、版本升级后 CRI 版本不匹配、磁盘空间满等。排查systemctl status containerd、df -h、df -i。镜像拉取问题。沙箱创建时如果基础镜像拉不下来同样会报错。看 kubelet 日志和 image pull 相关 Events。节点系统资源紧张比如 PID 数量达到上限、内存不足以启动 sandbox。排查顺序我建议是先看 kubelet 日志或容器运行时状态再确认 CNI 组件再查磁盘和镜像。不要一开始就去翻调度器日志方向错了一切都白费。5.6 调度与运行时的快速诊断速查表症状归类可能原因首要排查命令Pod 一直 PendingEvents 有 FailedScheduling调度问题资源不足、标签不匹配、污点、反亲和冲突kubectl describe podPod 一直 PendingEvents 无失败记录调度器/绑定链路调度器异常、API Server 写入慢kubectl logs kube-schedulerPod 一直 ContainerCreating运行时问题sandbox 创建失败、镜像拉取失败systemctl status containerd、kubectl get pods -n kube-systemPod CrashLoopBackOff应用问题容器启动即退出、健康检查失败kubectl logs / kubectl describe pod节点 NotReady节点问题kubelet 异常、网络插件掉线kubectl get nodes、journalctl -u kubelet这张表是我处理线上问题默认的第一步先归类再深挖比上来就翻日志高效得多。6. 调度之外容易被忽略的细节6.1 调度决策是一次性的调度器只在 Pod 创建时做一次位置决策之后它的任务就结束了。运行期间节点标签变化、调度策略修改、节点资源变紧张都不会触发现有 Pod 的重新调度。这一点经常被新接触 Kubernetes 的同学误解改了反亲和配置以为存量副本会自动搬走其实不会要滚动重启 Deployment 生成新 Pod 才会按新策略调度。还有一个与此相关的经典误解ConfigMap 或 Secret 变更不会触发 Pod 重新滚动。很多人改了配置发现 Pod 里的配置没变化以为是调度问题其实是 controller 没有监听 ConfigMap 变更去滚动 Deployment。这和调度无关但经常被放在一起讨论。所以做策略变更时要有一个预期修改调度配置后要么通过 kubectl rollout restart 触发新一轮滚动更新要么等下一次发布版本才会体现。6.2 自定义调度器当 default-scheduler 不够时默认调度器并不是万能钥匙。有些场景比如希望把批处理任务调度到数据已经存在的节点以减少读取耗时或者希望在调度过程中注入公司内部的成本权重模型默认调度器都不支持。这种情况下可以部署自定义调度器通过 spec.schedulerName 让特定工作负载走自己的调度器spec: schedulerName: my-scheduler自定义调度器最简实现就是 watch 未调度的 Pod执行自己的选择逻辑然后调用 Kubernetes API 更新 Pod 的 nodeName。生产环境我见过一些团队把成本权重、碳排指标、GPU 拓扑感知这些写进自己的调度逻辑。但有一条边界要记住默认调度器的插件机制覆盖了抢占、优先级、污点过滤等语义自定义调度器如果完全另起炉灶这些都要自己实现。所以我更推荐的方式是尽量用默认调度器加调度框架插件而不是完全替换除非你的场景确实特殊。6.3 资源配额与调度失败的边界还有一类创建失败和调度无关。如果命名空间配置了 ResourceQuota当配额不足时API Server 会直接拒绝创建 Pod报错类似 exceeded quota。LimitRange 配置也会在构建 Pod 时校验资源范围。这类错误发生在 Pod 进调度队列之前Events 里可能连 FailedScheduling 都没有。遇到这种情况第一反应应该是看 API Server 层的错误而不是去翻调度器。LimitRange 示例apiVersion: v1 kind: LimitRange metadata: name: mem-limit-range spec: limits: - default: memory: 512Mi defaultRequest: memory: 256Mi max: memory: 2Gi type: Container判断方法很简单kubectl apply 或 create 时控制台直接报错说明创建阶段就被拦截如果 apply 时成功之后才 Pending才进入调度问题范畴。6.4 我自己沉淀下来的一点体会调度策略玩到现在我的感受是Kubernetes 的调度机制本身并不复杂难的是把业务对分布、容灾、隔离的真实诉求翻译成一组准确的约束。我见过一半以上的调度现场事故都不是策略不会写而是把约束写多了、写冲突了或者在不该用硬约束的地方用了硬约束。所以落地时我会先问三个问题这个服务的副本能不能共节点能不能共可用区资源不足时 Pod 等待还是让低优 Pod 挪位置三个问题的答案基本决定了你要用反亲和还是拓扑分布、用 required 还是 preferred、要不要配优先级抢占。想清楚这三个问题再动手写 yaml比一边写一边试要稳得多。这些年下来真正让我少走弯路的从来不是某个技巧而是每次变更前先想清楚调度器需要理解什么业务语义再让策略去表达它。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。