Kubernetes v1.37 将 Memory QoS 提升到 Beta,并在 Linux cgroup v2 节点上默认开启功能门控。这个变化听起来像是“升级后马上改变内存行为”,但实际设计更谨慎:默认的 memoryThrottlingFactor 为 null,memoryReservationPolicy 为 None,因此 kubelet 不会仅因为升级就写入 memory.high、memory.min 或 memory.low。
对集群管理员来说,v1.37 的重点不是立刻启用所有 Memory QoS 能力,而是获得一个稳定的配置入口,并根据工作负载选择是否开启内存限流或分层内存保护。
v1.37 到底改变了什么
Memory QoS 最初在 Kubernetes v1.22 以 Alpha 形式引入,v1.36 又扩展了分层内存预留能力。到了 v1.37,它进入 Beta,并且 MemoryQoS feature gate 默认开启。
但“默认开启 feature gate”不等于“默认对容器限流”。v1.37 使用了更安全的默认配置:
memoryThrottlingFactor默认是null,不会自动设置memory.high。memoryReservationPolicy默认是None,不会自动设置memory.min或memory.low。- 如果升级前 kubelet 配置文件中已经显式设置了
memoryThrottlingFactor,该配置会继续生效。 - 如果旧配置没有这个字段,升级后使用新的
null默认值,已有工作负载不会因为升级突然开始受到memory.high限制。
这个默认值调整很重要。早期 Alpha 版本中,memoryThrottlingFactor 默认是 0.9。如果 v1.37 仍然沿用这个默认值,那么 feature gate 自动开启可能会让原本没有限流的容器突然被内核节流,造成升级后的性能回归。
两类能力:限流与分层保护
通过 memory.high 控制内存增长
设置 memoryThrottlingFactor 后,kubelet 会根据容器的内存限制计算 memory.high,主要作用于 Burstable 和 BestEffort 容器。它不是一个简单的“超过多少就杀掉”的硬上限,而是给 Linux 内核提供压力信号,让内核在接近内存限制时对容器进行更积极的回收和节流。
例如,下面的配置显式启用 0.9 的内存限流因子:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
memoryThrottlingFactor: 0.9
memoryThrottlingFactor 必须是 0 到 1 之间的值。实际采用多大的因子,应结合应用的工作集、延迟目标和节点内存超卖程度进行压测,而不是直接套用某个固定数字。
通过 memory.min 和 memory.low 做分层预留
将 memoryReservationPolicy 设置为 TieredReservation 后,kubelet 会按照 Pod QoS 类别写入不同的 cgroup v2 内存保护值:
- Guaranteed Pod 获得
memory.min,表示更强的最低内存保护。 - Burstable Pod 获得
memory.low,表示在内存回收时提供较弱的保护。 - BestEffort 工作负载不获得同等级别的内存预留保护。
只开启分层预留的 kubelet 配置如下:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
memoryReservationPolicy: TieredReservation
也可以同时开启两种能力:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
memoryThrottlingFactor: 0.9
memoryReservationPolicy: TieredReservation
需要注意,Memory QoS 依赖 Linux cgroup v2。配置文件写入后,还需要确认节点实际使用的是 cgroup v2,并通过节点上的 cgroup 文件或监控系统验证 kubelet 是否写入了预期值。
一个可执行的启用与检查流程
下面示例假设你已经准备好 kubelet 配置文件,并且希望在一台 Linux 节点上启用限流和分层预留。实际生产环境中,应通过节点配置管理系统或发行版提供的 kubelet 配置机制发布,而不是直接在运行中的节点上随意覆盖文件。
先检查节点使用的 cgroup 版本:
stat -fc %T /sys/fs/cgroup
# cgroup2fs 表示使用 cgroup v2
将配置保存为 memory-qos.yaml:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
memoryThrottlingFactor: 0.9
memoryReservationPolicy: TieredReservation
然后把该配置合并到目标节点的 kubelet 配置中,并重启 kubelet。不同发行版的服务名称和配置参数可能不同,下面是常见 systemd 环境中的检查命令:
sudo systemctl restart kubelet
sudo systemctl status kubelet --no-pager
sudo journalctl -u kubelet -n 100 --no-pager
确认节点上的 cgroup 层级:
sudo find /sys/fs/cgroup -maxdepth 3 -type f \\
\( -name memory.high -o -name memory.low -o -name memory.min \) \\
-print
对于具体容器,cgroup 路径会因容器运行时、Pod UID 和 QoS 类别而不同。排查时应以节点上实际的 cgroup 路径为准,不要假设所有发行版都使用完全相同的目录结构。
如果只想验证 Kubernetes 层面的节点和 Pod 状态,可以先执行:
kubectl get nodes -o wide
kubectl get pods -A -o wide
kubectl describe node <node-name>
将 <node-name> 替换为目标节点名称。Kubernetes API 不会直接展示所有底层 memory.high 或 memory.low 文件的当前值,因此最终验证通常需要结合节点文件系统、kubelet 日志和容器运行时信息。
升级现有集群时要留意的兼容行为
如果 kubelet 配置中已经明确写了:
memoryThrottlingFactor: 0.9
那么升级到 v1.37 后,限流行为会继续存在。相反,如果旧配置完全没有 memoryThrottlingFactor,升级后默认值是 null,kubelet 不会继续自动设置 memory.high。
这意味着升级前应先盘点真实配置,而不是只查看 feature gate:
sudo grep -nE 'MemoryQoS|memoryThrottlingFactor|memoryReservationPolicy' \
/var/lib/kubelet/config.yaml /etc/kubernetes/kubelet-config.yaml 2>/dev/null
命令中的配置路径需要按集群发行版调整。重点是确认三件事:
- 是否显式设置过
memoryThrottlingFactor。 - 是否已经使用
TieredReservation。 - 节点是否运行 Linux cgroup v2。
如果确实需要关闭 Memory QoS,可以显式关闭 feature gate:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
featureGates:
MemoryQoS: false
关闭时要同步检查其他配置。kubelet 不接受与关闭状态冲突的配置,例如把 memoryThrottlingFactor 设置成除旧默认值 0.9 之外的值,或者仍然设置 memoryReservationPolicy: TieredReservation。因此,停用时应移除这些字段,或调整为兼容值,然后再重启 kubelet。
在 cgroup v2 节点上,feature gate 关闭,或者 memoryReservationPolicy 不是 TieredReservation 时,kubelet 会在启动和相关协调路径中清理过期保护值,例如将根 kubepods cgroup 的 memory.min、memory.low 清零,并清理 Burstable QoS cgroup 的 memory.low。容器的旧 memory.high 值也会在重启或 resize 等协调路径中重置为最大值。
最大限制:预留策略是节点级别的
当前 memoryReservationPolicy 不能按 Pod 单独选择。只要节点启用 TieredReservation,节点上的所有 Pod 都会按照对应 QoS 类别获得保护。这会带来一个现实的调度和运维取舍:
- 需要硬内存保护的 Guaranteed 工作负载可以获得更强的最低保障。
- Burstable 工作负载会获得较弱的回收保护。
- 但同一节点上无法让某个 Pod 选择“完全不参与预留”,另一个 Pod 选择“必须获得预留”。
此外,硬预留保护覆盖容器 cgroup 计费的内存,包括 page cache。一个大量读取文件的 Pod 可能因此保留一部分本来可以被回收、用于帮助其他工作负载的缓存内存。
所以,TieredReservation 并不是所有节点的通用开关。对于混合型节点,建议先识别以下工作负载:
- 对尾延迟敏感、并且明确需要内存保护的服务。
- 会产生大量 page cache 的批处理或文件扫描任务。
- 内存限制设置不准确、容易频繁触发回收的容器。
- 依赖 BestEffort 或 Burstable 竞争节点剩余内存的低优先级任务。
给集群管理员的落地清单
Memory QoS 进入 Beta 后,可以按下面的顺序推进:
- 确认目标 Linux 节点运行 cgroup v2。
- 记录升级前 kubelet 的 Memory QoS 配置和应用内存指标。
- 不要因为 feature gate 默认开启,就假设
memory.high已经启用。 - 先在少量节点上压测
memoryThrottlingFactor,观察延迟、回收、OOM 和节点压力。 - 单独评估
TieredReservation对 page cache 和混合工作负载的影响。 - 使用节点级配置发布策略,确保所有 kubelet 配置一致。
- 为回滚准备好删除 Memory QoS 字段、关闭 feature gate 和重启 kubelet 的流程。
Beta 的意义在于接口和行为已经足够稳定,可以让运营团队进行受控试用;它不代表应当在所有节点上无条件启用全部保护策略。v1.37 的默认值已经尽量避免升级带来隐式限流,接下来更重要的是用实际工作负载验证:哪些服务值得保护,哪些服务更适合保持可回收,以及节点级策略是否真的符合你的资源分层方式。