ITPub博客

首页 > 云计算 > 容器服务 > Kubernetes(容器编排平台) 转向 cgroup v2:你需要了解的内容

Kubernetes(容器编排平台) 转向 cgroup v2:你需要了解的内容

容器服务 作者:ITPUB社群 时间:1970-01-01 08:33:46 0 删除 编辑

在 Linux 中,cgroups(控制组)是一种用于管理系统资源的内核特性。Kubernetes 使用 cgroups 为容器分配 CPU 和内存等资源,确保应用程序平稳运行且互不干扰。随着 Kubernetes v1.31 的发布,对 v1 cgroup 管理的支持已进入维护模式。自 Kubernetes v1.25 起,对 v2 cgroup 管理的支持已趋于稳定。

与 cgroup v1 相比,cgroup v2 提供了单一的统一层级、更一致的接口,以及更强大的资源隔离和现代资源管理特性基础。

cgroup v1 的弃用

Kubernetes 已弃用 cgroup v1。从 Kubernetes v1.35 开始,failCgroupV1 默认为 true,因此 kubelet 默认不会在 cgroup v1 节点上启动。管理员可以在 kubelet 配置文件中临时设置 failCgroupV1: false,但移除将遵循 Kubernetes 弃用策略。进一步的移除工作记录在 KEP-5573:移除 cgroup v1 支持中。

如果您仍在使用早于 v1.35 的版本,请在升级前将每个 Linux 节点迁移到 cgroup v2,或计划设置临时的 failCgroupV1: false 覆盖。如果您已经使用 v1.35 或更高版本,请确认每个 Linux 节点都运行 cgroup v2(或者您有意保留该覆盖)。在默认配置下,剩余的 cgroup v1 节点会在 kubelet 启动期间失败。

对于 kubeadm 管理的集群,Kubernetes v1.35 还将其变为更早、更严格的检查。由 k8s.io/system-validators 提供的 SystemVerification 预检在检测到 cgroup v1 且 kubelet 为 v1.35 或更高版本时,会在 kubeadm init、kubeadm join 和 kubeadm upgrade 期间返回错误;对于较旧的 kubelet,该检查仍为警告。详情请参阅 kubernetes/system-validators#1.12.1 发布说明。

常见问题解答涵盖三个主要方面:为何迁移、优缺点,以及使用 cgroup v2 时需要牢记的关键要点。

cgroup v1 的局限性与 cgroup v2 的改进

Linux 内核文档对这两个接口均有描述:

  • cgroup v1 文档

  • cgroup v2 文档

让我们列举一些已知问题。

active_file 内存不被视为可用内存

kubelet 将 active_file 内存视为不可回收。因此,对于 I/O 密集型工作负载,较大的页缓存可能导致 kubelet 报告内存压力并驱逐 Pod。这是一个已知的 kubelet 问题(kubernetes/kubernetes#43916);迁移到 cgroup v2 本身并不会改变这一计算方式。文档中记录的解决方法是,在测量出合适的值后,为执行密集 I/O 的容器设置相等的内存请求和限制。

Kubernetes v1.36 中的内存 QoS 更新

内存 QoS 在 Kubernetes v1.22 中作为 Alpha 特性引入,并在 v1.27 中更新。它在 v1.36 中仍为 Alpha,但现在将内存节流与内存预留分离,并增加了分层内存保护:

内存 QoS 仅在启用 cgroup v2 的 Linux 节点上可用。它依赖 cgroup v2 内存控制器:memory.high 提供节流,而 memory.min 和 memory.low 在启用分层预留时提供硬保护和软保护。cgroup v1 无法提供这种保护模型。

  • 启用 MemoryQoS 特性门控会对 Burstable 容器应用 memory.high 节流。阈值由 request、limit 和 memoryThrottlingFactor(默认 0.9)推导得出。

  • memoryReservationPolicy: None 是默认值。它不会写入 memory.min 或 memory.low。

  • memoryReservationPolicy: TieredReservation 将 Guaranteed Pod 的内存请求映射到 memory.min(硬保护),将 Burstable Pod 的请求映射到 memory.low(软保护)。BestEffort Pod 不会获得任何保护。

  • kubelet 暴露 Alpha 指标,用于显示节点上 memory.min 和 memory.low 的总预留量。

  • 建议使用内核 5.9 或更高版本。在较旧的内核上,memory.high 回收可能触发已知的活锁;从 v1.36 开始,当在受影响的内核上启用内存 QoS 时,kubelet 会记录一条警告。

例如,要启用分层保护:

apiVersion
:
kubelet.config.k8s.io/v1beta1
kind
:
KubeletConfiguration
featureGates
:
MemoryQoS
:
true
memoryReservationPolicy
:
TieredReservation
memoryThrottlingFactor
:
0.9

Kubernetes 的总体建议是不要在生产环境中启用 Alpha 特性;但是,如果你判断带有分层预留的内存 QoS 对你的生产平台有用,请确保在启用 TieredReservation 特性门控之前测试配置并考虑硬预留内存。有关 Kubernetes QoS 类到 cgroup v2 控制项的当前映射,请参阅 Pod QoS 文档。

容器感知的 OOM 处理

在 cgroup v2 节点上,kubelet 将 singleProcessOOMKill 默认为 false。因此,它会为每个容器 cgroup 设置 memory.oom.group,以便 OOM 事件会一起杀死该容器中的所有进程,而不是留下一个部分功能正常的多进程容器。仅当你需要与 cgroup v1 兼容的行为(即内核可能一次杀死一个进程)时,才设置 singleProcessOOMKill: true。有关此设置,请参阅 KubeletConfiguration 参考。

此行为的作用范围是容器 cgroup,而不是整个 Pod。此外,cgroup.kill 是一个独立的管理接口:向其写入 1 会向该 cgroup 及其后代中的所有进程发送 SIGKILL;它不会配置 OOM 行为。cgroup v2 内存控制器还提供 memory.events 计数器,监控系统和用户空间 OOM 管理器可以观察这些计数器。

无根支持

在 cgroup v1 中,将控制器委托给权限较低的容器可能是危险的。

与 cgroup v1 不同,cgroup v2 官方支持委托。大多数无根容器(rootless containers)的实现依赖 systemd 将 v2 控制器委托给非 root 用户。

这种委托机制与 Kubernetes Pod 用户命名空间(user namespaces)是分开的,后者将容器用户映射到非特权主机用户。Pod 用户命名空间支持在 Kubernetes v1.36 中已毕业为稳定版;在启用之前,请检查其文件系统、内核、CRI 运行时和 OCI 运行时先决条件。

还有什么?

  • eBPF 相关情况:在 cgroup v1 中,设备访问控制通过接口文件暴露。cgroup v2 设备控制器没有接口文件,而是基于 cgroup BPF 实现。Cilium 为基于套接字的负载均衡挂载 BPF cgroup 程序。其默认 cgroup 根目录为 /run/cilium/cgroupv2 。

  • 压力失速信息(Pressure Stall Information,PSI)报告节点、Pod 和容器级别的 CPU、内存和 I/O 争用情况。在受支持的集群上,kubelet 默认暴露 PSI( KubeletPSI 已稳定并锁定开启)。PSI 需要 cgroup v2、Linux 4.20 或更高版本、 CONFIG_PSI=y ,以及未使用 psi=0 启动的内核。kubelet 通过 Summary API(应用程序编程接口) 和 /metrics/cadvisor 呈现数据。

  • 迁移时,请更新直接读取 cgroup 文件系统的软件。迁移指南建议使用 cAdvisor v0.43.0 或更高版本,并列出了兼容的 Java、Node.js 和 automaxprocs 版本。

较新 OCI 运行时中的 CPU 权重转换

cgroup v1 使用 cpu.shares ,而 cgroup v2 使用 cpu.weight 。较新的 OCI 运行时使用改进的非线性转换,既保留默认优先级,又为较小的 CPU 请求提供更可用的粒度。该变更在 OCI 运行时中实现,而非在 Kubernetes 中:它在 crun v1.23 和 runc v1.3.2 中可用。升级运行时后,预测精确 cpu.weight 值的监控或策略工具可能需要更新。请阅读《从 cgroup v1 CPU Shares 到 v2 CPU Weight 的新转换》以了解公式、示例和兼容性注意事项。

原地资源更新

原地 Pod 垂直扩缩容在 Kubernetes v1.35 中已毕业为稳定版。随后 Kubernetes v1.36 [启用](/blog/2026/04/30/kubernetes-v1-36-inplace-pod-level-resources-beta/)了 Pod 级别资源的原地垂直扩缩容,默认作为 Beta 功能。kubelet 协调 Pod 级别和容器 cgroup 之间的变更,使增加时在容器限制增长之前创建余量,而减少时在收缩 Pod 级别边界之前约束容器。此 v1.36 功能要实现准确的聚合强制执行,需要 cgroup v2。

采用 cgroup 版本 2

要求

在 Kubernetes 中使用 cgroup v2 所需的条件。首先,你需要使用支持 v2 cgroup 管理的 Kubernetes 版本;该支持自 Kubernetes v1.25 起已稳定,所有受支持的 Kubernetes 版本都包含此支持。

  • 你至少需要一个 Linux 节点;cgroup 是仅限 Linux 的概念

  • 你的操作系统安装必须启用 cgroup v2 运行

  • 内核版本必须为 5.8 或更高(使用内存 QoS 时建议 5.9 或更高)

  • 容器运行时必须支持 cgroup v2。例如:containerd v1.4 或更高版本支持 cgroup v2;使用 containerd v2.0 或更高版本以自动发现 cgroup 驱动 CRI-O v1.20 或更高版本

  • kubelet 和容器运行时都必须配置为使用正确的 cgroup 驱动。请参阅配置 kubelet 的 cgroup 驱动以匹配容器运行时 cgroup 驱动。

目前,你可以选择重新启用 cgroup v1;Kubernetes 项目推荐使用 cgroup v2,但在 Kubernetes 1.36(当前版本)中,cgroup v1 选项仍作为回退方案受到支持。该回退方案计划在 Kubernetes v1.38 中移除。如果你运行的是较旧的集群,请规划迁移;如果你正在使用 Linux 节点搭建新集群,应优先选择 cgroup v2。无论哪种情况,请查阅 Kubernetes 运行时文档以及(如果相关)containerd 兼容性矩阵。

cgroup v2 相关的内核更新

Kubernetes 于 2014 年首次发布时,只存在 cgroup v1。cgroup v2 管理最早出现在 2016 年发布的 Linux 内核 4.5 中。

  • 在 Linux 4.5 中,支持 cgroup v2 的 io、memory 和 pids 控制器。

  • Linux 4.15 增加了对 cgroup v2 cpu 控制器的支持。

  • 压力失速信息(PSI)支持始于 Linux 4.20。

  • 由于缺少 cgroup 级别的任务冻结器支持,Kubernetes 项目不建议在低于 5.2 的 Linux 内核上使用 cgroup v2。

  • Kubernetes 文档将 5.8 列为 cgroup v2 的最低内核版本;根 cgroup 的系统级 cpu.stat 文件是在 Linux 5.8 中添加的。

  • Memory QoS 使用的 memory.high 活锁修复存在于 Linux 5.9 及更高版本中。

  • memory.peak 是在 Linux 5.19 中添加的。

cgroup 驱动配置

将 kubelet 的 cgroup 驱动配置为与容器运行时的 cgroup 驱动一致。

如果你使用 kubeadm 管理集群,Kubernetes 建议使用 systemd cgroup 驱动,因为 kubeadm 将 kubelet 作为 systemd 服务进行管理。对于其他管理工具,请查阅你用于管理集群的工具的文档。

如果你可以选择任一选项,我推荐使用 systemd 驱动。

无论你选择哪种工具,kubelet 都会自动尝试检测运行时推荐的 cgroup 驱动。这种自动检测依赖于使用实现了 RuntimeConfig CRI RPC 的运行时(例如:containerd v2.0+ 或 CRI-O v1.28+)。

如果你使用的容器运行时支持 cgroup v2,但不支持自动 cgroup 驱动检测,你可以通过编辑 kubelet 配置文件手动配置覆盖。例如:

apiVersion
:
kubelet.config.k8s.io/v1beta1
kind
:
KubeletConfiguration
cgroupDriver
:
systemd

通过 CRI 自动发现运行时的 cgroup 驱动由 KEP-4033 跟踪,并在 Kubernetes v1.34 中进入稳定阶段。它要求运行时实现 RuntimeConfig CRI RPC(containerd v2.0+ 或 CRI-O v1.28+)。当可用时,kubelet 会使用运行时报告的值,而不是其配置的 cgroupDriver 值。

故障排查工具和命令

关于 cgroups,你应该了解的工具和命令:

  • stat -fc %T /sys/fs/cgroup/ :检查是否启用了 cgroup v2;它会返回 cgroup2fs 。

  • systemctl list-units 'kube*' --type=slice 或 --type=scope :列出 systemd 当前内存中与 Kubernetes 相关的单元。

  • bpftool cgroup list /sys/fs/cgroup/* :列出附加到 cgroup CGROUP 的所有程序。

  • systemd-cgls /sys/fs/cgroup/* :递归显示控制组内容。

  • systemd-cgtop :按资源使用情况显示排名靠前的控制组。

  • tree -L 2 -d /sys/fs/cgroup/kubepods.slice :显示 Pod 相关的 cgroups 目录。

如何检查 Pod 的 CPU 或内存限制是否已成功应用到 cgroup 文件?

从 API 对象向下追踪到节点。确定节点,然后将 spec 中的期望资源与 status 中的实际生效值进行比较(原地调整大小可能导致两者不同步):

kubectl get pod-n\
-o
jsonpath
=
'{.spec.nodeName}{"\n"}'
kubectl get pod-n\
-o
jsonpath
=
'{range .spec.containers[*]}{.name}{" spec\t"}{.resources}{"\n"}{end}'
kubectl get pod-n\
-o
jsonpath
=
'{range .status.containerStatuses[*]}{.name}{" status\t"}{.resources}{"\n"}{end}'

在该节点上,以 root 身份,使用 systemd cgroup 驱动和 cgroup v2(这些片段还需要 jq )。使用 kubelet 标签定位容器;仅凭容器名称在节点上并不唯一。如果容器未运行,请使用 crictl ps -a :

CONTAINER_ID
=
$(
crictl ps
\
--label io.kubernetes.pod.namespace
=\
--label io.kubernetes.pod.name
=\
--name-q
|
head -n1
)
# OCI view (not the CRI protobuf field names)
crictl inspect
"
$CONTAINER_ID
"
|
jq
'.info.runtimeSpec.linux.resources'
# Kernel cgroup path for this container
PID
=
$(
crictl inspect
"
$CONTAINER_ID
"
|
jq -r
'.info.pid'
)
CGROUP
=
"/sys/fs/cgroup
$(
awk -F:
'$1=="0"{print $3}'
/proc/
$PID
/cgroup
)
"
cat
"
$CGROUP
/cpu.weight"
# request.cpu → shares → weight; not a limit
cat
"
$CGROUP
/cpu.max"
# limit.cpu as "quota period"; unlimited is "max"
cat
"
$CGROUP
/memory.max"
# limit.memory; unlimited is "max"
# Pod-level cgroup (parent slice), used by Pod-level resources
cat
"
$(
dirname
"
$CGROUP
"
)
/cpu.max"
"
$(
dirname
"
$CGROUP
"
)
/memory.max"
# Present when MemoryQoS is enabled
cat
"
$CGROUP
/memory.high"
"
$CGROUP
/memory.min"
"
$CGROUP
/memory.low"
SCOPE
=
$(
basename
"
$CGROUP
"
)
systemctl show
"
$SCOPE
"
\
-p CPUWeight -p CPUQuotaPerSecUSec -p CPUQuotaPeriodUSec -p MemoryMax

当运行时使用 systemd 驱动时,不要将 info.runtimeSpec.linux.cgroupsPath 视为文件系统路径;该值是 systemd 单元路径( slice:runtime:id ),而不是 /sys/fs/cgroup 下的目录。

每一层的预期映射:

  • Kubernetes: spec.containers[*].resources (期望值)和 status.containerStatuses[*].resources (原地调整大小后实际生效值)。Pod 级资源位于 spec.resources 和父 pod slice 上。

  • CRI(kubelet → 运行时;即使在 v2 上,这些 protobuf 名称仍保持 cgroup v1 风格): cpu_shares 、 cpu_quota 、 cpu_period 、 memory_limit_in_bytes ,以及用于 Memory QoS 的 unified 。 crictl inspect 不会显示这些名称;它显示的是运行时从中派生出的 OCI spec。

  • OCI spec: linux.resources.cpu.{shares,quota,period} 、 linux.resources.memory.limit 和 linux.resources.unified 。运行时会将这些 shares、quota 和 period 转换为 cgroup v2 文件。升级 crun 或 runc 后,shares 到 weight 的转换公式可能会改变;请参阅 CPU weight conversion 。

  • systemd scope: CPUWeight 、 CPUQuotaPerSecUSec 、 CPUQuotaPeriodUSec 、 MemoryMax 。未限制或未设置的值通常显示为 [not set] 或 infinity。

  • cgroupfs:容器 scope 上的 cpu.weight (来自 CPU request;未设置的 request 仍会产生默认的 2 shares)、 cpu.max 和 memory.max 。Memory QoS 会添加 memory.high 、 memory.min 和 memory.low 。


转载声明:本文为转载整理,原文版权归原作者及原站点所有。

原文来源:Kubernetes Blog

原创地址:https://kubernetes.io/blog/2026/10/06/kubernetes-cgroups-v2-shift/

原文作者:Kubernetes Blog;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2022-11-21

  • 博文量
    1090
  • 访问量
    546996