4. 节点资源模型与可分配资源计算

在 Kubernetes 中,节点上真正能被 Pod 使用的 CPU 和内存,并不是 kubectl describe node 里看到的 Capacity。Capacity 表示节点整机资源总量,而调度器实际参与计算的是 Allocatable。如果忽略这一层差异,就会出现“节点看起来还有资源,Pod 却调度不上去”或“节点内存充足,却频繁驱逐 Pod”的现象。理解 Allocatable 的推导过程,是生产资源池容量规划的基础。

Allocatable 的推导公式

kubelet 启动时会根据启动参数和节点配置,计算并向 API Server 上报三类资源值:Capacity、Allocatable 和已分配资源。其中 Allocatable 的推导关系为:

Allocatable = Capacity
            - kube-reserved
            - system-reserved
            - eviction-threshold(仅内存)

CPU 与内存的差异在于:CPU 是可压缩资源,通常不设置驱逐阈值;内存是不可压缩资源,必须为 kubelet 的驱逐机制预留安全边界。因此内存的 Allocatable 会额外减去 --eviction-hard 中声明的阈值。

三类预留的职责边界

kube-reserved 用于为 Kubernetes 自身组件预留资源,典型对象包括 kubelet、容器运行时(containerd 或 Docker)、kube-proxy 等。这些进程不属于任何业务 Pod,但会持续消耗 CPU 和内存。若不预留,它们会与业务 Pod 争抢资源,在节点压力大时导致 kubelet 自身被 OOM 或响应迟缓。

system-reserved 用于为操作系统和系统守护进程预留资源,例如 sshd、systemd、journald、udev 以及内核线程。这部分资源同样不能被调度器分配给 Pod,否则系统层在内存回收时可能触发不可预期的行为。

eviction-threshold 是 kubelet 的驱逐触发线。当节点内存可用量低于该阈值时,kubelet 会按 QoS 等级驱逐 Pod。它本质上是一块“不可被 Pod 占用”的内存缓冲,用于给 kubelet 留出反应时间,避免节点直接进入 OOM Killer 全局杀进程的状态。

节点资源模型:从 Capacity 到 Allocatable 的扣减关系 Capacity(整机资源) CPU: 16 core | Memory: 64 GiB kube-reserved kubelet / containerd kube-proxy system-reserved sshd / systemd journald / 内核线程 eviction-threshold memory.available 仅内存生效 Allocatable(调度器可见资源) CPU: 15.2 core | Memory: 56.5 GiB(示例值) 示例计算(内存 64 GiB 节点): kube-reserved = 2 GiB | system-reserved = 3 GiB | eviction-hard = 2.5 GiB Allocatable = 64 − 2 − 3 − 2.5 = 56.5 GiB,调度器只能在此范围内分配 Pod 内存请求

生产环境中的预留配置实践

预留值没有统一标准,需要结合节点规格、运行时类型和业务特征确定。以下是一组常见参考区间,属于经验性示例,实际值应通过监控节点上 kubelet、containerd、systemd 等进程的常驻内存峰值来校准。

节点内存规格 kube-reserved 建议 system-reserved 建议 eviction-hard 建议
8 GiB 1 GiB 1 GiB 1 GiB
16 GiB 1.5 GiB 1.5 GiB 1.5 GiB
64 GiB 2 GiB 3 GiB 2.5 GiB
128 GiB 3 GiB 4 GiB 3 GiB

配置方式是在 kubelet 启动参数中显式声明。以 systemd 管理的 kubelet 为例,可在 /var/lib/kubelet/config.yaml 或 kubelet 的 EnvironmentFile 中设置:

# /var/lib/kubelet/config.yaml 片段
kubeReserved:
  cpu: "500m"
  memory: "2Gi"
  ephemeral-storage: "2Gi"
systemReserved:
  cpu: "500m"
  memory: "3Gi"
  ephemeral-storage: "2Gi"
evictionHard:
  memory.available: "2.5Gi"
  nodefs.available: "10%"
  imagefs.available: "15%"

修改后需要重启 kubelet 使配置生效,然后通过 kubectl describe node <node-name> 查看 Allocatable 是否按预期下降。重点关注输出中的 Allocatable 与 Allocated resources 两段,前者是调度上限,后者是当前已分配量。

对生产资源池的实际影响

预留配置直接改变资源池的有效容量。假设一个集群有 20 台 64 GiB 节点,若每台预留 7.5 GiB,则整个资源池损失 150 GiB 可调度内存。如果业务侧按 Capacity 做容量规划,就会高估可用资源,导致调度失败率上升。因此容量规划必须以 Allocatable 为基准,并额外考虑 DaemonSet、系统组件等非业务 Pod 的占用。

另一个常见问题是 eviction-threshold 设置过小。当 memory.available 阈值只有 100 MiB 时,kubelet 可能在内存已经极度紧张时才触发驱逐,此时驱逐操作本身也可能因内存不足而失败,最终由内核 OOM Killer 介入,杀掉包括 kubelet 在内的任意进程。生产环境通常建议将 memory.available 设置在 1 GiB 到 3 GiB 之间,具体取决于节点规格和内存分配速度。

注意:eviction-threshold 不是“预留后就不再用”的内存。它是一道触发线,当可用内存跌破该线时 kubelet 开始驱逐 Pod。被驱逐的 Pod 释放内存后,可用内存回升,驱逐停止。因此实际运行中,节点可用内存会在阈值附近波动,而不是稳定保持一个固定值。

如何验证与持续校准

配置完成后,建议从三个层面验证。第一,检查 Allocatable 数值是否符合预期,可用以下命令快速对比:

kubectl get node <node-name> -o jsonpath='{.status.capacity.memory}{"\n"}{.status.allocatable.memory}{"\n"}'

第二,观察节点上 kubelet 和 containerd 的实际内存占用,确认 kube-reserved 是否足够。可通过 systemd-cgtop 或 crictl stats 查看。第三,在压测环境中主动制造内存压力,观察 kubelet 是否在预期阈值附近触发驱逐,以及被驱逐 Pod 的 QoS 等级是否符合预期顺序(BestEffort 优先,其次是 Burstable,最后是 Guaranteed)。

随着节点上运行的组件版本升级、容器运行时更换或业务 Pod 密度变化,预留值需要定期复核。建议将 Allocatable、已分配资源、节点实际可用内存三项指标纳入监控,当 Allocatable 与 Capacity 的差值长期偏离设计值时,及时调整预留参数,避免资源池容量与规划脱节。