返回首页

3.1 调度器原理:Pod 为什么会落到这台节点

这一章我们来解决一个很常见、也很核心的问题:同样是创建 Pod,为什么它最后会落到某一台节点,而不是别的节点?

理解这个问题之后,你会更容易看懂 PendingUnschedulablenodeSelectorAffinityTaintPriorityClass 这些概念,也能在生产环境里更快定位“为什么 Pod 一直起不来”。

Kubernetes 的调度本质上是:给一个还没绑定节点的 Pod,挑选一台最合适的 Node。默认调度器 kube-scheduler 会持续监听还没有分配 .spec.nodeName 的 Pod,然后为它寻找最合适的节点。默认情况下,Pod 创建后就会进入可调度状态;如果使用了 schedulingGates,Pod 会先停留在等待调度前的 gated 状态,直到 gate 被移除后才会真正参与调度。::cite[137]

1. 先建立一个整体认知

很多同学一开始会把调度理解成“随机找台机器”。其实完全不是。

更接近真实情况的理解是:

  1. 调度器先看哪些节点绝对不行
  2. 再从剩余节点里看哪些节点更合适
  3. 最后选出一个分数最高或最优的节点,并完成绑定。

所以,调度不是“碰运气”,而是一个先过滤,再排序的过程。官方文档把这件事描述为:调度器负责为未分配节点的 Pod 找到最合适的 Node;调度框架中常见的思路就是先过滤不可行节点,再对可行节点进行评分。::cite[137]

2. 调度流程概览:过滤与打分

你可以把默认调度流程粗略理解成下面这张脑图:

  • 进入队列:新建 Pod 且未绑定节点;
  • PreFilter / Filter:筛掉不满足条件的节点;
  • Score:给剩余节点打分;
  • Bind:把 Pod 绑定到目标节点;
  • 后续由 kubelet 真正拉镜像并启动容器

2.1 过滤阶段:先看“能不能放”

过滤阶段关注的是硬条件。只要有一个条件不满足,这台节点就直接出局。

常见过滤条件包括:

  • 节点是否处于可调度状态;
  • 节点资源是否满足 Pod 的 request;
  • Pod 的 nodeSelector / nodeAffinity 是否匹配;
  • 节点污点是否被 Pod 容忍;
  • 端口、卷、拓扑等约束是否满足。

例如,官方调度配置文档里专门提到 NodeUnschedulable 这类过滤逻辑:如果节点 .spec.unschedulable=true,那它会在过滤阶段被排除。::cite[138]

2.2 打分阶段:再看“谁更适合”

通过过滤的节点,并不一定都一样好。

打分阶段会综合考虑:

  • 资源是否更均衡;
  • 是否更符合软亲和性偏好;
  • 是否更利于 Pod 分散;
  • 是否更符合拓扑分布目标。

然后调度器会从这些候选节点里选一个最优解。你可以把它理解成:

  • Filter 决定能不能上车
  • Score 决定坐哪一辆更合适

2.3 一个非常实用的思维模型

当你排查调度问题时,不要一上来就问“为什么没调度成功”,而应该拆成两步:

  1. 是不是所有节点都在过滤阶段被刷掉了?
  2. 如果有多个候选节点,最终为什么选中了这台?

这套思路在排查 Pending Pod 时非常有效。

3. 调度器如何选择最合适的节点

3.1 调度器看的不是“当前感觉”,而是声明式约束

调度器不是凭经验判断,而是依据 Pod 与 Node 上的声明式字段做决策,例如:

  • Pod 的 resources.requests
  • Pod 的 nodeSelector
  • Pod 的 affinity
  • Pod 的 tolerations
  • Pod 的 topologySpreadConstraints
  • Node 的标签、污点、可调度状态、可分配资源

也就是说,Pod 最终落到哪台节点,本质上是约束与偏好的共同结果

3.2 一个最直观的例子:nodeSelector

先给某个节点打标签:

kubectl label node worker-1 disktype=ssd

再创建一个带 nodeSelector 的 Pod:

apiVersion: v1
kind: Pod
metadata:
  name: pod-on-ssd
spec:
  containers:
    - name: nginx
      image: nginx:1.27
      resources:
        requests:
          cpu: "100m"
          memory: "128Mi"
  nodeSelector:
    disktype: ssd

这个 Pod 只能被调度到带有 disktype=ssd 标签的节点。

如果集群里只有 worker-1 满足条件,那么调度器其实就没有太多选择空间,最终大概率只会落到那台机器。

3.3 再看一个“多个候选节点”的例子

假设有三台节点都满足基础条件:

  • worker-1
  • worker-2
  • worker-3

Pod 的 request 很小,三台机器都能放得下;同时没有设置硬性亲和性,也没有污点冲突。那接下来就进入评分阶段。

此时调度器可能会倾向于:

  • 让资源分布更均衡;
  • 避免把同类 Pod 全堆到一台机器;
  • 满足你配置的 preferred 类型亲和性偏好。

所以最终结果往往不是“随机”,而是“在满足硬约束的前提下,选择综合分更高的节点”。::cite[137]

4. 实操:观察一个 Pod 是如何被调度的

4.1 创建测试 Pod

apiVersion: v1
kind: Pod
metadata:
  name: scheduler-demo
  labels:
    app: scheduler-demo
spec:
  containers:
    - name: app
      image: nginx:1.27
      resources:
        requests:
          cpu: "200m"
          memory: "256Mi"
        limits:
          cpu: "500m"
          memory: "512Mi"

应用:

kubectl apply -f scheduler-demo.yaml

4.2 查看调度结果

kubectl get pod scheduler-demo -o wide

你会看到 NODE 列出现具体节点名称。

4.3 看事件,比看状态更重要

kubectl describe pod scheduler-demo

重点看 Events

如果调度成功,通常能看到类似:

Normal  Scheduled  default-scheduler  Successfully assigned default/scheduler-demo to worker-2

这条事件非常关键,因为它直接告诉你:

  • 是哪个调度器完成的;
  • 最终绑定到了哪台 Node。

5. 常见调度失败原因分析

Pod 一直 Pending,绝大多数情况都不是“调度器坏了”,而是没有任何节点同时满足它的约束

5.1 资源 request 过大

调度器在放置 Pod 时,会依据容器声明的 request 判断节点是否有足够可分配资源。如果 request 超过所有节点可提供的资源,这个 Pod 就会一直 Pending。::cite[327]

例如:

apiVersion: v1
kind: Pod
metadata:
  name: huge-request-pod
spec:
  containers:
    - name: app
      image: nginx:1.27
      resources:
        requests:
          cpu: "8"
          memory: "16Gi"

如果你的测试集群每台节点只有 2 CPU / 4Gi 内存,那显然没有节点能通过过滤。

5.2 nodeSelector / nodeAffinity 条件过严

比如你写了:

nodeSelector:
  gpu: "true"

但实际上集群里没有任何节点带这个标签,Pod 一定起不来。

5.3 节点被设置为不可调度

如果节点被 cordon,它会带上不可调度标记。官方调度配置文档说明,这类节点会被 NodeUnschedulable 等过滤逻辑直接剔除。::cite[138]

排查命令:

kubectl get nodes

如果你看到 SchedulingDisabled,说明这台节点不会接收新的 Pod。

5.4 污点未被容忍

如果节点带有 NoSchedule 污点,而 Pod 没有对应 toleration,那么它不会被调度到该节点。这是调度失败的高频原因之一。

5.5 调度前就被 gate 挡住了

从官方文档可知,Pod 默认创建后会被视为 ready for scheduling;但如果你显式设置了 schedulingGates,Pod 会先留在等待状态,直到 gate 被移除。::cite[331]

这类问题表面看起来像“调度器没工作”,其实是 Pod 根本还没进入正常调度流程。

6. 一个典型的失败排查清单

遇到 Pending Pod,可以按下面顺序排查:

  1. kubectl describe pod <pod-name>Events
  2. 看是否报 0/3 nodes are available 这类信息;
  3. 检查 requests 是否过大;
  4. 检查 nodeSelector / affinity 是否写错;
  5. 检查节点是否被 cordon
  6. 检查节点是否有 taint
  7. 检查是否设置了 schedulerName 指向一个不存在或异常的调度器;
  8. 检查是否存在 schedulingGates

这是非常适合面试、考试和生产排障的一套基础方法论。

7. 默认调度与自定义调度思路

7.1 默认调度器已经能覆盖大多数场景

Kubernetes 自带默认调度器 kube-scheduler,对于绝大多数业务已经足够好:

  • 普通 Web 服务;
  • 定时任务;
  • 有基础资源约束的中间件;
  • 需要亲和性、反亲和性、污点容忍的业务。

很多时候你并不需要“自己写调度器”,而只需要正确使用现有调度能力。

7.2 什么情况下考虑自定义调度

当默认调度器不满足业务策略时,可以考虑:

  • 使用不同的调度配置;
  • 运行多个调度器;
  • 让某些 Pod 指定 schedulerName 交给特定调度器处理。

官方文档明确说明:Kubernetes 可以同时运行多个调度器,并通过 Pod 的 schedulerName 指定使用哪一个调度器。::cite[323]

7.3 一个自定义调度器使用示例

下面这个 Pod 不走默认调度器,而是交给名为 my-scheduler 的调度器:

apiVersion: v1
kind: Pod
metadata:
  name: custom-scheduler-demo
spec:
  schedulerName: my-scheduler
  containers:
    - name: nginx
      image: nginx:1.27
      resources:
        requests:
          cpu: "100m"
          memory: "128Mi"
        limits:
          cpu: "300m"
          memory: "256Mi"

如果集群里没有名为 my-scheduler 的可用调度器,这个 Pod 也会一直 Pending。

7.4 自定义调度的实际思路

实际工作里,自定义调度通常不是“从零写一个复杂系统”,而是围绕以下目标做增强:

  • 让 GPU、SSD、大内存节点优先承载特定工作负载;
  • 为批处理任务与在线任务采用不同调度策略;
  • 引入更细粒度的业务优先级规则;
  • 与公司内部资源平台打通,按成本或配额做调度。

也就是说,默认调度器解决通用性,自定义调度器解决业务特化

8. 实操:演示 schedulerName

8.1 查看 Pod 使用的是哪个调度器

kubectl get pod custom-scheduler-demo -o yaml

你会在输出里看到:

spec:
  schedulerName: my-scheduler

8.2 用事件判断是否真的被对应调度器处理

kubectl describe pod custom-scheduler-demo

如果事件里没有 my-scheduler 的调度记录,就要检查:

  • 调度器是否真的在运行;
  • schedulerName 是否拼写正确;
  • 该调度器是否具备必要权限;
  • 目标 Pod 是否满足它自己的调度逻辑。

9. 本章小结

请记住下面四句话:

  1. 调度的本质,是给未绑定节点的 Pod 找最合适的 Node。
  2. 调度过程可以简化理解为:先过滤,再打分。
  3. Pod 调度失败,通常是所有节点都没通过过滤。
  4. 默认调度器解决大多数问题,自定义调度器用于业务特化。

只要你抓住“过滤 + 打分”这条主线,后面学习资源限制、亲和性、污点、优先级时都会顺很多。


📝 版权声明:本文为原创技术博客,转载请注明出处。

如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!

上一篇

2.1 Pod 详解:最小调度单元

下一篇

4.4 Ingress 与网关