深色模式
Pod 调度实战与调度约束
摘要:本文面向生产 SRE / 平台工程师,解释 kube-scheduler 如何把未绑定节点的 Pod 落到某个节点,覆盖调度周期(过滤→打分→绑定)、哪些字段构成"调度约束"、为何 Pod 会长期
Pending,以及如何排障与回滚。适用 Kubernetes v1.28+。
适用版本与前提
- Kubernetes:v1.28+(文中调度插件名为该版本默认启用项)
- 工具:
kubectl、集群読取权限(至少get/describenode 与 pod) - 前提:已具备一个多节点集群,且 kube-scheduler 以默认配置运行
背景与问题
当创建一个没有显式指定 spec.nodeName 的 Pod 时,它最初处于 Pending 且 nodeName 为空。kube-scheduler 的职责就是为这类"未调度"的 Pod 选出一个最优节点,并通过一次 bind 调用把决策写回 API Server。
调度不是"随便找个能跑的节点"。在大规模生产集群里,调度质量直接决定:
- 可用性:关键 Pod 是否被凑在同一故障域(同一个节点 / 可用区)?
- 资源利用率:节点是否严重碎片化或超卖?
- 性能与成本:Pod 是否贴近数据、是否跨可用区产生网络费用?
理解调度约束,是后续掌握亲和性、污点、拓扑分布、优先级抢占的前提。
核心概念
kube-scheduler 把一次调度分为两个阶段(scheduling context):
- Scheduling cycle(调度周期):串行执行,选出节点。
- Binding cycle(绑定周期):可并发执行,把决策应用到集群。
Pod 调度依赖若干约束字段,它们都属于 Pod 的 spec 或工作负载模板:
| 约束 | 字段位置 | 作用 |
|---|---|---|
| 资源请求 | spec.containers[].resources.requests | 决定节点是否有足够 CPU/内存容纳 |
| 节点选择器 | spec.nodeSelector | 硬约束:节点必须带全部指定 label |
| 指定节点 | spec.nodeName | 绕过调度器,直接绑定(危险) |
| 节点/ Pod 亲和与反亲和 | spec.affinity | 软/硬拓扑约束(见 affinity.md) |
| 污点容忍 | spec.tolerations | 决定能否落在被 taint 的节点(见 taint-toleration.md) |
| 拓扑分布 | spec.topologySpreadConstraints | 跨域均衡分布(见 topology-spread.md) |
| 优先级 | spec.priorityClassName | 影响排队与抢占(见 preemption.md) |
架构与原理
过滤(Filter)阶段逐个节点运行一组内置插件,任一插件判定不可行即剔除该节点。关键插件(按功能归类):
- NodeUnschedulable:跳过被标记为
unschedulable的节点(如kubectl cordon后)。 - NodeName / NodeSelector:满足显式/简单 label 约束。
- TaintToleration:Pod 的 tolerations 必须能容忍节点上的 taint。
- NodeAffinity / InterPodAffinity:亲和与反亲和约束。
- NodeResourcesFit:节点可分配资源 ≥ Pod 的
requests(注意是 requests 而非 limits)。 - PodTopologySpread:满足
topologySpreadConstraints的 skew 约束。 - VolumeRestrictions / VolumeBinding / NodeVolumeLimits:存储相关约束。
如果过滤后没有任何可行节点,Pod 进入 Pending。默认 PostFilter 插件会尝试抢占(preemption)低优先级 Pod,详见 preemption.md。
注意
资源调度看的是 requests 而不是 limits。若 requests 设得过低,节点会被超卖,运行期触发 kubelet 的驱逐(Eviction);若 requests 设得过高,节点利用率低下、Pod 大量 Pending。这是生产中最常见的两类调度问题。
资源请求:被低估的第一约束
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.27
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
NodeResourcesFit 插件默认策略为 LeastAllocated(倾向于更空闲的节点)。注意:调度只看 requests 是否放得下,已分配给 Pod 的 limits 不会被二次计入,因此 limits 之间可能发生运行时争抢(需配合 ResourceQuota / LimitRange 与 QoS 治理)。
nodeSelector 与 nodeName
yaml
spec:
nodeSelector:
disktype: ssd1
2
3
2
3
nodeSelector 是最简单的硬约束:节点必须同时拥有所列全部 label。它表达力有限(仅"与"逻辑、仅硬约束),因此官方推荐优先用 nodeAffinity(见 affinity.md)。
yaml
spec:
nodeName: node-071
2
2
生产危险
spec.nodeName 会完全绕过调度器,直接把 Pod 绑定到指定节点,且不校验 taint、资源、亲和性。若该节点带 NoExecute taint 且 Pod 无对应 toleration,kubelet 会立刻驱逐它。仅在调试或 DaemonSet 类特殊场景使用,禁止用于普通业务 Pod。
生产实践
- 永远为工作负载设置
requests,并按真实用量(而非拍脑袋)设定;用 VPA 推荐值校准。 - 用
kubectl cordon而非直接下线节点来做维护,配合drain优雅驱逐。 - 不要滥用
nodeName;用nodeAffinity/taint表达放置意图,让调度器保留决策能力。 - 监控调度失败:对长期
Pending的 Pod 设置告警(见"可观测性")。
操作步骤
步骤 1:验证环境与调度器状态
bash
kubectl version --short
kubectl config current-context
# 查看节点可分配资源与标签
kubectl describe nodes -l 'disktype=ssd'
kubectl get nodes -o custom-columns=NAME:.metadata.name,CPU_REQ:.status.allocatable.cpu,MEM:.status.allocatable.memory1
2
3
4
5
2
3
4
5
步骤 2:应用示例并观察调度
bash
kubectl apply -f web.yaml
kubectl get pods -o wide -w1
2
2
验证
bash
# Pod 已落到预期节点(无 nodeName 时由调度器决定)
kubectl get pod -o wide
# 查看调度事件
kubectl describe pod <pod> | sed -n '/Events:/,/^$/p'1
2
3
4
2
3
4
回滚与清理
bash
# 回滚 Deployment
kubectl rollout undo deployment/web
# 清理
kubectl delete -f web.yaml
# 维护结束后恢复节点可调度
kubectl uncordon node-071
2
3
4
5
6
2
3
4
5
6
故障排查:Pending 根因表
常用诊断命令:
bash
kubectl describe pod <pod> # 看 FailedScheduling 原因
kubectl get events --sort-by=.lastTimestamp
kubectl describe node <node> # 看节点 Allocatable / 已分配 / taints1
2
3
2
3
| 现象 | 根因 | 处理 |
|---|---|---|
0/N nodes are available: N Insufficient cpu | requests 总和超节点可分配 | 缩容 / 调小 requests / 扩容节点 |
had untolerated taint | 节点有 taint 但 Pod 无 toleration | 加 toleration 或换节点 |
node(s) were unschedulable | 节点被 cordon 或 NotReady | kubectl uncordon / 排查节点健康 |
| 长时间 Pending 但资源充足 | 亲和/拓扑/抢占配置过严 | 放宽 whenUnsatisfiable: ScheduleAnyway 或检查 PriorityClass |
安全与合规
nodeName绕过所有调度约束,属于权限敏感字段,应通过 admission / RBAC / OPA 限制普通用户写入。- 节点维护使用
cordon+drain(带--ignore-daemonsets --delete-emptydir-data谨慎使用),避免直接关机导致 Pod 被强制驱逐。
性能、容量与成本
- 调度器在大型集群中每秒可处理大量 Pod,但
InterPodAffinity是 O(N²) 级开销,官方不建议在数百节点以上集群滥用(见 affinity.md)。 - 过度分散(每个 Pod 一节点)会拉高成本;过度集中会降低可用性。拓扑分布(topology-spread.md)是平衡手段。
常见坑
- requests 漏配:Pod 以 0 requests 被调度,节点被静默超卖,触发 OOM / 驱逐。
- limits 不等于调度约束:两个 requests 小但 limits 大的 Pod 可能挤在同一节点抢 CPU。
nodeName误用:绕过 taint 检查,造成"调度成功但启动即被驱逐"。
替代方案与权衡
| 方案 | 适用 | 不适用 |
|---|---|---|
nodeSelector | 简单硬约束 | 需要软约束 / 复杂逻辑 |
nodeAffinity | 表达力强、支持软硬 | 仅需基础选择时略重 |
taint/toleration | 驱离 / 专用节点 | 需要主动"吸引"放置 |
topologySpreadConstraints | 跨域均衡 | 仅需单节点放置 |
FAQ
Q:Pending 一定是资源不够吗? A:不一定。约束(taint、affinity、topologySpread、volume)任一不满足都会 Pending,需看 FailedScheduling 事件的具体原因。
Q:调度器选了"次优"节点正常吗? A:当多个节点分数相同,kube-scheduler 会随机选一个;只要满足全部约束即为"可行",属于正常行为。
参考资料
- Kubernetes 官方文档 - Kubernetes Scheduler,访问日期:2026-10-08。
- Kubernetes 官方文档 - Assigning Pods to Nodes,访问日期:2026-10-08。
- Kubernetes 官方文档 - Scheduler Framework,访问日期:2026-10-08。