Kubernetes v1.36:借助 Memory QoS 实现分层内存保护

我谨代表 SIG Node 宣布 Kubernetes v1.36 中 Memory QoS 特性(Alpha)的更新。 Memory QoS 使用 cgroup v2 的内存控制器,为内核提供更好的指引来处理容器内存。 它最早在 v1.22 中引入,并在 v1.27 中更新。 在 Kubernetes v1.36 中,我们引入了以下内容:可选启用的内存预留、基于 QoS 类的分层保护、 可观测性指标,以及针对 memory.high 的内核版本告警。

v1.36 的新变化

使用 memoryReservationPolicy 选择性启用内存预留

v1.36 将节流与预留分离开来。启用该特性门控后,会开启 memory.high 节流 (kubelet 基于 memoryThrottlingFactor 设置 memory.high,默认值为 0.9), 但内存预留现在由一个独立的 kubelet 配置字段控制:

  • None(默认):不写入 memory.minmemory.low。通过 memory.high 进行的节流仍然有效。
  • TieredReservation:kubelet 基于 Pod 的 QoS 类 写入分层内存保护:

Guaranteed Pod 通过 memory.min 获得硬保护。例如,一个请求 512 MiB 内存的 Guaranteed Pod 会得到:

$ cat /sys/fs/cgroup/kubepods.slice/kubepods-pod6a4f2e3b_1c9d_4a5e_8f7b_2d3e4f5a6b7c.slice/memory.min
536870912

内核在任何情况下都不会回收这部分内存。如果无法兑现这一保证, 它会对其他进程触发 OOM killer 以释放内存页。

Burstable Pod 通过 memory.low 获得软保护。对于同样请求 512 MiB 内存的 Burstable Pod:

$ cat /sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8b3c7d2e_4f5a_6b7c_9d1e_3f4a5b6c7d8e.slice/memory.low
536870912

在正常压力下,内核会避免回收这部分内存;但如果另一种选择是系统范围的 OOM, 内核仍可能回收其中一部分。

BestEffort Pod 既不会获得 memory.min,也不会获得 memory.low。 它们的内存仍然是完全可回收的。

与 v1.27 行为的对比

在更早的版本中,启用 MemoryQoS 特性门控后,会立即为每个带有内存请求的容器设置 memory.minmemory.min 是一种硬预留,无论内存压力如何,内核都不会回收它。

假设一个节点有 8 GiB RAM,而 Burstable Pod 的请求总量达到 7 GiB。 在更早版本中,这 7 GiB 会作为 memory.min 被锁定, 从而给内核、系统守护进程或 BestEffort 工作负载留下极少余量, 并增加 OOM kill 的风险。

在 v1.36 的分层预留机制下,这些 Burstable 请求会映射到 memory.low,而不是 memory.min。 在正常压力下,内核仍会保护这部分内存;但在极端压力下, 它可以回收其中一部分以避免系统范围的 OOM。 只有 Guaranteed Pod 才会使用 memory.min,这使得硬预留保持在更低水平。

借助 v1.36 中的 memoryReservationPolicy, 你可以先启用节流,观察工作负载行为, 然后在节点拥有足够余量时再选择启用预留。

可观测性指标

kubelet 的 /metrics 端点会暴露两个 Alpha 级稳定性指标:

指标说明
kubelet_memory_qos_node_memory_min_bytesGuaranteed Pod 的 memory.min 总量
kubelet_memory_qos_node_memory_low_bytesBurstable Pod 的 memory.low 总量

这些指标对于容量规划非常有用。 如果 kubelet_memory_qos_node_memory_min_bytes 正在逐渐逼近节点的物理内存, 你就知道硬预留已经变得紧张了。

$ curl -sk https://localhost:10250/metrics | grep memory_qos
# HELP kubelet_memory_qos_node_memory_min_bytes [ALPHA] Total memory.min in bytes for Guaranteed pods
kubelet_memory_qos_node_memory_min_bytes 5.36870912e+08
# HELP kubelet_memory_qos_node_memory_low_bytes [ALPHA] Total memory.low in bytes for Burstable pods
kubelet_memory_qos_node_memory_low_bytes 2.147483648e+09

内核版本检查

在版本低于 5.9 的内核上,memory.high 节流可能触发 内核活锁 问题。这个缺陷已在内核 5.9 中修复。 在 v1.36 中,当启用该特性门控时,kubelet 会在启动时检查内核版本, 如果内核版本低于 5.9 就记录一条告警日志。 该特性仍然可以继续工作,这只是信息提示,并不是硬阻断。

Kubernetes 如何将 Memory QoS 映射到 cgroup v2

Memory QoS 使用四个 cgroup v2 内存控制器接口:

  • memory.max:硬内存限制,与之前版本一致
  • memory.min:硬内存保护,在 TieredReservation 下仅对 Guaranteed Pod 设置
  • memory.low:软内存保护,在 TieredReservation 下对 Burstable Pod 设置
  • memory.high:内存节流阈值,与之前版本一致

下表展示了在配置 memoryReservationPolicy: TieredReservation 时, Kubernetes 容器资源如何映射到 cgroup v2 接口。 对于默认值 memoryReservationPolicy: None,不会设置 memory.minmemory.low

QoS 类memory.minmemory.lowmemory.highmemory.max
Guaranteed设置为 requests.memory
(硬保护)
不设置不设置
(requests == limits,因此节流没有意义)
设置为 limits.memory
Burstable不设置设置为 requests.memory
(软保护)
基于节流因子公式计算设置为 limits.memory
(如果已指定)
BestEffort不设置不设置基于节点可分配内存计算不设置

cgroup 层级

cgroup v2 要求父 cgroup 的内存保护值至少要与其所有子 cgroup 之和一样大。 kubelet 通过以下方式维持这一点: 将 kubepods 根 cgroup 上的 memory.min 设置为所有 Guaranteed 和 Burstable Pod 内存请求之和, 并将 Burstable QoS cgroup 上的 memory.low 设置为所有 Burstable Pod 内存请求之和。 这样,内核就能够正确执行容器级和 Pod 级的保护值。

kubelet 直接使用 runc 的 libcontainer 库管理 Pod 级别和 QoS 类级别的 cgroup, 而容器级别的 cgroup 则由容器运行时(containerd 或 CRI-O)管理。

如何使用?

前提条件

  1. Kubernetes v1.36 或更高版本
  2. 使用 cgroup v2 的 Linux。推荐使用 5.9 或更高版本的内核; 更早版本的内核也能工作,但可能会遇到活锁问题。 你可以运行 mount | grep cgroup2 来验证 cgroup v2 是否已启用。
  3. 支持 cgroup v2 的容器运行时(containerd 1.6+、CRI-O 1.22+)

配置

要启用带分层保护的 Memory QoS:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
featureGates:
  MemoryQoS: true
memoryReservationPolicy: TieredReservation  # 可选值:None(默认)、TieredReservation
memoryThrottlingFactor: 0.9  # 可选,默认值为 0.9

如果你只想启用 memory.high 节流,而不启用内存保护, 可以省略 memoryReservationPolicy,或者将其设置为 None

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
featureGates:
  MemoryQoS: true
memoryReservationPolicy: None  # 默认值

如何进一步了解?

参与其中

此特性由 SIG Node 推动。 如果你有兴趣参与贡献或提供反馈, 可以通过 Slack#sig-node)、 邮件列表 或定期举行的 SIG Node 会议 找到我们。 请将缺陷报告提交到 kubernetes/kubernetes, 并将增强提案提交到 kubernetes/enhancements