资源管理与自动伸缩:requests、limits、HPA 与 VPA
安全模型控制谁能做什么,资源管理控制能用多少。这两个维度共同决定了集群的多租户能力。requests 和 limits 是 Kubernetes 资源模型的基础,理解它们的精确语义是做好容量规划的前提。HPA 和 VPA 在此基础上提供了自动化的弹性能力。
在传统物理机和虚拟机时代,资源隔离靠硬件分区或 hypervisor,资源弹性靠人工扩容。Kubernetes 把资源管理内置为一等公民,通过 cgroup 在内核层面强制执行,通过控制器实现自动化弹性。这一篇的核心问题是:requests 和 limits 如何影响调度和运行时行为,HPA 和 VPA 如何根据负载自动伸缩?
资源模型全景
1 | |
1 | |
核心概念
requests 与 limits 精确语义
1 | |
CPU requests 转化为 cgroup cpu.shares(值 = requests * 1024 / 1000,最小 2),在节点 CPU 竞争时按比例分配时间片。CPU limits 转化为 CFS bandwidth(cpu.cfs_quota_us),以 100ms 为周期(cpu.cfs_period_us = 100000),若进程在周期内用完配额,剩余时间被 throttle。
Memory requests 只影响调度,运行时不限制实际内存使用(直到超过 limits)。Memory limits 转化为 cgroup memory.limit_in_bytes,超过后内核的 OOM killer 会选择杀死该 cgroup 中的进程。
CPU throttling 是一个常被忽视的性能问题。container_cpu_throttled_seconds_total 和 container_cpu_cfs_throttled_periods_total 是两个重要的 Prometheus 指标,throttling 比例超过 25% 通常意味着 CPU limits 设置过低。
QoS 类型
| QoS 类型 | 条件 | 驱逐优先级 |
|---|---|---|
| Guaranteed | 所有容器:requests == limits,且两者都已设置 | 最低(最后被驱逐) |
| Burstable | 有 requests,但不等于 limits;或只设置了 limits | 中等 |
| BestEffort | 没有任何 requests 和 limits | 最高(第一个被驱逐) |
节点内存压力时,kubelet 按 BestEffort → Burstable → Guaranteed 的顺序驱逐 Pod。Guaranteed Pod 只有在节点极端内存压力(超出所有 limits)时才会被驱逐。
ResourceQuota
ResourceQuota 限制命名空间内的资源总量:
1 | |
当 namespace 有 ResourceQuota 时,所有新创建的 Pod 必须显式设置 requests 和 limits,否则被拒绝。
LimitRange
LimitRange 定义命名空间的默认值和约束范围,自动注入没有设置资源的 Pod:
1 | |
HPA(Horizontal Pod Autoscaler)
HPA 根据指标自动调整 Deployment/StatefulSet/ReplicaSet 的副本数:
1 | |
HPA 控制算法
1 | |
HPA 每 15 秒(默认)从 metrics-server 或 Prometheus Adapter 获取指标,计算目标副本数,通过 Scale 子资源更新 Deployment。scaleDown 有 5 分钟默认稳定窗口,防止指标短暂下降导致频繁扩缩容。
自定义指标 HPA
需要安装 Prometheus Adapter,把 Prometheus 指标注册为 Custom Metrics API:
1 | |
VPA(Vertical Pod Autoscaler)
VPA 自动推荐或调整 Pod 的 requests/limits:
1 | |
VPA 的四种模式:
| 模式 | 行为 |
|---|---|
| Off | 只计算推荐值,不做任何修改,用于观察 |
| Initial | 只在 Pod 创建时应用推荐值,运行中不变 |
| Recreate | 当推荐值与当前值差异超过阈值时,驱逐 Pod 重建 |
| Auto | 与 Recreate 相同(未来可能支持 in-place 更新) |
VPA 和 HPA 的冲突:两者同时对 CPU 指标操作时,HPA 根据当前 requests 计算扩容目标,VPA 同时调整 requests,导致相互干扰。标准做法:HPA 用 CPU,VPA 用内存(设置 controlledResources: ["memory"]),或用 KEDA 完全替代 HPA。
KEDA(Kubernetes Event-Driven Autoscaling)
KEDA 把任意外部指标(消息队列深度、数据库行数、HTTP 请求速率)转化为 HPA 兼容的自定义指标:
1 | |
KEDA 支持缩容到 0(scale-to-zero),在无流量时彻底停止工作负载,节省资源。
实验:HPA 在压力下的扩缩容
1 | |
映射到 Kubernetes 内部机制
HPA Controller 是 kube-controller-manager 中的一个控制器,每 15 秒(--horizontal-pod-autoscaler-sync-period)执行一次调谐循环:获取当前副本数 → 从 metrics API 获取指标 → 计算目标副本数 → 调用 Scale 子资源更新副本数。
VPA 由三个组件组成:VPA Recommender(持续从 metrics API 收集历史数据,使用指数加权移动平均计算推荐值)、VPA Updater(监控 VPA 对象,决定何时驱逐 Pod 使新配置生效)、VPA Admission Plugin(Pod 创建时,根据推荐值修改 requests/limits)。
节点驱逐的触发机制:kubelet 监控节点内存和磁盘压力,设置 Node Condition(MemoryPressure、DiskPressure)。当压力超过 soft eviction threshold 并持续一段时间,或超过 hard eviction threshold 时,kubelet 立即开始按 QoS 优先级驱逐 Pod。
模式提炼
requests 是"预订",limits 是"上限",QoS 是"驱逐优先级"。HPA 解决"需要多少个 Pod"的问题,VPA 解决"每个 Pod 需要多大"的问题,两者在不同维度互补。KEDA 扩展了 HPA 的指标来源,特别适合消息驱动的异步处理场景。
资源管理的本质是在"资源利用率"和"服务稳定性"之间找平衡点。requests 设得太低,调度过多 Pod 到节点导致资源争用;limits 设得太低,正常负载下出现 CPU throttling 或 OOM kill;limits 设得太高,资源浪费。正确的做法是从 Off 模式的 VPA 推荐开始,结合压测数据调整。
工程迁移表
| 资源机制 | 工程类比 |
|---|---|
| requests / limits | 线程池 coreSize / maxSize,数据库连接池 minIdle / maxActive |
| CPU throttling(CFS) | 进程 nice 值调度,CPU 时间片轮转 |
| OOM kill | JVM -Xmx 超出后的 OutOfMemoryError,进程 crash |
| QoS Guaranteed | 高优先级服务,独占资源,类似实时任务调度 |
| HPA | 云厂商 Auto Scaling Group,按负载自动增减实例 |
| VPA | 数据库实例垂直扩容,升级到更大规格的 VM |
| KEDA | 消息队列消费者动态扩缩,AWS Lambda 函数触发 |
| ResourceQuota | 云账号 Service Quota,部门 cost center 限额 |
常见误解
误解一:requests 等于容器实际使用的资源量,设高了浪费,设低了不够用。
requests 只影响调度,不限制实际使用。一个 requests 100m CPU 的容器,如果节点有空闲,可以使用更多 CPU(直到 limits)。在资源充足时,所有容器都能获得比 requests 更多的资源;只有在竞争时,requests 才决定各容器的分配比例。因此,requests 应该设置为正常负载下的典型使用量,不是峰值,也不是最低值。
误解二:CPU limits 会像内存 OOM 一样杀死进程。
CPU 超出 limits 的结果是 throttling(限速),不是进程终止。内核会在 CFS 周期(100ms)内限制该 cgroup 的 CPU 时间,进程继续运行,但速度变慢。这对延迟敏感的应用影响很大(P99 延迟升高),但不会导致 crash。内存超 limits 才会触发 OOM kill。两种超限的后果完全不同。
误解三:HPA 和 VPA 可以同时作用于同一应用的 CPU 指标,互相补充。
HPA 基于 CPU 使用率(相对于 requests)计算目标副本数。如果 VPA 同时调整 requests,HPA 的基准值发生变化,计算出的目标副本数也随之变化,两个控制器会相互干扰,导致副本数震荡。标准做法是:HPA 管副本数(基于 CPU 指标),VPA 只管内存(设置 controlledResources: ["memory"]),或者放弃 HPA,用 KEDA + VPA 的组合。
练习
练习一:CPU throttling 分析。部署一个设置了较低 CPU limits 的应用,用 kubectl exec 进入容器运行 CPU 密集计算,同时通过 kubectl top pod 和 cgroup 文件(/sys/fs/cgroup/cpu/cpu.stat 中的 nr_throttled)观察 throttling 现象。对比提高 limits 前后的延迟变化。
练习二:QoS 驱逐实验。在一个内存有限的测试节点上,分别创建 Guaranteed、Burstable、BestEffort 三类 Pod,然后逐渐填满节点内存(使用内存泄漏测试工具),观察 kubelet 的驱逐顺序是否符合 QoS 规则,查看 kubelet 日志中的驱逐决策。
练习三:HPA 冷却参数调优。部署一个 HPA,默认配置下产生一次负载尖峰,观察扩缩容的过程和时间线。调整 scaleDown.stabilizationWindowSeconds 和 scaleUp.stabilizationWindowSeconds,对比不同参数下系统对负载变化的响应速度和稳定性,理解冷却参数的权衡。
系列导航
- 00 核心概念导读
- 01 API Server 与声明式 API
- 02 etcd 与持久化
- 03 控制器模式与 Informer 机制
- 04 Pod 生命周期
- 05 调度器深入
- 06 kubelet 与容器运行时
- 07 网络模型与 CNI
- 08 Service 与 kube-proxy
- 09 Ingress 与 Gateway API
- 10 存储体系
- 11 配置与密钥管理
- 12 RBAC 与安全模型
- 13 资源管理与自动伸缩
- 14 CRD 与 Operator 模式
- 15 Helm 与应用打包
- 16 可观测性
- 17 生产集群运维
参考资料
- Resource Management for Pods and Containers: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
- Horizontal Pod Autoscaling: https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/
- Vertical Pod Autoscaler: https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler
- KEDA: https://keda.sh/
- Configure Quality of Service for Pods: https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/
