Kubernetes 调度与容量治理:让 Pod 放得下、跑得稳、扩得动
Kubernetes 调度看似简单:创建 Pod,调度器选择节点,kubelet 拉起容器。生产环境里,它往往是稳定性问题的放大器。资源请求设置不准、节点池规划混乱、亲和性规则过度复杂、优先级设计缺失,都会导致 Pod 长时间 Pending、关键服务被挤压、扩容动作赶不上流量变化。
容量治理的目标不是让节点利用率永远最高,而是在成本、弹性和稳定性之间取得平衡。一个健康的集群应当做到:关键服务有资源保障,批处理任务不会干扰在线业务,高峰期能扩得动,故障时有足够冗余。
调度链路
K8s 调度器主要经历过滤和打分两个阶段。过滤阶段排除不满足条件的节点,例如资源不足、节点选择器不匹配、污点不可容忍、PVC 绑定限制、端口冲突。打分阶段在可用节点中选择更合适的节点,例如资源均衡、亲和性偏好、拓扑分布。
当 Pod Pending 时,先看事件:
kubectl describe pod <pod> -n <namespace>
常见原因包括:
Insufficient cpu或Insufficient memorynode(s) had untolerated taintnode(s) didn't match Pod's node affinitypod has unbound immediate PersistentVolumeClaimsToo many pods
不要只看节点实际 CPU 使用率。调度器依据的是资源请求 requests,不是当前使用率。节点看起来很空,但如果已分配请求量接近上限,新 Pod 仍然无法调度。
Requests 与 Limits
requests 决定调度和资源保障,limits 决定资源上限。很多集群的问题来自两个极端:完全不设置 requests,或给所有服务设置过高 requests。
推荐策略:
- 在线服务必须设置 CPU 和内存 requests。
- 内存 limit 应谨慎设置,避免 OOMKilled 变成常态。
- CPU limit 不一定必须设置,尤其是延迟敏感服务,过低 CPU limit 会引入 throttling。
- 批处理任务可以设置较低优先级,结合 namespace ResourceQuota 控制总体消耗。
可以通过如下指标观察 CPU throttling:
rate(container_cpu_cfs_throttled_periods_total[5m])
/
rate(container_cpu_cfs_periods_total[5m])
如果核心服务 throttling 长期较高,说明 CPU limit 可能过紧,或者应用线程模型与 CPU 配额不匹配。
节点池规划
生产集群建议按工作负载类型规划节点池,而不是把所有 Pod 混跑在同一批节点上。常见节点池包括:
- system:运行 CoreDNS、Ingress、监控、日志等平台组件。
- online:运行在线业务服务。
- batch:运行离线任务、CI Runner、定时 Job。
- stateful:运行对磁盘、网络或稳定性要求更高的有状态服务。
- gpu 或 high-memory:运行特殊资源服务。
节点池之间通过 taints、tolerations、nodeSelector 或 nodeAffinity 建立边界。平台组件节点池可以设置污点,只有明确容忍的系统 Pod 才能调度上去。
tolerations:
- key: "dedicated"
operator: "Equal"
value: "system"
effect: "NoSchedule"
nodeSelector:
nodepool: system
亲和性与拓扑分布
亲和性规则可以表达“必须跑在某类节点”或“尽量靠近/远离某些 Pod”。但规则越多,调度越难,故障时越容易无节点可选。
高可用服务建议使用 topologySpreadConstraints,让副本跨节点、可用区或机架分布:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: checkout
如果业务要求强隔离,可以把 whenUnsatisfiable 设置为 DoNotSchedule,但这会牺牲弹性。生产中更常见的策略是“尽量分散,但不要因为分散失败导致服务无法启动”。
优先级与抢占
没有优先级的集群,在资源紧张时无法区分核心链路和低价值任务。建议为不同类型工作负载设置 PriorityClass:
platform-critical:DNS、Ingress、监控、控制面依赖。business-critical:核心在线业务。standard:普通服务。batch-low:批处理、报表、CI。
抢占可以保护关键服务,但也可能造成低优先级任务反复被驱逐。对批处理系统,要确保任务具备可重试能力,并且不会因抢占导致数据不一致。
HPA、VPA 与集群自动扩容
HPA 解决副本数弹性,VPA 解决资源请求建议,Cluster Autoscaler 或云厂商节点自动扩容解决节点容量。三者要一起设计。
典型链路是:
- 流量上升,HPA 增加 Pod 副本。
- 新 Pod 因节点资源不足 Pending。
- 集群自动扩容增加节点。
- 调度器把 Pending Pod 放到新节点。
如果节点启动慢、镜像拉取慢、HPA 指标滞后,就会出现扩容跟不上流量的情况。关键服务可以预留缓冲容量,或使用预测型扩容策略。
VPA 在生产中建议先以 recommendation 模式运行,用它校准 requests。直接自动修改在线服务 requests 可能触发重建 Pod,需要谨慎评估。
容量水位
容量治理应关注三类水位:
- 已请求水位:所有 Pod requests 之和与节点可分配资源的比例。
- 实际使用水位:真实 CPU、内存、磁盘、网络使用。
- 弹性水位:在节点故障或流量突增时还能承载多少副本。
一个常见目标是在线节点池保留 20% 到 30% 的请求余量,具体比例取决于节点扩容速度、业务峰谷、故障域设计和成本压力。对金融、支付、核心交易类业务,余量应更保守。
排障方法
当出现 Pending 或调度异常时,可以按以下路径排查:
- 查看 Pod Events,确认过滤失败原因。
- 查看 requests 是否超过节点可分配资源。
- 检查 nodeSelector、nodeAffinity、tolerations。
- 检查 PVC 是否绑定,存储类是否支持目标可用区。
- 检查 namespace ResourceQuota 和 LimitRange。
- 检查节点最大 Pod 数、CNI IP 池容量。
- 检查自动扩容组件日志。
总结
调度与容量治理是 K8s 生产化的底层能力。它连接成本、稳定性、发布效率和故障恢复。真正可靠的集群,不是平时节点利用率看起来漂亮,而是在资源紧张、节点故障、流量突增、批任务挤压时,仍然能让关键服务优先运行,并且让扩容链路可靠闭环。
适用场景
适合正在处理 K8s、Kubernetes, Scheduler, Capacity, HPA 相关问题的团队,用于快速建立排查路径和交付标准。
问题背景
深入理解 K8s 调度链路、资源请求、优先级、亲和性、污点容忍和容量治理,避免集群在高峰期出现 Pending、抢占和雪崩。
排查步骤
先确认影响范围和最近变更,再收集日志、配置、指标和链路数据,最后按风险从低到高执行修复。
命令示例
示例命令请替换为你的真实资源名,并使用环境变量保存账号、密码、token 等敏感信息。
风险说明
生产环境操作前需要确认备份、权限边界、变更窗口和回滚路径,避免扩大故障影响。
回滚方案
保留原配置和发布版本;如修复后指标异常,立即回退配置、镜像或数据库变更并复核日志。
交付清单
问题定位记录、关键命令、修复步骤、验证结果、后续优化建议。
遇到类似技术问题?
如果你的服务器、K8s、Docker、CI/CD、数据库或监控系统出现类似问题,可以提交日志和配置文件,我们帮你远程诊断。