Kubernetes 弹性伸缩、调度与容量效率工具
大促开始三分钟后,业务 QPS 已经翻倍,HPA 也把 Deployment 从 20 个副本扩到了 80 个。控制台看起来一切都在自动工作,接口 P99 却继续恶化。新增 Pod 有一半停在 Pending:它们声明的内存 request 无法装进剩余节点;Cluster Autoscaler 找到候选节点组后,又因为云账号配额不足无法创建实例;少数成功启动的 Pod 还在拉取一个 3 GiB 镜像。最终业务恢复用了 18 分钟,而团队最初只盯着“HPA 已经扩容”这一条绿色状态。
另一个集群恰好相反。节点利用率长期不到 30%,平台启用激进整合后,夜间批任务与在线服务反复被排空和重建。监控显示节点数下降、成本曲线变好,但队列等待时间和在线错误率同步上升。资源效率从来不是把 Pod 塞得越满越好;它要在恢复速度、故障域、工作负载中断、云配额和空闲成本之间做出可解释的工程选择。
先把“自动扩容”拆成七个可观察时刻
一次流量上涨至少产生七个时刻:需求出现、指标可见、伸缩对象变化、Pod 创建、调度失败或成功、节点 Ready、业务容量恢复。只记录开始时间和 HPA 副本数,会把中间所有等待都算成“云主机启动慢”,也可能把应用冷启动误判成调度器故障。
T0 业务需求或队列积压出现
T1 Resource / Custom / External Metric 可查询
T2 HPA、KEDA 或 VPA 作出决策
T3 新 Pod 创建或资源建议生效
T4 kube-scheduler 给出放置结果;失败则产生 Unschedulable
T5 Cluster Autoscaler / Karpenter 请求并注册节点
T6 Pod Ready,流量、吞吐与错误率回到目标区间T1 - T0 是信号延迟,T2 - T1 是控制器响应,T4 - T3 是调度等待,T5 - T4 是容量供给,T6 - T5 是镜像、网络、存储和应用启动。这个分解让 SLO 有真正的 owner:应用团队负责指标语义和启动预算,平台团队负责控制器与调度,基础设施团队负责配额、实例和节点注册,安全团队负责云 IAM 与跨租户边界。
四类控制器不会替彼此完成工作
HPA 通过目标对象的 /scale 子资源改变副本;VPA 推荐或更新单个 Pod 的 CPU、内存 request;KEDA 读取消息积压、请求速率等事件源,并借助 HPA 完成 1 -> N;Cluster Autoscaler 或 Karpenter 根据不可调度 Pod 改变节点容量。Descheduler 只驱逐当前放置不再合适的 Pod,重新放置仍由调度器完成。
最危险的组合是让多个控制器写同一事实。例如 VPA 提高 CPU request 后,基于 CPU utilization 的 HPA 分母随之变化;GitOps 又持续把 replicas 写回固定值,三个控制环会相互抵消。组合前必须列出每个字段唯一写入者、观察者、冻结方式和回退值。
第一次诊断先读对象,不先改参数
下面的只读命令可以在多数集群建立初始时间线。执行者只需读取工作负载、伸缩对象、Event 和节点;读取跨命名空间对象仍应通过受审计的运维身份,不要给应用 ServiceAccount 绑定集群管理员。
export NS=capacity-lab
export APP=checkout
kubectl version
kubectl get apiservice v1beta1.metrics.k8s.io
kubectl top pod -n "$NS" --containers
kubectl get hpa,vpa,scaledobject -n "$NS"
kubectl describe hpa "$APP" -n "$NS"
kubectl get pod -n "$NS" -o wide
kubectl get event -n "$NS" --sort-by=.lastTimestamp
kubectl get node -L topology.kubernetes.io/zone,node.kubernetes.io/instance-typekubectl top 失败时,先检查 Metrics API、APIService 和 kubelet 采集链;HPA 的 AbleToScale=True 并不证明指标可用,还要看 ScalingActive、当前/目标指标与 Event。Pod Pending 时,kubectl describe pod 的 FailedScheduling 比节点总 CPU 更有价值:亲和性、污点、拓扑域、端口、PVC 拓扑和单 Pod request 都可能让“集群总量足够”仍然不可调度。
Kubernetes HPA 文档说明了算法、缺失指标与稳定窗口;资源管理文档解释 request 与 limit 的执行差异;调度器配置文档给出了 profile 和插件的稳定配置入口。版本化能力应以目标控制面与对应工具发行线为准。
用一个反例证明“节点空闲”不等于可调度
在隔离 Namespace 中创建一个 request 大于任一节点剩余连续容量的 Pod。即使所有节点 CPU 使用率很低,它仍会 Pending;调度器按 request 和约束判断,而不是按当前瞬时利用率赌博。
apiVersion: v1
kind: Pod
metadata:
name: oversized-request
namespace: capacity-lab
spec:
restartPolicy: Never
containers:
- name: hold
image: registry.k8s.io/pause:3.10
resources:
requests:
cpu: "6"
memory: 20Gi
limits:
memory: 20Gikubectl create namespace capacity-lab
kubectl apply -f oversized-request.yaml
kubectl get pod oversized-request -n capacity-lab -w
kubectl describe pod oversized-request -n capacity-lab
kubectl get event -n capacity-lab --field-selector involvedObject.name=oversized-request
kubectl delete namespace capacity-lab --wait=true预期证据是 Pod 保持 Pending,并出现 Insufficient cpu、Insufficient memory 或具体约束不匹配,而不是容器启动后的 OOM。若节点伸缩器存在,还应同时观察它是找到可行节点组、被最大规模限制,还是报告没有任何实例类型满足 Pod 需求。实验结束删除整个 Namespace;若测试触发了真实云节点,必须等节点回收并核对实例、磁盘和公网地址,避免把一次教学实验变成持续费用。
工作负载伸缩先解决信号和所有权
CPU、内存是容易取得的信号,却不一定代表业务需求。异步消费者更关心队列深度与单实例处理速率,Web 服务可能关心并发、请求速率或延迟预算。指标必须具有稳定语义、可接受的新鲜度和失败策略;陈旧指标继续扩容、空指标缩到零、标签爆炸拖垮适配器,都是生产事故。
HPA 的 behavior 用来限制扩缩速度和稳定窗口,不是掩盖错误指标的万能防抖。VPA 应先在推荐模式积累样本,再评估 Recreate 或原地更新带来的中断。KEDA 的 TriggerAuthentication 要使用命名空间隔离和短期身份,不能把共享云密钥复制进多个 ScaledObject。工作负载文章会分别给出这些工具的安装、正反实验和卸载核销。
节点供给的瓶颈常在 Kubernetes 之外
Cluster Autoscaler 依赖预定义节点组及其模板,Karpenter 根据 Pod 约束动态选择实例。前者拥有清晰、稳定的容量池边界,后者能扩大实例候选集合并做整合;两者都受云 API、IAM、区域库存、配额、启动脚本、镜像仓库、CNI 地址和 DaemonSet 开销约束。
生产选型至少比较扩容 P95、候选规格覆盖、故障域、Spot 中断处理、缩容保护、云权限、跨云可移植性、升级兼容和退出路径。Karpenter 的整合可以减少浪费,也可能触发换机;Cluster Autoscaler 的节点组更易预测,也可能因为模板过窄留下 Pending Pod。成本结论必须和扰动、恢复时间及 owner 一起评审。
调度策略是在可用性和装箱率之间做选择
NodeSelector、Affinity、Taint/Toleration、Topology Spread、PriorityClass 和 PDB 共同决定 Pod 可以去哪、偏好去哪、资源紧张时谁先获得容量。约束越严格,故障隔离越清晰,但候选节点越少、碎片和空闲成本越高。Bin Packing 能降低节点数,也会扩大单节点故障影响面并增加缩容扰动。
Scheduler Profile 适合在同一个 kube-scheduler 进程中提供不同评分行为;自定义调度器和 Volcano 则引入新的控制面、HA、升级与责任边界。Descheduler 用于处理已经发生的偏斜,但必须从 dry-run 开始,限制驱逐量,并证明被驱逐 Pod 有地方重新运行。
批任务需要先准入,再谈放置
在线服务通常持续运行并通过副本吸收流量,批任务则可以排队、成组启动、借用配额或等待昂贵加速器。Kueue 通过 ClusterQueue、LocalQueue、ResourceFlavor 和 Workload 决定作业何时获得配额;Volcano 通过 Queue、PodGroup、Gang Scheduling 和调度插件决定指定工作负载如何放置。前者不替代 kube-scheduler,后者也不应被当成单纯的配额控制器。
队列治理要防止两个极端:静态配额把资源闲置在没有作业的租户,过度借用与抢占又让低优先级任务永远饥饿。团队应同时观察排队时间、准入时间、运行时间、抢占次数、资源利用率和业务优先级,并为错误优先级、错误 Flavor 与 webhook 故障保留暂停入口。
容量治理以业务恢复时间收口
月度容量评审不应只展示平均 CPU。至少要保留峰值和分位数、request/usage 比例、不可调度时间、扩容各阶段耗时、节点碎片率、队列等待、驱逐与重启、配额失败、云实例缺货以及应用冷启动。每次版本升级或控制参数修改,都用同一组阶梯、突发和故障负载重新测量。
容量冗余也需要明确预算:零缓冲节省空闲成本却把全部恢复时间交给节点创建;负优先级占位 Pod 可以快速释放调度容量,但不能消除镜像和应用冷启动;预热节点恢复最快,却长期付费。架构师要给出允许的 P95 恢复时间、可接受的空闲比例、故障域余量和停止扩容条件,而不是追求一个脱离业务的利用率数字。
工程收口清单
指标有语义、新鲜度、访问控制、失败策略和 owner。replicas、requests、节点对象与队列配额各有唯一写入者。HPA、VPA、KEDA、节点伸缩器和 GitOps 的冻结与回退顺序经过演练。
Pending Pod 能按 request、约束、PVC、优先级、配额、库存和权限分型。节点供给时间线覆盖云请求、实例启动、Node Ready、CNI/CSI、镜像与 Pod Ready。调度约束与故障域有容量代价评估,Descheduler 从 dry-run 和驱逐预算开始。
Kueue 准入与 Volcano 放置的对象、状态和责任清晰分离。实验使用隔离 Namespace、最小 RBAC、占位凭证和可核对清理步骤。扩容成功以吞吐、延迟、错误率或队列水位恢复为准,不以控制器单一状态代替。
升级、迁移和退出后核销 CRD、webhook、ClusterRole、云 IAM、节点、磁盘与持续费用。
