K8s Pod 调度策略完全指南
Kubernetes 调度器(kube-scheduler)通过预选(Filtering)和优选(Scoring)决定 Pod 落在哪个节点。 默认行为在生产环境往往不够用。本文覆盖六大调度机制,附完整 YAML 配置和生产案例。
调度流程概览
调度策略的生效优先级从高到低:
nodeName(最高优先级,直接指定节点名,跳过调度器)- Taints / Tolerations(节点污点过滤)
- nodeSelector(简单标签匹配)
- nodeAffinity(灵活的节点亲和性)
- podAffinity / podAntiAffinity(Pod 间亲和/反亲和)
- topologySpreadConstraints(拓扑分散约束)
- 资源请求(节点剩余资源是否满足 requests)
六大调度机制
1. nodeSelector — 最简单的调度约束
通过标签键值对匹配节点,硬约束,无匹配节点则 Pod 永久 Pending。
spec:
nodeSelector:
disktype: ssd
适用场景: 简单场景,如"数据库必须跑在 SSD 节点"。复杂场景建议用 nodeAffinity。
2. nodeAffinity — 灵活的节点亲和性
支持多种操作符和软/硬约束结合:
| 操作符 | 含义 |
|---|---|
| In | 值在列表中 |
| NotIn | 值不在列表中 |
| Exists | 标签存在(不关心值) |
| DoesNotExist | 标签不存在 |
| Gt / Lt | 大于/小于(仅数字值) |
- 硬约束:
requiredDuringSchedulingIgnoredDuringExecution— 不满足不调度 - 软约束:
preferredDuringSchedulingIgnoredDuringExecution— 尽量满足,权重 1-100
⚠️
IgnoredDuringExecution表示 Pod 已运行后,即使节点标签变了也不驱逐。Kubernetes 计划实现RequiredDuringExecution但尚未可用。
逻辑关系: 多个 nodeSelectorTerms 之间是 OR,同一 nodeSelectorTerm 中的多个 matchExpressions 是 AND。
3. podAffinity / podAntiAffinity — Pod 间亲和与反亲和
控制 Pod 之间的调度关系:
- podAffinity: 让 Pod 和指定 Pod 部署在同一拓扑域(降低网络延迟)
- podAntiAffinity: 让 Pod 远离指定 Pod(分散风险)
topologyKey 定义拓扑域:
kubernetes.io/hostname→ 以节点为域topology.kubernetes.io/zone→ 以可用区为域
⚠️ 性能警告: podAffinity 计算复杂度为 O(N²),N 是集群 Pod 数量。5000+ Pod 的大集群中调度延迟可从 5ms 升至 200ms。优先用 topologySpreadConstraints 替代。
4. Taints & Tolerations — 节点专用化
从节点角度排斥 Pod,实现节点专用化(GPU 节点、数据库节点等):
| Effect | 对新 Pod | 对已有 Pod | 适用场景 |
|---|---|---|---|
| NoSchedule | 不调度 | 不影响 | 节点专用化 |
| PreferNoSchedule | 尽量不调度 | 不影响 | 软限制 |
| NoExecute | 不调度 | 驱逐 | 节点维护、故障隔离 |
常用内置污点:
node.kubernetes.io/not-ready:NoExecutenode.kubernetes.io/unreachable:NoExecutenode-role.kubernetes.io/control-plane:NoSchedule
tolerationSeconds: 容忍 NoExecute 污点的最长等待时间(到期后被驱逐)。
5. topologySpreadConstraints — 拓扑分散(推荐)
K8s 1.19 GA,比 podAntiAffinity 更精细地控制 Pod 在拓扑域间的分布:
| 参数 | 说明 |
|---|---|
| maxSkew | 域间 Pod 数量最大差值,1 = 严格均匀 |
| topologyKey | 拓扑域标签 key |
| whenUnsatisfiable | DoNotSchedule(硬)/ ScheduleAnyway(软) |
| minDomains | 最少拓扑域数量(1.25 Beta) |
示例: 9 副本 + 3 zone + maxSkew=1 → 每个 zone 恰好 3 个 Pod。
✅ 生产环境跨故障域部署的首选方案。
6. PriorityClass — 优先级与抢占
高优先级 Pod 可抢占低优先级 Pod 的资源:
preemptionPolicy: PreemptLowerPriority # 可抢占
preemptionPolicy: Never # 不抢占(批处理任务推荐)
⚠️
PreemptLowerPriority会驱逐低优先级 Pod(收到 SIGTERM),确保应用能正确处理优雅关闭。生产环境建议批处理任务设Never。
生产案例
案例一:MySQL 主从集群调度
- Master:nodeAffinity 锁定 SSD + database 节点,PriorityClass
critical-production - Slave:podAntiAffinity 硬约束分散到不同节点,topologySpreadConstraints 跨 zone
- 结果:Master 固定在 SSD 节点,Slave 分布在 zone-a/b/c 的不同节点
案例二:灰度发布调度
- 灰度节点打标签
canary=true - 灰度 Deployment 通过 nodeAffinity 锁定灰度节点
- 验证通过后更新稳定版镜像 + 缩容灰度 Deployment
最佳实践
性能优化
- 能用 topologySpreadConstraints 就不要用 podAntiAffinity(计算复杂度差别巨大)
- 合理设置 resource requests — 调度器按 requests 决策。设 P95 实际用量,避免过高(节点利用率低)或过低(OOMKill)
- 使用 Descheduler 重平衡 — 节点扩容/Pod 漂移后自动重新调度
安全加固
- 限制 nodeName 直接指定 — 通过 OPA/Gatekeeper 策略禁止(跳过所有调度检查)
- PriorityClass 权限控制 — RBAC 限制高优先级类的使用
- 节点污点防篡改 — 通过准入 webhook 拦截关键节点 taint 修改
高可用配置
- 核心服务 ≥3 副本 + podAntiAffinity 硬约束 + topologySpreadConstraints 跨 zone
- 使用 PodDisruptionBudget(PDB)限制同时不可用 Pod 数量
- 调度策略配置(PriorityClass、节点标签、污点)纳入 GitOps
排障速查
| 现象 | 原因 | 排查命令 |
|---|---|---|
| Pod Pending,0/N nodes available | nodeSelector/Affinity 无匹配节点 | kubectl get nodes --show-labels |
| 调度成功但分布不均匀 | topologySpreadConstraints labelSelector 写错 | 确认 labelSelector 与 Pod labels 一致 |
| 高优先级 Pod 抢占后,被抢占 Pod 无法恢复 | 集群资源不足 | 扩容节点或降低 requests |
| taint 添加后已有 Pod 没被驱逐 | 用了 NoSchedule 而非 NoExecute | NoSchedule 只影响新 Pod |
| drain 卡住 | PDB 限制 | 先扩容副本或临时调整 PDB minAvailable |
监控告警
| 指标 | 正常 | 告警 | 含义 |
|---|---|---|---|
| 调度延迟 P99 | <100ms | >500ms | 调度器过载或策略过于复杂 |
| Pending Pod 数 | 0 | >10(持续 5min) | 持续有 Pod 无法调度 |
| 调度失败率 | <1% | >5% | 资源不足或策略配置问题 |
| 节点 CPU 分配率 | 40-70% | >85% | 新 Pod 无法调度 |
| 抢占事件数 | 0 | >5次/小时 | 资源规划不合理 |
命令速查表
# 节点标签管理
kubectl label node <node> key=value # 添加标签
kubectl label node <node> key- # 删除标签
kubectl get nodes --show-labels # 查看所有标签
# 污点管理
kubectl taint nodes <node> key=value:NoSchedule # 添加污点
kubectl taint nodes <node> key=value:NoSchedule- # 删除污点
kubectl describe node <node> | grep Taints # 查看污点
# 调度排查
kubectl get events --field-selector reason=FailedScheduling -A
kubectl describe pod <pod> | grep -A 10 Events
kubectl top nodes
# 节点维护
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data
kubectl cordon <node> # 标记不可调度
kubectl uncordon <node> # 恢复调度
关联页面
| 页面 | 关联点 |
|---|---|
| resource-rbac-scheduling-troubleshooting | 调度失败排障(Taint/OOMKill/RBAC) |
| k8s-resource-limits-configuration | 资源限制配置(Request/Limit/QoS) |
| k8s-statefulset-guide | StatefulSet 与有状态应用调度 |
| k8s-rolling-update-pitfalls | 滚动更新与灰度发布 |
| k8s-troubleshooting-principles | K8s 排障基本原则 |
| k8s-capacity-planning-qos-cost-optimization | K8s 容量规划方法论与成本优化 — 从流量到资源预算的完整框架,含 QoS 策略、弹性伸缩协同、落 |
| k8s-pod-pending-troubleshooting-guide | Pod Pending 排障指南 — 7 个排查方向(资源不足/污点/亲和性/存储/配额/选择器/端 |
| pod-troubleshooting | Pod 排障完整指南 |