Kubernetes 调度、驱逐与节点维护实战
Kubernetes 节点维护最常见的误区,是把 kubectl drain 当作一个没有前置条件的下线按钮。实际能否安全驱逐,取决于资源请求、拓扑分布、PDB、副本、优先级、本地存储和剩余容量。
本文从调度决策开始,讲清楚主动维护、节点压力驱逐和优雅关机之间的区别,并给出可执行的节点维护 Runbook。
一、调度器在决定什么
调度大致分为两个阶段:
- Filter:排除不满足资源、亲和性、污点、端口、卷拓扑等条件的节点;
- Score:在剩余节点中按资源、分布、亲和性等规则评分。
Pod Pending 时,不要先重启 scheduler,应先查看事件:
kubectl describe pod -n <namespace> <pod>
kubectl get events -n <namespace> --sort-by=.lastTimestamp
常见原因包括:
- CPU、内存或扩展资源不足;
- 未容忍节点污点;
- nodeSelector 或 nodeAffinity 无匹配节点;
- Pod 反亲和或拓扑分布无法满足;
- PVC 拓扑冲突;
- 主机端口冲突;
- ResourceQuota 或 LimitRange;
- 节点不可调度。
二、requests与limits决定调度质量
调度器主要根据 requests 安排资源,而不是应用的真实瞬时用量。
requests过小
- 节点被过度装箱;
- 运行时发生 CPU 争用和内存压力;
- HPA 指标失真;
- 维护时没有足够迁移空间。
requests过大
- Pod 长期 Pending;
- 节点利用率看似很低;
- 容量碎片严重;
- 扩容成本增加。
建议用历史 P95/P99、启动峰值和业务 SLO 共同决定,而不是复制统一模板。
三、QoS与OOM
QoS 类别包括 Guaranteed、Burstable 和 BestEffort。它能帮助判断内存压力下的风险,但 kubelet 的驱逐排序并不是简单地只按 QoS 名称。
检查:
kubectl get pod -n <namespace> <pod> \
-o jsonpath='{.status.qosClass}{"\n"}'
kubectl describe node <node>
journalctl -k --since '-30 min' | grep -Ei 'oom|killed process'
区分两类现象:
- 容器 cgroup 内 OOM:通常显示 OOMKilled;
- 节点整体内存压力:可能触发 kubelet 驱逐或内核 OOM。
四、节点选择与污点
nodeSelector与nodeAffinity
nodeSelector 简单直接;nodeAffinity 支持必选与偏好条件。生产标签要有稳定语义,例如区域、机型、磁盘类型和资源池,不要把临时状态写成长期调度标签。
taint与toleration
污点用于排斥不合适的 Pod,容忍只是允许调度,不保证一定调度过去。
kubectl describe node <node> | grep -A5 Taints
kubectl taint node <node> maintenance=true:NoSchedule
临时维护污点使用后要记录和清理,避免节点恢复后仍无法接收业务。
五、拓扑分布与高可用
如果副本全部落在同一节点或同一可用区,副本数再多也不能抵御故障。
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: api
设计时需要权衡:
DoNotSchedule能保证分布,但容量不足时会 Pending;ScheduleAnyway可提高可调度性,但可能牺牲故障域均衡;- 标签缺失的节点会导致意外结果;
- PVC 和存储拓扑也必须匹配。
维护前应确认剩余故障域是否容得下被驱逐副本。
六、优先级与抢占
PriorityClass 可让高优先级 Pod 在资源不足时抢占低优先级 Pod,但它不是免费容量。
使用原则:
- 只为真正关键的控制面或核心业务设置高优先级;
- 不要让所有业务都成为最高优先级;
- 结合配额限制高优先级对象;
- 观察被抢占业务是否形成重启风暴;
- 明确 preemptionPolicy。
抢占解决的是“谁先使用有限资源”,不是总体容量不足。
七、PDB能保护什么
PodDisruptionBudget 限制 API 发起的自愿中断,例如 drain。它不能阻止:
- 节点硬件故障;
- 内核崩溃;
- 节点压力驱逐;
- 直接删除 Pod 或 Deployment;
- 某些控制器替换。
kubectl get pdb -A
kubectl describe pdb -n <namespace> <name>
常见错误:
- 单副本业务设置
minAvailable: 1,导致永远无法 drain; - 多个 PDB 选择器重叠;
- 副本本身不健康,可用数不足;
maxUnavailable与滚动发布策略冲突;- PDB 有保护但没有足够容量重新调度。
八、理解三种“驱逐”
API发起的驱逐
drain 通过 Eviction API 请求终止 Pod,会考虑 PDB。
节点压力驱逐
kubelet 根据内存、磁盘、inode 和 PID 压力主动终止 Pod。硬驱逐可能不遵循 Pod 的正常终止宽限期,也不受 PDB 保护。
抢占
scheduler 为高优先级 Pod 选择牺牲低优先级 Pod,目标是释放调度资源。
三者触发方、保护机制和日志位置不同,排障时必须先分类。
九、节点压力阈值
常见信号:
memory.available;nodefs.available;nodefs.inodesFree;imagefs.available;imagefs.inodesFree;pid.available。
kubectl describe node <node>
df -hT
df -ih
free -h
ps -e --no-headers | wc -l
journalctl -u kubelet --since '-30 min' --no-pager
不要只把阈值调得更低来消除告警。需要治理镜像、日志、emptyDir、进程泄漏和请求配置。
十、标准节点维护流程
1. 维护前评估
- 是否有控制面、etcd 或关键 DaemonSet;
- 是否有 Local PV;
- 是否有单副本业务;
- PDB 是否允许驱逐;
- 剩余节点是否有足够 CPU、内存、GPU 和存储拓扑;
- 维护窗口和回退条件;
- 节点恢复后需要哪些验证。
2. cordon
kubectl cordon <node>
kubectl get node <node>
cordon 只阻止新 Pod 调度,不会迁移已有 Pod。
3. 查看节点工作负载
kubectl get pods -A -o wide --field-selector spec.nodeName=<node>
kubectl get pdb -A
标记有状态、单副本、Local PV、emptyDir、GPU 和长任务。
4. drain
kubectl drain <node> \
--ignore-daemonsets \
--delete-emptydir-data \
--timeout=30m
使用 --delete-emptydir-data 表示接受 emptyDir 数据丢失,必须事先确认。不要默认添加 --force 或 --disable-eviction。
5. 验证业务重新落位
kubectl get pods -A -o wide
kubectl get events -A --sort-by=.lastTimestamp
检查 Pending、CrashLoopBackOff、PVC 挂载、服务端点和业务指标。
6. 执行维护
完成内核、运行时、驱动、硬件或配置变更,保留操作日志。
7. 节点恢复
systemctl is-active kubelet containerd
kubectl get node <node> -o wide
kubectl uncordon <node>
恢复后检查 DaemonSet、网络、存储、DNS、监控和真实业务。
十一、优雅节点关机
Linux 节点可由 kubelet 配合 systemd 感知计划关机,并按普通 Pod 和关键 Pod 分阶段终止。
需要审查 kubelet 的:
shutdownGracePeriod;shutdownGracePeriodCriticalPods。
同时注意操作系统自动更新工具可能修改关机等待逻辑。应在测试环境验证“系统关机—Pod终止—节点恢复”的完整路径,而不是只检查配置项存在。
十二、DaemonSet与静态Pod
drain 默认忽略 DaemonSet 管理的 Pod,因为删除后仍会在相同节点重建。维护时要判断:
- CNI、CSI、日志和监控 DaemonSet 是否需要优雅停止;
- 静态 Pod 是否属于控制面;
- 节点重启后 DaemonSet 是否依赖外部镜像仓库;
- 新内核或运行时是否与节点插件兼容。
控制面节点的维护必须逐台执行,并保持 etcd 仲裁与 API Server 可用。
十三、Local PV与长任务
Local PV
Pod 通常不能随意迁移到其他节点。维护前必须确认数据副本、恢复方式和节点可恢复时间。
训练、批处理和长任务
可考虑:
- checkpoint;
- Job 重试;
- 维护前停止接收新任务;
- 设置合理 terminationGracePeriodSeconds;
- 在任务平台层做迁移和续跑。
Kubernetes 驱逐不会自动为业务生成可恢复检查点。
十四、Descheduler的定位
Descheduler 可以根据策略驱逐已经运行但分布不合理的 Pod,让 scheduler 重新安排。
使用前要明确:
- 它会产生真实的 Pod 中断;
- 仍受 PDB 和可调度容量约束;
- 不应作为错误 requests 或标签体系的补丁;
- 策略需要灰度、审计和速率控制。
适合处理节点利用不均、拓扑分布变化和污点策略调整后的存量 Pod。
十五、常见维护故障
drain返回429
通常表示 PDB 不允许更多中断。检查副本健康、PDB 选择器、可用数和滚动发布状态。
Pod驱逐后一直Pending
检查剩余容量、污点、亲和性、拓扑、PVC 和主机端口。节点维护前的模拟调度不足是常见原因。
Pod长时间Terminating
检查 finalizer、preStop、terminationGracePeriodSeconds、存储卸载、进程不响应和 API 连接。不要第一时间强制删除有状态 Pod。
uncordon后节点仍不接收Pod
检查额外污点、标签、节点条件、资源可分配量、调度器事件和 DaemonSet 健康。
十六、维护自动化
自动化脚本至少应实现:
- 变更单和审批校验;
- 节点与业务预检;
- PDB 和容量检查;
- cordon、drain;
- 超时即暂停;
- 执行维护;
- 节点健康和业务验证;
- uncordon;
- 观察窗口;
- 输出审计报告。
自动化不能只串联命令,必须包含停止条件和人工接管点。
十七、生产检查清单
调度治理
- [ ] 资源 requests 有数据依据;
- [ ] 关键业务跨节点或跨区分布;
- [ ] 污点、标签和优先级有统一规范;
- [ ] PDB 与副本和发布策略匹配;
- [ ] Local PV 和特殊设备有明确策略。
维护前
- [ ] 剩余容量足够;
- [ ] PDB允许当前批次驱逐;
- [ ] 有状态和长任务已确认;
- [ ] 已定义超时与回退条件;
- [ ] 控制面和etcd不会同时失去多数实例。
维护后
- [ ] Node Ready且资源正常;
- [ ] CNI、CSI、监控、日志DaemonSet恢复;
- [ ] Pod无异常Pending或Terminating;
- [ ] DNS、存储、入口和业务指标通过;
- [ ] 临时污点和维护标记已清理。
参考资料
- Kubernetes:Scheduling, Preemption and Eviction
https://kubernetes.io/docs/concepts/scheduling-eviction/ - Kubernetes:Disruptions
https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ - Kubernetes:Node-pressure Eviction
https://kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction/ - Kubernetes:Node Shutdowns
https://kubernetes.io/docs/concepts/cluster-administration/node-shutdown/