Kubernetes 基础体系 · 第 69/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes 容量规划:Request、利用率、碎片、故障余量和压测
容量规划不是回答“集群还有多少 CPU”这么简单,而是要同时回答以下问题:
- 调度器还能不能放下下一个 Pod?
- 现有 Pod 的
requests是否接近真实需求? - 节点剩余资源是否因为碎片而无法使用?
- 丢失一个节点后,副本能否重新调度?
- 业务高峰、滚动发布和故障恢复是否会同时发生?
- 压测得到的数字,是否能支持生产容量决策?
Kubernetes 中至少存在三种容易混淆的“容量”:
- 调度容量:由 Pod 的
requests和节点的Allocatable决定。 - 运行时容量:由 CPU、内存、磁盘、网络和内核等实际资源决定。
- 服务容量:由延迟、错误率、吞吐量、队列长度等业务 SLO 决定。
一个节点可能还有 30% 的 CPU 空闲,但因为内存不足、Pod 请求过大或拓扑约束限制,仍然无法调度新的 Pod。反过来,调度器可能认为容量充足,但实际运行时已经出现 CPU Throttle、内存回收或业务延迟恶化。
一、先定义容量规划中的几个核心量
1. Capacity、Allocatable 和 Available 不是同一个概念
对节点来说,可以先把资源分成三个层次:
Capacity:节点向 Kubernetes 报告的总资源。Allocatable:允许普通 Pod 使用的资源上限。Available:某个时刻尚未被已调度 Pod 的requests消耗的资源。
近似关系是:
其中:
- 是集群的调度容量;
Request(pod)是 Pod 的有效资源请求;Available只是调度意义上的剩余量,不等于操作系统此刻的空闲量。
节点的 Allocatable 通常小于 Capacity,因为节点需要为以下组件预留资源:
- kubelet;
- 容器运行时;
- 操作系统进程;
- Kubernetes 系统 Pod;
- 内存压力和磁盘压力相关的系统余量。
节点可以通过 kubeReserved、systemReserved、evictionHard 等配置控制部分预留,但具体生效方式取决于 Kubernetes 发行版、kubelet 配置和云厂商节点镜像。不能简单地用云主机规格减去业务 Pod 请求来计算可用容量。
查看节点容量:
kubectl get nodes -o custom-columns=\
NAME:.metadata.name,\
CPU_CAPACITY:.status.capacity.cpu,\
CPU_ALLOCATABLE:.status.allocatable.cpu,\
MEM_CAPACITY:.status.capacity.memory,\
MEM_ALLOCATABLE:.status.allocatable.memory
可能得到:
NAME CPU_CAPACITY CPU_ALLOCATABLE MEM_CAPACITY MEM_ALLOCATABLE
worker-01 8 7600m 32Gi 29Gi
worker-02 8 7600m 32Gi 29Gi
这里不能把 8 CPU 和 7600m 混为一谈。调度器使用的是 Allocatable,因此两个节点的集群调度 CPU 容量是:
不是 16000m。
2. Request 是调度承诺,不是实际使用量
容器资源声明通常包含:
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
request 表示调度器在放置 Pod 时必须为其“预留”的资源额度。对 CPU 而言,500m 表示 0.5 个逻辑 CPU;对内存而言,512Mi 表示 512 MiB 的调度请求。
但 request 不意味着:
- 容器启动后一定立刻获得这么多 CPU;
- 节点上真的切出一块独占内存给它;
- 容器实际持续使用这么多资源;
- 内存使用超过 request 就一定被杀死。
limit 才是运行时的上限语义,但 CPU 和内存的行为不同:
- CPU 超过 limit,通常受到 CFS 配额限制,表现为 CPU Throttle;
- 内存超过 limit,可能触发该容器的 cgroup OOM;
- Pod 没有内存 limit 时,仍可能受到节点内存压力和驱逐影响;
- 具体 OOM 选择还会受到 QoS、
oom_score_adj、节点压力和进程行为影响。
因此,容量规划必须同时记录:
这三个量分别回答“调度占用”“当前使用”和“理论运行上限”。
二、Pod 的有效 Request 如何计算
不能只把 YAML 中所有容器的数字机械相加,因为 Pod 还可能包含 Init Container 和 Pod Overhead。
对某个资源维度 ,一个 Pod 的有效请求近似为:
原因是:
- 普通容器通常并行运行,因此请求需要求和;
- Init Container 通常串行运行,因此它们之间取最大值;
- Pod Overhead 用于运行时或沙箱等额外开销,适用于配置了相应 RuntimeClass 的场景。
例如:
spec:
initContainers:
- name: migrate
resources:
requests:
cpu: "2"
memory: "1Gi"
containers:
- name: api
resources:
requests:
cpu: "500m"
memory: "512Mi"
- name: sidecar
resources:
requests:
cpu: "200m"
memory: "256Mi"
CPU 的有效请求是:
内存的有效请求是:
这会造成一个常见现象:业务容器运行后只需要 700m CPU,但 Init Container 的 2 CPU 请求会在调度阶段把整个 Pod 当成 2 CPU。如果 Init Container 长期存在于 Deployment 模板中,滚动更新时还会持续影响调度和扩容。
验证 Pod 实际请求:
kubectl describe pod <pod-name>
重点查看:
Requests:
cpu: 2
memory: 1Gi
也可以通过 Prometheus 的 kube-state-metrics 指标统计:
sum by (namespace, pod) (
kube_pod_container_resource_requests{
resource="cpu",
unit="core"
}
)
但要注意,这类指标通常按容器暴露,Init Container 和 Pod Overhead 的处理不能仅凭一条简单查询完全还原。容量审计工具必须明确是否实现了 Kubernetes 对 Pod 有效请求的计算规则。
三、利用率:分母不同,结论就不同
“CPU 利用率 70%”没有上下文时几乎没有意义。至少要明确分母。
1. 相对 Capacity 的利用率
例如节点有 8 个 CPU,实际使用 4 个 CPU:
这反映硬件或虚拟机总能力的使用程度。
2. 相对 Allocatable 的利用率
如果 Allocatable=7.6 CPU:
对于 Kubernetes 调度容量,这个分母通常比 Capacity 更合理。
3. 相对 Request 的利用率
如果 Pod 的 CPU request 是 500m,实际使用 250m:
HPA 的 CPU 利用率通常就是“实际 CPU 使用量相对于 Pod CPU request 的比例”,而不是相对于节点总 CPU。也因此,request 的大小会直接影响 HPA 的行为:
- request 过小:相同实际使用量会产生更高的利用率,HPA 更容易扩容;
- request 过大:利用率看起来偏低,HPA 可能扩容较慢;
- 没有合适的 CPU request:基于资源利用率的 HPA 可能无法正确计算。
4. 一个完整算例
假设节点:
- Capacity:8 CPU;
- Allocatable:7.6 CPU;
- 所有 Pod 的 CPU request 总和:6 CPU;
- 实际 CPU 使用:3 CPU。
那么:
三者都正确,但含义不同:
- 37.5%:相对整台机器;
- 39.5%:相对可被 Kubernetes 使用的节点资源;
- 50%:已声明请求被实际使用了一半。
因此,“节点 CPU 只有 40%,为什么 HPA 已经扩容?”并不矛盾。HPA 可能看到的是 Pod 相对 request 的 80%,而节点总 CPU 仍然只有 40%。
四、Request 应该如何从实际数据推导
1. CPU Request 的基本推导
CPU 使用具有较强的时间波动性。可以定义一个采样窗口内的 CPU 使用量随机变量 ,然后选择目标利用率 :
例如:
- 业务高峰期间 CPU 使用量的 P95 为
350m; - 希望高峰时 request 利用率不超过 70%;
- 则建议初始 request:
这个推导的直觉是:即使达到 P95 使用量,仍然保留约 30% 的 request 余量。
但这不是 Kubernetes 的规范要求,而是容量建模方法。选择 P95、P99 还是最大值,需要结合:
- SLO 对延迟尖峰的容忍程度;
- HPA 的扩容速度;
- 单 Pod 承载的并发量;
- CPU limit 是否存在;
- 节点是否允许 CPU 超卖;
- 业务是否对 CPU Throttle 敏感。
如果业务是低延迟交易服务,CPU P99 可能比 P95 更有意义;如果是异步批处理,允许排队,则可以选择更高的超卖比例。
2. 内存 Request 不能只看平均值
内存通常不像 CPU 那样适合依靠短时间超卖,因为内存不可压缩。CPU 不够时,进程可能变慢;内存不够时,进程可能被 OOM。
可以使用:
其中:
PeakWorkingSet是观测周期内的峰值工作集;- 是应用增长、分配抖动和测量误差余量。
例如一个服务在 14 天内的工作集峰值为 1.4Gi,预留 25%:
可以向上取整为 2Gi。
这里要区分 RSS、working set、cache 和 page fault。监控系统中的“内存使用量”可能包含不同 cgroup 指标,不能直接把某一条 kubectl top 数值当成唯一事实。生产上应明确:
- 使用的是 cgroup memory current、working set 还是 RSS;
- 是否包含文件缓存;
- 容器运行时和监控组件的采样周期;
- 是否覆盖启动、GC、缓存预热和流量切换阶段。
3. Request 与 Limit 的关系
一种常见配置是:
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "1"
memory: "2Gi"
其含义是:
- 调度阶段按
500m CPU和1Gi memory计算; - 运行时 CPU 最高允许接近
1 CPU,超过后可能被 throttle; - 运行时内存达到
2Gi附近可能触发 cgroup OOM; - 节点压力下,该 Pod 的 QoS 和实际使用会影响驱逐风险。
CPU limit 不是“可持续获得的 CPU 保证”。当节点繁忙时,容器即使没有达到 CPU limit,也可能只能获得接近 request 所对应的权重;当 CPU limit 较低时,周期性负载可能出现明显 CFS throttling。
内存 limit 也不是容量规划中的“可额外借用空间”。如果所有 Pod 的 memory request 很小、limit 很大,调度器可能允许大量 Pod 进入节点,但高峰时它们会竞争同一块物理内存,最终导致 OOM 或 Eviction。
五、碎片:总剩余资源足够,但仍然调度失败
1. 一维碎片的基本例子
假设有两个节点,每个节点的 CPU Allocatable 是 8 CPU。
当前状态:
node-a: 已用 request 5 CPU,剩余 3 CPU
node-b: 已用 request 5 CPU,剩余 3 CPU
集群剩余 CPU 总量:
此时来了一个需要 4 CPU 的 Pod。虽然集群总剩余量是 6 CPU,但每个节点都只有 3 CPU,因此 Pod 无法放置。
这就是外部碎片:总空间足够,但没有足够大的连续可放置单元。
如果把一组小 Pod 合并或重新调度,可能变成:
node-a: 已用 request 8 CPU,剩余 0 CPU
node-b: 已用 request 2 CPU,剩余 6 CPU
此时同一个 4 CPU Pod 可以调度。集群总容量没有变化,只改变了资源分布。
2. 多资源维度下的碎片
Kubernetes 调度不是只看 CPU。假设:
node-a: 剩余 2 CPU,剩余 8Gi memory
node-b: 剩余 8 CPU,剩余 1Gi memory
新 Pod 请求:
cpu: 4
memory: 4Gi
总剩余资源为:
CPU: 10
Memory: 9Gi
但没有任何节点同时满足 4 CPU 和 4Gi memory,所以仍然无法调度。
这可以形式化为:Pod 能否放到节点 ,必须对每一个资源维度 都满足:
只要有一个维度不满足,该节点就是不可行节点。
3. 影响碎片的调度约束
以下因素会把“可用资源”进一步切碎:
- Pod 的
nodeSelector和 Node Affinity; - Pod Anti-Affinity;
- topology spread constraints;
- Taint 和 Toleration;
- 可用区、机型、架构等节点标签;
- GPU、FPGA 等不可超卖的扩展资源;
- PersistentVolume 的拓扑限制;
- 节点最大 Pod 数;
- CNI 地址池和 Pod IP 数量;
- DaemonSet 在每个节点上消耗的请求;
- PodDisruptionBudget 对驱逐和滚动操作的限制。
因此,容量模型不能只写成:
这个公式只适合非常理想的一维场景。
更现实的模型是先为每个节点类型 定义可分配向量:
然后检查每个 Pod 是否能找到满足所有约束的节点。这个问题本质上接近多维装箱问题,通常没有简单的精确闭式解,因此必须使用实际调度模拟、历史调度结果和压测验证。
4. 选择调度策略也会改变碎片
Kubernetes 调度器通常会先过滤不满足约束的节点,再对可行节点打分。与资源分布相关的调度插件会影响 Pod 是倾向于“填满已有节点”还是“摊开到更多节点”。
- 偏向填充节点,可以减少空闲节点数量,但可能形成大块节点级碎片,也会降低单节点故障后的弹性;
- 偏向分散,可以提高故障隔离和副本分布,但会留下更多小块空闲资源;
- topology spread 能改善跨区或跨节点均衡,但均衡约束本身可能降低装箱效率。
所以“减少碎片”和“提高故障容余”经常存在张力,不能只优化其中一个指标。
六、故障余量:不是把平均利用率降到某个百分比
1. N+1 的形式化条件
设集群有 个节点,每个节点的可调度容量为 。业务在目标高峰下需要的请求为 。
如果要求任意一个节点故障后,剩余节点仍能容纳业务请求,则至少需要满足:
在同规格节点场景中,节点容量为 ,则:
如果还要求保留额外安全余量 ,可以写为:
例如:
- 5 个节点;
- 每个节点 Allocatable 为
7.6 CPU; - 目标高峰请求为
25 CPU; - 要求任意一个节点故障后再保留 15% CPU 余量。
故障后的有效容量:
因为:
所以在纯 CPU、无其他约束的理想模型中满足 N+1。但如果内存、拓扑、DaemonSet、Pod 数量或大 Pod 碎片不满足,CPU 计算仍然不能证明系统安全。
2. 故障余量必须按资源维度分别计算
假设故障后剩余节点提供:
CPU: 30 CPU
Memory: 100 Gi
业务高峰请求:
CPU: 20 CPU
Memory: 95 Gi
CPU 余量是 33%,但内存余量只有 5%。集群的有效故障余量应由更紧张的维度决定,不能只看 CPU。
可以对每种资源计算:
本例中:
内存才是故障场景下的主要瓶颈。
3. 故障余量还要覆盖恢复过程
“丢失一个节点后还能运行”不等于“能够自动恢复”。
故障恢复通常经历:
- 节点失联;
- Node Controller 观察到状态变化;
- Pod 被判定为不可继续运行;
- ReplicaSet、StatefulSet 或其他控制器创建替代 Pod;
- 调度器寻找可行节点;
- 镜像拉取、容器启动、探针通过;
- Service Endpoint 更新;
- 流量逐步恢复。
在此期间还可能有:
- 节点故障检测延迟;
- 镜像仓库限流;
- 多个 Pod 同时拉镜像造成网络拥塞;
- PVC 跨区挂载限制;
- PodDisruptionBudget 或拓扑约束导致替代 Pod 无法放置;
- HPA/VPA 或 Cluster Autoscaler 的决策延迟。
因此,故障余量至少应覆盖两个阶段:
- 静态容量余量:故障后请求能否放下;
- 恢复容量余量:替代副本启动和预热期间是否会产生额外负载。
滚动发布也会制造额外容量需求。对于 Deployment,滚动更新期间可能同时存在旧 Pod 和新 Pod。maxSurge 越大,更新期间的瞬时请求越高;如果节点没有足够余量,更新可能卡在 Pending。
检查 Pending Pod 的真实原因:
kubectl describe pod <pending-pod>
重点看 Events,例如:
0/5 nodes are available:
2 Insufficient cpu,
1 Insufficient memory,
2 node(s) didn't match Pod's node affinity/selector.
这三类信息分别说明:
- 总资源不足;
- 某个资源维度不足;
- 约束造成的碎片或隔离。
七、利用率目标如何与容量目标同时成立
容量规划中常用的两类目标经常互相冲突:
- 正常运行时,希望节点利用率较高,减少空闲成本;
- 故障和高峰时,希望保留足够余量。
设正常业务请求为 ,高峰请求为 ,故障后的有效容量为 。同时要求:
其中:
- 是正常调度容量目标;
- 是故障后的额外余量;
- 是正常状态下集群容量。
如果只根据平均流量计算节点数量:
可能得到很高的平均利用率,但高峰或故障时完全没有恢复空间。
一个完整算例
假设:
- 单节点 CPU Allocatable:
7.6 CPU; - 正常高峰业务 request:
20 CPU; - 希望故障后仍保留 20% CPU 余量;
- 要求任意一个节点故障后继续运行。
需要节点数 满足:
因此:
至少需要 5 个节点。
如果只按无故障容量计算:
3 个节点能够承载正常高峰,但丢失一个节点后只剩:
无法承载 20 CPU。这就是“平均容量足够,但故障余量不足”。
八、QoS、Eviction 和容量规划的关系
Kubernetes 根据容器的 requests 和 limits 为 Pod 分配 QoS 类别:
Guaranteed
通常要求 Pod 中每个容器对 CPU 和内存都同时设置 request 和 limit,且两者相等。
resources:
requests:
cpu: "1"
memory: "1Gi"
limits:
cpu: "1"
memory: "1Gi"
Burstable
设置了部分或全部 requests/limits,但不满足 Guaranteed 条件。
BestEffort
Pod 中没有任何容器设置 CPU 或内存 requests/limits。
QoS 影响节点内存压力下的驱逐优先级,但不能把它理解为“Guaranteed Pod 永远不会被杀”。如果节点自身不可恢复、内核发生 OOM、容器触发自己的 memory limit,Guaranteed 也可能终止。
容量规划中的关键误区是:
“Memory request 小于 memory limit,所以多出来的部分可以当成安全容量。”
不能这样计算。假设节点 Allocatable 是 16Gi,调度上的 Pod memory request 总和为 12Gi,但实际高峰使用可能达到 18Gi。调度器认为还有 4Gi 空间,运行时却会发生节点内存压力。
因此应同时检查:
sum(memory requests) <= allocatable memory
peak(actual memory usage) <= allocatable memory - system reserve
并且为故障、滚动更新和突发分配保留余量。
查看 Pod QoS:
kubectl get pod <pod-name> \
-o jsonpath='{.status.qosClass}{"\n"}'
查看节点是否发生压力:
kubectl describe node <node-name>
关注:
Conditions:
MemoryPressure False
DiskPressure False
PIDPressure False
也要检查事件:
kubectl get events --all-namespaces \
--field-selector reason=Evicted \
--sort-by=.lastTimestamp
Eviction 是 kubelet 在节点压力下主动终止 Pod 的机制,和容器因 memory limit 触发 cgroup OOM 不是同一条路径:
- cgroup OOM:某个 cgroup 的内存限制被触发;
- 节点 Eviction:节点整体资源压力达到 kubelet 阈值,kubelet 主动驱逐 Pod。
诊断时应同时查看 Pod 终止原因、容器状态、节点事件和内核日志,不能只看到 OOMKilled 就断言是节点驱逐。
九、HPA 和 VPA 会改变容量模型
1. HPA 的 request 依赖
基于 CPU 利用率的 HPA 通常计算:
假设每个 Pod:
CPU request = 500m
实际使用 = 400m
目标利用率 = 70%
则当前利用率:
期望副本数近似为:
如果当前有 3 个副本:
实际 HPA 还会受到采样窗口、缺失指标、初始化期间、缩容稳定窗口和多指标取最大值等因素影响,因此不能把公式当成精确的即时控制器实现。
request 过小会使 HPA 过度敏感;request 过大则可能让 HPA 长期认为利用率不高。容量规划应先让 request 接近真实稳态需求,再调整 HPA 目标,而不是用错误 request 来“校准”扩缩容。
2. HPA 扩容本身需要容量余量
HPA 产生更多副本后,新增 Pod 需要经过:
- 控制器修改副本数;
- 创建 Pod;
- 调度;
- 拉取镜像;
- 启动容器;
- 通过 Readiness;
- 接收流量。
如果集群没有可调度空间,HPA 只会创建更多 Pending Pod,不能自动解决底层节点容量不足。此时通常需要 Cluster Autoscaler 或其他节点供应机制,但它们也有启动延迟、节点组约束和云厂商配额限制。
因此,HPA 容量规划要验证:
并且该条件要在高峰和单节点故障场景下成立。
3. VPA 与 HPA 的冲突
VPA 会根据历史使用情况给出或应用新的资源建议。若 VPA 调整 CPU request,而 HPA 又根据 CPU request 计算利用率,可能产生反馈变化:
- VPA 增大 request;
- 相同实际使用量对应的利用率下降;
- HPA 缩容或扩容变慢;
- VPA 再根据新的使用轨迹调整 request。
CPU 维度同时使用 HPA 和 VPA 时需要谨慎设计,常见做法是让 HPA 基于外部业务指标或并发量扩缩容,而让 VPA 主要负责内存建议;具体策略取决于实现和版本。VPA 并非 Kubernetes 核心稳定 API,其安装方式、更新模式和重启行为必须以所用项目版本为准,不能把它当成所有集群默认内置能力。
十、容量观测:不要只使用 kubectl top
kubectl top 依赖 Metrics Server,适合查看当前资源使用概况:
kubectl top nodes
kubectl top pods -A --sort-by=cpu
kubectl top pods -A --sort-by=memory
但它存在几个边界:
- 主要反映当前或短窗口数据;
- 不提供完整的 P95/P99 历史分布;
- 不能直接解释 CPU Throttle;
- 不能替代业务延迟、吞吐和错误率;
- 不能直接告诉你碎片、拓扑约束和 Pending 原因;
- Metrics Server 不等同于完整监控系统。
生产容量规划至少需要同时采集以下数据:
1. 调度层数据
- 节点 Capacity 和 Allocatable;
- Pod requests 和 limits;
- Pending Pod 数量及原因;
- 节点不可调度状态;
- Pod 分布、拓扑和亲和性;
- DaemonSet 资源开销;
- 最大 Pod 数和 IP 使用量。
2. 运行时数据
- CPU 使用量;
- CPU Throttle 时间或比例;
- 内存 working set、RSS、cgroup current;
- OOMKilled;
- 节点 MemoryPressure;
- 磁盘空间和 inode;
- ephemeral-storage 使用;
- 网络带宽、丢包和连接数;
- PID 使用量。
3. 服务层数据
- 请求吞吐;
- 并发数;
- P50、P95、P99 延迟;
- 5xx 和业务错误率;
- 队列长度;
- 数据库连接池;
- 下游依赖延迟;
- GC、线程池和文件描述符。
一个典型 Prometheus 查询示例:
sum(
rate(container_cpu_usage_seconds_total{
container!="",
image!=""
}[5m])
)
它表示容器 CPU 使用率的总和,单位近似为 CPU core。若要计算相对于 request 的利用率,需要把它与对应容器或 Pod 的 CPU request 对齐。实际生产查询必须处理:
- 容器重启;
- Pod 名称变化;
- Init Container;
- 多容器 Pod;
- 指标标签重复;
- 缺失 request;
- kube-state-metrics 与 kubelet 指标的时间对齐。
简单地把全局使用量除以全局 request,可能掩盖某个节点已经过载而另一个节点空闲的情况。因此,容量仪表盘应至少同时展示:
集群总量
节点分布
命名空间分布
工作负载分布
资源维度
峰值和分位数
Pending 与驱逐
十一、压测:验证“能运行”之外的服务容量
容量压测的目标不是找一个单独的 QPS 数字,而是确定在给定 SLO 下,系统能稳定承载多少负载,以及增加容量后瓶颈是否真的移动。
1. 先定义压测变量
一个可复现的压测模型至少要固定:
- 请求类型和请求比例;
- payload 大小;
- 并发模型;
- 到达率;
- 依赖服务;
- 数据集规模;
- 缓存命中率;
- 副本数;
- 节点规格;
- CPU 和内存 request/limit;
- HPA 是否启用;
- 是否包含发布和故障注入。
压测需要区分两种负载模型:
闭环模型
固定并发数。一个请求结束后,客户端才发出下一个请求。
并发数固定 → 响应变慢 → 实际到达率下降
它适合观察固定并发下的响应时间,但在系统变慢时会自动降低压力,可能掩盖排队问题。
开环模型
固定到达率。无论前面的请求是否完成,测试器都按设定速率继续发送。
到达率固定 → 系统变慢 → 队列增长 → 延迟和错误暴露
它更适合验证“每秒进入多少请求时系统开始失稳”,但需要限制测试器自身的连接和内存,否则测试器会先成为瓶颈。
2. 压测阶段
一个完整流程通常包含:
- 基线测试:单副本或固定副本,确认功能和指标链路正确;
- 预热:等待 JVM、Go runtime、缓存、连接池和 HPA 指标稳定;
- 阶梯加压:逐步提高并发或到达率;
- 稳态观察:每个阶梯保持足够时间,观察资源、延迟和错误;
- 峰值保持:验证目标峰值是否能持续;
- 突发测试:观察短时间流量冲击;
- 扩容测试:记录 HPA、调度、节点扩容和 Ready 延迟;
- 故障测试:删除节点或阻断依赖,验证 N+1 和恢复能力;
- 回落测试:观察缩容和资源释放是否稳定。
不能只记录压测工具输出的平均延迟。平均值可能是:
P50 = 20ms
P95 = 30ms
P99 = 2s
此时平均值仍然很好,但尾延迟已经明显违反很多业务 SLO。
3. 一个可运行的 Kubernetes 压测示例
以下 Deployment 使用 httpbin 作为示例 HTTP 服务:
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
spec:
replicas: 2
selector:
matchLabels:
app: httpbin
template:
metadata:
labels:
app: httpbin
spec:
containers:
- name: httpbin
image: kennethreitz/httpbin:latest
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
spec:
selector:
app: httpbin
ports:
- port: 80
targetPort: 80
应用并等待 Ready:
kubectl apply -f httpbin.yaml
kubectl rollout status deployment/httpbin
kubectl get pods -l app=httpbin -o wide
启动一个临时客户端:
kubectl run loadgen \
--rm -it \
--image=busybox:1.36 \
--restart=Never \
-- sh
在容器内执行:
while true; do
wget -q -O- http://httpbin/get >/dev/null
done
这个示例只适合验证 Service 连通性和产生非常粗糙的请求流量,不适合正式性能测试,因为:
- BusyBox 循环不是严格的并发或固定到达率模型;
- 没有可靠的延迟分位数统计;
- 客户端本身可能成为瓶颈;
httpbin不是业务应用;- 镜像标签使用
latest不利于复现。
正式压测应使用固定版本的压测工具或专用压测平台,并把压测客户端运行在独立节点或集群之外。否则客户端 CPU、网络和连接池会污染结果。
4. 如何从压测结果反推 Request
假设压测得到以下结果:
目标业务 SLO:P99 < 200ms,错误率 < 0.1%
稳定吞吐:每个 Pod 120 RPS
稳定状态 CPU P95:420m
稳定状态内存峰值:900Mi
如果目标 CPU request 利用率为 70%,则:
内存按峰值加 25%:
可以设置为:
resources:
requests:
cpu: "600m"
memory: "1200Mi"
如果目标高峰为 1000 RPS,则按每 Pod 120 RPS 计算:
如果还要求单节点故障后继续承载,并且每个节点只能放下 4 个这样的 Pod,则至少需要:
但这仍然只是粗略上界。实际还要检查副本是否能跨区分布、是否有足够内存、是否存在滚动发布的 surge,以及 HPA 从 9 扩到目标副本数的时间。
十二、常见失败表现与诊断路径
1. 节点 CPU 利用率不高,但 Pod 一直 Pending
优先检查:
kubectl describe pod <pod-name>
kubectl get nodes
kubectl describe node <node-name>
常见原因:
- 单个 Pod 的 CPU 或内存 request 大于任意节点剩余量;
- 节点存在 taint;
- Pod 有 node affinity;
- Pod Anti-Affinity 或 topology spread 无法满足;
- 可用区或架构不匹配;
- 节点最大 Pod 数已达上限;
- 需要 GPU 等扩展资源;
- PVC 无法在目标拓扑挂载。
此时查看集群总 CPU 利用率通常没有帮助,因为调度是按节点和约束逐个判断的。
2. Request 利用率很低,但节点经常 MemoryPressure
可能是 memory request 过大或监控分母不一致,也可能是 memory limit 过高导致运行时过度超卖。
排查:
kubectl top nodes
kubectl top pods -A --sort-by=memory
kubectl describe node <node-name>
kubectl get events -A --sort-by=.lastTimestamp
同时核对:
- Pod memory request 总和;
- 实际 cgroup 内存;
- 文件缓存;
- kubelet 和系统预留;
evictionHard;- 是否有突发批任务;
- 是否发生 OOMKilled。
3. CPU 利用率不高,但延迟上升
可能原因包括:
- CPU limit 触发 Throttle;
- 单线程程序无法利用多个 CPU;
- 锁竞争;
- GC;
- 下游数据库或网络成为瓶颈;
- 请求排队在连接池、线程池或队列中;
- 节点 CPU steal 或虚拟化争用;
- 某个关键 Pod 被压缩到少数节点。
CPU 使用量低并不能证明服务有容量。必须同时观察 CPU Throttle、运行队列、应用线程、依赖延迟和业务 P99。
4. HPA 扩容了,但延迟继续恶化
可能是:
- 新 Pod 处于 Pending;
- 节点没有可用 request 容量;
- Cluster Autoscaler 尚未完成节点创建;
- 镜像拉取慢;
- Readiness 探针迟迟不通过;
- 新副本没有真正接收流量;
- 瓶颈在数据库、限流器或外部 API;
- HPA 使用了不合适的指标或 request。
检查:
kubectl get hpa
kubectl describe hpa <hpa-name>
kubectl get pods -l app=<app> -o wide
kubectl get events --sort-by=.lastTimestamp
容量规划不能只验证“副本数增加”,还要验证“新副本从创建到真正承载流量的完整时间”。
十三、把容量规划落成一个可审计的模型
一个可用于生产评审的容量表,至少应包含以下字段:
节点类型
节点数量
Capacity
Allocatable
系统与 DaemonSet 开销
业务 Request
业务实际 P50/P95/P99
业务峰值
滚动发布额外副本
HPA 最大副本数
单节点故障后的容量
内存压力阈值
Pending Pod
Pod 数量/IP 余量
可用区和拓扑约束
可以按以下步骤计算:
第一步:计算调度容量
每种节点类型的 Allocatable
- DaemonSet requests
- 系统预留影响
= 普通工作负载可用空间
不能重复扣除已经体现在 Allocatable 中的系统预留。应先确认节点状态中的 Allocatable 已包含哪些配置。
第二步:计算有效业务请求
对每个 Pod:
普通容器 request 之和
与 Init Container request 最大值取较大者
再加 Pod Overhead
然后按命名空间、工作负载、节点和可用区聚合。
第三步:加入运行时峰值约束
分别检查:
峰值 CPU 使用
峰值内存使用
CPU limit 和 Throttle
内存 limit 和 OOM
磁盘、网络、PID
CPU 可以有一定程度的超卖,但内存超卖的风险通常更高。任何超卖比例都必须通过压测和历史故障数据验证,而不是从 CPU 经验直接复制到内存。
第四步:加入故障和变更场景
至少计算:
- 正常高峰;
- 单节点故障;
- 单可用区故障;
- HPA 扩容;
- Deployment 滚动发布;
- 镜像缓存为空;
- 关键 DaemonSet 重启;
- 节点维护和主动驱逐。
第五步:检查碎片而不是只看总和
对大 request Pod、GPU Pod、StatefulSet 和有拓扑约束的工作负载,执行实际调度验证。总量公式只能证明“理论上可能足够”,不能证明“存在一个可行放置方案”。
十四、规范、实现和经验必须分开
以下结论属于 Kubernetes 资源模型的基本规范语义:
- 调度器主要依据 Pod 的
requests和节点Allocatable做资源可行性判断; - 容器的 CPU 和内存 limit 由运行时执行,但具体 cgroup 行为依赖内核和容器运行时;
- HPA 的资源利用率依赖相应资源 request;
- QoS 类别由资源声明模式决定,并影响压力场景下的处理。
以下内容属于常见实现或部署相关行为:
kubectl top依赖 Metrics Server;- kubelet 的预留和驱逐阈值受发行版和节点配置影响;
- CPU Throttle 的指标名称和采集方式受 cAdvisor、kubelet、Prometheus 配置影响;
- 节点自动扩容的启动时间和配额受云厂商影响;
- CNI 的 Pod IP 容量可能先于 CPU 或内存成为瓶颈。
以下内容属于经验建议,不能视为 Kubernetes 保证:
- CPU request 使用 P95 除以目标利用率;
- 内存 request 在峰值上增加固定百分比;
- 预留 15% 或 20% 故障余量;
- 让 CPU request 等于某个固定分位数;
- 通过设置更高 limit 消除所有性能问题。
这些数字只有在明确负载模型、SLO、节点规格和故障目标后才有意义。
结语
Kubernetes 容量规划的核心不是寻找一个“集群利用率目标”,而是建立从业务负载到调度和故障恢复的完整约束链:
Request 决定 Pod 能不能被放下;利用率说明当前使用情况,但必须明确分母;碎片解释了为什么总剩余资源不等于可调度资源;故障余量决定节点丢失或滚动发布时是否还能恢复;压测则用真实负载验证这些计算是否符合业务 SLO。
只有把这几个层次放在同一个模型中,容量规划结果才不仅能解释“现在为什么 Pending”,也能回答“高峰、扩容、发布和故障同时发生时,系统是否仍然可用”。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes 分布式追踪:OpenTelemetry、Context、采样和基础设施关联
- 下一篇:Kubernetes 成本治理:资源归属、闲置、Spot、存储网络和优化边界
- 延伸:Kubernetes Resource 与 QoS:Request、Limit、CPU Throttle、OOM 和 Eviction
- 延伸:Kubernetes HPA 与 VPA:指标、算法、稳定窗口、冲突和容量
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论