这一章我们来解决一个很常见、也很核心的问题:同样是创建 Pod,为什么它最后会落到某一台节点,而不是别的节点?
理解这个问题之后,你会更容易看懂 Pending、Unschedulable、nodeSelector、Affinity、Taint、PriorityClass 这些概念,也能在生产环境里更快定位“为什么 Pod 一直起不来”。
Kubernetes 的调度本质上是:给一个还没绑定节点的 Pod,挑选一台最合适的 Node。默认调度器 kube-scheduler 会持续监听还没有分配 .spec.nodeName 的 Pod,然后为它寻找最合适的节点。默认情况下,Pod 创建后就会进入可调度状态;如果使用了 schedulingGates,Pod 会先停留在等待调度前的 gated 状态,直到 gate 被移除后才会真正参与调度。::cite[137]
1. 先建立一个整体认知
很多同学一开始会把调度理解成“随机找台机器”。其实完全不是。
更接近真实情况的理解是:
- 调度器先看哪些节点绝对不行。
- 再从剩余节点里看哪些节点更合适。
- 最后选出一个分数最高或最优的节点,并完成绑定。
所以,调度不是“碰运气”,而是一个先过滤,再排序的过程。官方文档把这件事描述为:调度器负责为未分配节点的 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 一个非常实用的思维模型
当你排查调度问题时,不要一上来就问“为什么没调度成功”,而应该拆成两步:
- 是不是所有节点都在过滤阶段被刷掉了?
- 如果有多个候选节点,最终为什么选中了这台?
这套思路在排查 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-1worker-2worker-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,可以按下面顺序排查:
kubectl describe pod <pod-name>看Events;- 看是否报
0/3 nodes are available这类信息; - 检查
requests是否过大; - 检查
nodeSelector/affinity是否写错; - 检查节点是否被
cordon; - 检查节点是否有
taint; - 检查是否设置了
schedulerName指向一个不存在或异常的调度器; - 检查是否存在
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. 本章小结
请记住下面四句话:
- 调度的本质,是给未绑定节点的 Pod 找最合适的 Node。
- 调度过程可以简化理解为:先过滤,再打分。
- Pod 调度失败,通常是所有节点都没通过过滤。
- 默认调度器解决大多数问题,自定义调度器用于业务特化。
只要你抓住“过滤 + 打分”这条主线,后面学习资源限制、亲和性、污点、优先级时都会顺很多。
📝 版权声明:本文为原创技术博客,转载请注明出处。
如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!