集群容量规划:节点选型、装箱率、资源预留与超卖
capacity-planning 要回答的问题是:给定业务规模,该买什么样的机器、每台跑多少个 Pod、留多少余量,才能在不出事的前提下把资源用满。
容量规划里最容易出错的地方不是"买少了",而是把可分配资源算多了:
max-pods 说了算,而是 IP 供给说了算。Allocatable = Capacity − kubeReserved − systemReserved − 硬驱逐阈值(evictionHard)
三块预留的含义:
| 预留 | 留给谁 | 典型值 |
|---|---|---|
| kubeReserved | kubelet、容器运行时、CNI 等 Kubernetes 自身组件 | cpu 100m、memory 100Mi、ephemeral-storage 1Gi、pid 1000 |
| systemReserved | 内核、sshd、监控 agent、登录会话等系统进程 | 同上量级,内存要额外给内核留一份 |
| evictionHard | 触发驱逐的硬阈值,等价于从 Allocatable 里扣掉 | memory.available 100Mi、nodefs.available 10%、nodefs.inodesFree 5%、imagefs.available 15% |
注意 kubeReserved 的官方定位:它通常与节点上的 Pod 密度成正比 —— 跑 30 个 Pod 和跑 110 个 Pod 的节点,容器运行时和 kubelet 的开销完全不同,预留值不该照抄。
min(110, 10 × 核数):8 核节点约 80 个 Pod,16 核以上才考虑接近 110。超过这个密度,kubelet 的状态上报、cgroup 操作、日志轮转都会变慢,节点更容易 NotReady。# 单个节点的可分配量(kubeadm 默认没有设预留,这里演示的是手工配置后的效果)
kubectl describe node <node> | grep -A 8 'Allocatable'
kubectl describe node <node> | grep -A 8 'Capacity'
# 节点上 requests 与 limits 的汇总,用来判断装箱率
kubectl describe node <node> | grep -A 6 'Allocated resources'
| 资源 | 是否可超卖 | 说明 |
|---|---|---|
| CPU | 可以 | 压缩型资源,超卖后表现为 throttling,不会杀死进程 |
| 内存 | 不可以 | 不可压缩,超卖后触发 OOMKilled 或节点驱逐 |
| ephemeral-storage | 不可以 | 写满会触发磁盘压力驱逐 |
| GPU / 大页 | 不可以 | 独占资源,必须精确匹配 |
实务上的做法:
统一用 requests/limits 与 QoS 的完整规则见 qos 与 limitrange 页面。
一台 8 vCPU / 32 GiB 的工作节点,预留配置如下:
kubeReserved: cpu: 1000m, memory: 2Gi, ephemeral-storage: 2Gi
systemReserved: cpu: 1000m, memory: 2Gi, ephemeral-storage: 2Gi
evictionHard: memory.available: 100Mi, nodefs.available: 10%, imagefs.available: 15%
Allocatable(vCPU) = 8 − 1 − 1 = 6
Allocatable(内存) = 32 − 2 − 2 − 0.1 ≈ 27.9 GiB
再按 Pod 密度分摊:该节点按 80 个 Pod 规划,则平均每个 Pod 能拿到的内存 requests 约为 27.9GiB / 80 ≈ 357MiB。如果业务 Pod 的平均内存 requests 是 512MiB,这台节点实际只能跑约 55 个 Pod —— 这就是"按 Pod 数规划"和"按资源规划"的差别。
# 推荐写在 KubeletConfiguration 里(命令行 flag 已标记废弃)
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
kubeReserved:
cpu: 1000m
memory: 2Gi
ephemeral-storage: 2Gi
systemReserved:
cpu: 1000m
memory: 2Gi
ephemeral-storage: 2Gi
evictionHard:
memory.available: "100Mi"
nodefs.available: "10%"
nodefs.inodesFree: "5%"
imagefs.available: "15%"
enforceNodeAllocatable:
- pods
# 把关键系统进程的 CPU 从调度里彻底摘出去(电信/NFV 场景常用,自 1.17 稳定)
reservedSystemCPUs: "0-1"
# 验证节点是否真的按预期预留
kubectl get node <node> -o jsonpath='{.status.allocatable}' | jq .
kubectl get --raw "/api/v1/nodes/<node>/proxy/configz" | jq '.kubeReserved, .systemReserved, .evictionHard'
# 1. 集群当前规模与增长趋势
kubectl get nodes --no-headers | wc -l
kubectl get pods -A --no-headers | wc -l
# 2. 每个节点的装箱率(requests 占 Allocatable 的比例)
kubectl describe node <node> | grep -A 6 'Allocated resources'
# 3. 是否有节点已经逼近驱逐阈值
kubectl get nodes -o custom-columns=\
NAME:.metadata.name,CPU:.status.allocatable.cpu,MEM:.status.allocatable.memory
# 4. 实际用量 vs requests(判断 requests 是否虚高)
kubectl top node
kubectl top pod -A --sort-by=memory | head -20
metrics-server 或 Prometheus 采集,按周/月看增长斜率,而不是等出事才看。假设业务侧统计出:内存 requests 合计约 1.2 TiB、CPU requests 合计约 300 核、Pod 总数约 4000 个。选用机型为 8 vCPU / 32 GiB(单节点 Allocatable 约 6 vCPU / 27.9 GiB),按每节点 80 个 Pod 规划:
按内存:1.2 TiB / 27.9 GiB ≈ 45 台
按 CPU:300 / 6 = 50 台
按 Pod:4000 / 80 = 50 台
取三者最大值 50 台,再叠加故障冗余(按可用区 N+1)→ 约 60 台
# 落到节点上校验:requests 汇总不得超过 Allocatable
kubectl describe node <node> | grep -A 6 'Allocated resources'
# 如果内存维度先撞线(常见),说明该给内存 requests 做瘦身或换更大内存的机型
经验规律:内存通常是主要约束(不可压缩,超了就是 OOM),CPU 因为有超卖空间相对宽松。两个维度都要算,取上限;三个维度里谁先撞线,就先优化谁。
max-pods。规划前先算清 IPAM 能提供多少个地址。imagefs.available 与 nodefs.available 的驱逐阈值实际是在保护它;镜像大的业务要单独评估磁盘容量。Capacity − kubeReserved − systemReserved 估算会高估可分配内存,大规模集群里这个误差足以造成批量驱逐。evictionHard 时,只写一项会清空其余项。KubeletConfiguration 的 map 字段是整体替换的:只写 memory.available 会让 nodefs.available、imagefs.available 等默认值变成 0,等于取消了对它们的驱逐保护。要么把全部阈值写全,要么设置 mergeDefaultEvictionSettings: true 表示与默认值合并。enforceNodeAllocatable 默认只有 pods。也就是说 kubeReserved/systemReserved 默认只是"账面扣除",并不会真的限制系统进程的用量。要让预留真正生效,需要额外配置 kubeReservedCgroup / systemReservedCgroup 并把 kube-reserved / system-reserved 加进 enforceNodeAllocatable。kubeReservedCgroup,如果指定了不存在的 cgroup,会直接启动失败;用 systemd 作为 cgroup driver 时,名字要带 .slice 后缀。cgroupfs,而 containerd 等运行时通常用 systemd;两者不一致会导致 kubelet 管理不到容器的 cgroup,表现为 Pod 起不来或资源限制失效。kubeadm 生成的配置会显式设为 systemd。max-pods 不是节点能跑多少 Pod 的真实上限。云环境里弹性网卡数量 × 每张网卡的 IP 数、以及集群的 Pod CIDR 大小,往往更早成为瓶颈;规划前先确认 IPAM 的供给能力,再决定 max-pods。pid 也是一种可预留资源。跑大量进程的节点(如 sidecar 多、构建类负载)要预留 pid 额度,否则会撞上内核的 pid 上限并波及整机。kubectl — Kubernetes集群管理工具node — 节点资源与状态qos — Pod 服务质量等级与驱逐顺序limitrange — 命名空间级默认 requests/limitsvpa — 垂直自动扩缩容与 requests 推荐scalability — 大规模集群的规模阈值与 SLOkubelet-config — KubeletConfiguration 的配置方式