Linux control groups,也就是 cgroups 控制组机制。cgroups 是 Linux 内核用来把进程分组,并限制、统计、隔离、控制这些进程资源使用的机制。比如限制某组进程最多用多少 CPU、内存、IO、进程数等。Docker、systemd、Kubernetes 等底层都会用到它。
cgroup 是一组进程的集合,内核可以对这组进程施加资源控制。它通过一个伪文件系统暴露接口,叫 cgroupfs。你可以把 cgroup 理解成 /sys/fs/cgroup 下的一棵目录树,每个目录就是一个 cgroup:
/sys/fs/cgroup
├── blkio
├── cpu
├── devices
├── memory
├── pids
├── ...
└── systemd
不同类型的资源控制器#
资源控制由不同的 controller 完成,例如:
| 控制器 | 作用 |
|---|---|
| cpu | 控制 CPU 使用 |
| memory | 控制内存使用 |
| io / blkio | 控制磁盘 IO |
| pids | 限制进程数量 |
| cpuset | 绑定 CPU 核和 NUMA 节点 |
| freezer | 冻结/恢复进程 |
| devices | 控制设备访问 |
| hugetlb | 控制 huge pages |
cgroups的两个版本:v1 和 v2#
v1 功能灵活但复杂,v2 使用统一层级、统一规则和更安全的委派模型,是现代 Linux 推荐使用的模型。
特点比较#
| 项目 | cgroups v1 | cgroups v2 |
|---|---|---|
| 层级结构 | 多个层级 | 单一统一层级 |
| 控制器挂载 | 可分别挂载 | 自动出现在统一层级 |
| 线程控制 | 支持 tasks | 默认不支持,后续 thread mode 支持 |
| 资源分配规则 | 不统一 | 更统一 |
| 内部进程规则 | 宽松 | 有 no internal processes 限制 |
| 通知机制 | release_agent | cgroup.events |
| 委派安全 | 较弱 | 更严格 |
| 网络控制 | net_cls / net_prio | 通过 eBPF、iptables 等方式 |
v1 比较灵活,但复杂,它的特点:
- 每个 controller 可以有自己的层级
- 多个 controller 也可以挂在同一个层级
- 不同 controller 的行为不统一
- 支持把同一个进程的不同线程放到不同 cgroup
- 管理复杂,容易产生语义混乱
v2 是新模型,目标是统一和简化,它的特点:
- 所有 controller 都放在一个统一层级里
- 文件系统类型是 cgroup2
- 控制器行为更一致
- 资源分配规则更清楚
- 委派机制更安全
- 默认不再允许随意按线程分组,后来通过 thread mode 部分支持
场景比较#
新系统、新容器环境里 cgroup v2 已经更多;但存量生产环境里 cgroup v1 / 混合模式仍然很多。
| 场景 | 现在常见情况 |
|---|---|
| 新版 Linux 发行版 | 多数默认用 cgroup v2 |
| 新版 Kubernetes / containerd / Docker | 基本都支持 cgroup v2 |
| 老服务器、老集群 | 很多仍是 cgroup v1 |
| 企业存量环境 | v1、v2、混合模式都可能存在 |
| systemd 较新的系统 | 倾向 v2 统一层级 |
典型趋势是:
- Ubuntu 22.04/24.04/26.04:通常默认偏向 cgroup v2
- Debian 11/12:默认支持/使用 cgroup v2
- Fedora:很早就默认 cgroup v2
- RHEL 9:默认 cgroup v2
- RHEL 8、CentOS 7、老 Ubuntu:很多还是 cgroup v1 或混合模式
- Kubernetes:cgroup v2 从较新版本开始已经稳定支持,官方也推荐在默认启用 v2 的发行版上使用
限制、统计、隔离资源的核心作用#
把一组进程的资源使用关进一个“笼子”里,让它们只能用被分配的资源,并且可以被观察、计量和管理。
限制:防止一个进程吃光机器#
比如某个服务内存泄漏,如果没有限制,它可能把整台机器内存吃光,导致其他服务也挂掉。用 cgroup 可以限制:这个服务最多只能用 2GB 内存 或者 这个服务最多只能用 2 个 CPU 的算力。这样它出问题时,影响被限制在自己的范围内。
核心价值: 防止资源抢占和故障扩散
统计:知道谁用了多少资源#
cgroup 可以统计一组进程用了多少:
- CPU 时间
- 内存
- IO
- 进程数
- 网络相关资源
- page cache 等
比如你可以知道:
- 容器 A 当前用了 800MB 内存
- 容器 B 当前用了 1.5 个 CPU
- 服务 C 磁盘 IO 很高
核心价值: 可观测、可计费、可排障。
Docker / Kubernetes 的资源监控,本质上大量依赖 cgroup 的统计能力。
隔离:让不同工作负载互不干扰#
多个服务跑在同一台机器上时,如果没有资源隔离,大家会互相抢资源。
例如:服务 A 是核心在线服务,服务 B 是离线批处理任务。没有隔离时,服务 B 可能占满 CPU,导致服务 A 延迟升高。
用 cgroup 可以做到:服务 A 保证较高 CPU 权重,服务 B 只能使用有限 CPU。
核心价值: 让不同服务在同一台机器上共存,但互相影响更小。
最终目的#
容器不是虚拟机,它共享宿主机内核。所以容器所谓的:
--memory=1g
--cpus=2
--pids-limit=100
底层基本就是通过 cgroup 实现的。也就是说:namespace 负责“看起来隔离”,cgroup 负责“资源上隔离”。
cgroup 的核心目的不是单纯“限制资源”,而是:让一台 Linux 机器可以安全、稳定、可控地运行多个工作负载。
它解决的是这些问题:
- 谁可以用多少资源?
- 谁正在用多少资源?
- 一个服务失控会不会拖垮整台机器?
- 多个服务之间怎么公平分配资源?
- 容器的 CPU、内存限制怎么实现?
- Kubernetes Pod 的资源限制怎么落地?
所以,“限制、统计、隔离资源”的核心作用就是:资源治理。把系统资源从“大家随便抢”,变成“按规则分配、按边界使用、按指标观测”。
CPU、memory、IO 维度的控制思想#
可以把 cgroup 对 CPU、memory、IO 的控制思想理解成三种不同资源治理模型:
- CPU:按时间片/权重分配
- Memory:按容量边界限制
- IO:按设备吞吐/请求速率调度
| 维度 | 控制对象 | 核心思想 | 失控后果 |
|---|---|---|---|
| CPU | 计算时间 | 权重分配 + 配额限制 | 服务变慢、延迟升高 |
| Memory | 内存容量 | 设置边界 + 回收/OOM | OOM、系统抖动、进程被杀 |
| IO | 磁盘访问 | 权重调度 + 带宽/IOPS 限制 | 磁盘延迟升高、服务卡顿 |
CPU 控制思想:分配“计算时间”#
CPU 是一种 可抢占、可调度、可压缩 的资源。意思是:一个进程 CPU 不够用,通常不会立刻死,只是变慢。
CPU 控制的核心是: 控制进程组能获得多少 CPU 时间,以及 CPU 紧张时谁优先。
所以 CPU 控制主要关注:
- 谁优先用 CPU?
- 谁最多能用多少 CPU?
- 多个 cgroup 之间如何公平分配?
常见控制方式有两类:权重控制 和 配额控制。
权重控制:谁更重要#
比如:服务 A 权重为 1024,服务 B 权重为 512
当 CPU 紧张时,A 大约能拿到 B 的两倍 CPU 时间。这不是硬限制,而是相对分配。
配额控制:最多只能用多少#
比如可以限制某个 cgroup:最多使用 2 个 CPU。
底层思想通常是:每 100ms 周期里,最多允许运行 200ms CPU 时间。因为 2 个 CPU 并行运行 100ms,总 CPU 时间就是 200ms。
Memory 控制思想:限制“容量边界”#
Memory 和 CPU 不一样。内存是 不可随便压缩、不可无限等待 的资源。CPU 不够,进程只是慢,内存不够,可能直接:
- 分配失败
- 触发 reclaim
- 触发 swap
- 触发 OOM kill
所以 memory 控制更像是在划边界:这个 cgroup 最多只能占多少内存。
Memory 控制的核心是: 给进程组划定内存容量边界,并在接近或超过边界时回收、限速或 OOM。
硬限制:不能超过#
比如某容器最多使用 1GB 内存,超过后可能触发OOM kill。
软限制 / 保护 / 高水位#
内存控制不只是简单 kill,也有更温和的控制方式,比如:
- 超过某个值后开始回收
- 低于某个值时尽量保护
像cgroup里就有这些参数:
| 文件 | 思想 |
|---|---|
| memory.max | 硬上限,超过可能 OOM |
| memory.high | 高水位,超过后强制回收、限速 |
| memory.low | 尽量保护这部分内存 |
| memory.min | 更强的保护 |
IO 控制思想:控制“访问设备的速度和优先级”#
IO 主要指磁盘、块设备访问。IO 和 CPU、Memory 又不一样。它的特点是:
- 慢
- 共享设备
- 有队列
- 有延迟
- 容易互相拖累
比如一个批处理任务疯狂读写磁盘,可能导致数据库延迟变高。所以 IO 控制关注:
- 谁的磁盘读写优先级高?
- 谁最多能读写多少 MB/s?
- 谁最多能发多少 IOPS?
- 怎么避免一个 cgroup 把磁盘队列占满?
IO 控制的核心是: 控制进程组访问块设备的优先级、吞吐量和请求速率,避免一个任务拖慢整块磁盘。
权重控制:IO 紧张时谁优先#
类似 CPU 权重。比如:数据库服务权重高,日志压缩任务权重低。当磁盘繁忙时,数据库优先获得 IO 调度。
限速控制:最多多少带宽 / IOPS#
可以限制:
- 最多读 100MB/s
- 最多写 50MB/s
- 最多 1000 IOPS