从零上手 Linux 资源控制:手把手带你玩懂 Cgroups,容器的底层秘密原来这么简单
你有没有好奇过:Docker 容器是怎么做到限制一个程序只能用 1G 内存、只能用 20% CPU 的?
答案其实不在 Docker 本身,而在 Linux 操作系统里 —— 它叫 Cgroups(控制组)。今天这篇文章,我们完全从零开始,不用 Docker,纯手动操作,一步步带你亲眼见证:操作系统是怎么给进程 "上枷锁" 的。
全程命令可复制,每一步都告诉你在哪个窗口敲、敲什么、会看到什么。新手友好,放心食用。
1 战前准备:先认识两个神器工具
在正式玩 Cgroups 之前,我们需要两个帮手:一个用来 "搞破坏"(制造压力),一个用来 "看监控"(观察效果)。
1.1 stress:专业的系统压力制造机
stress 就是一个专门给系统添堵的工具。你想让 CPU 跑满?想让内存飙高?想让磁盘疯狂读写?它都能一键搞定。
1.1.1 安装 stress
根据你的系统选一条命令执行即可:
Ubuntu / Debian 系统:
apt remove stress -y # 先卸载旧版(没有也没关系)
apt install stress -y # 安装
CentOS / 红帽系系统:
yum remove stress -y # 先卸载旧版
yum install stress -y # 安装
1.1.2 几个核心参数(大白话版)
| 参数 | 作用 | 实际意思 |
|---|---|---|
-c N | 产生 N 个 CPU 压力进程 | 让 N 个核跑满 100% |
-m N | 产生 N 个内存压力进程 | 不断申请释放内存 |
--vm-bytes B | 每个进程分配多大内存 | 比如 50M 就是每个进程吃 50M |
-t N | N 秒后自动结束 | 防止把系统玩崩了 |
-q | 安静模式,不输出信息 | 眼不见心不烦 |
1.2 pidstat:进程资源监控小能手
pidstat 可以实时查看每个进程占用了多少 CPU、多少内存、多少 IO。我们用它来验证 "限制到底生没生效"。
1.2.1 安装 pidstat
它属于 sysstat 工具包:
Ubuntu / Debian 系统:
apt remove sysstat -y
apt install sysstat -y
CentOS / 红帽系系统:
yum remove sysstat -y
yum install sysstat -y
1.2.2 常用参数(大白话版)
| 参数 | 作用 |
|---|---|
-u | 看 CPU 使用情况 |
-r | 看内存使用情况 |
-C 进程名 | 只看叫这个名字的进程 |
-p ALL | 显示所有进程 |
举个例子:
pidstat -u -C stress -p ALL 1 3
意思是:每秒刷新一次,总共刷 3 次,只看名字叫 stress 的进程的 CPU 占用。
2 初识 Cgroups:先看看系统里都有啥
Cgroups 不是什么神秘软件,它本质上就是 Linux 内核里的一套功能,以文件系统的形式暴露给用户。说白了,你改改文件内容,就能限制资源。
2.1 确认你的系统支持 Cgroups
操作窗口:终端窗口 A
执行命令:
cat /proc/filesystems | grep cgroup
预期看到的效果:
nodev cgroup
nodev cgroup2
看到 cgroup2 就说明你的系统支持第二版 Cgroups,没问题,继续往下走。
2.2 看看 Cgroups 能控制哪些资源
Cgroups 是按 "子系统" 分工的,每个子系统管一类资源。
操作窗口:终端窗口 A
执行命令:
cat /proc/cgroups
预期看到的效果(节选关键部分):
#subsys_name hierarchy num_cgroups enabled
cpuset 8 2 1
cpu 2 100 1
cpuacct 2 100 1
blkio 6 98 1
memory 10 124 1
devices 12 98 1
freezer 7 2 1
pids 5 107 1
挑几个最常用的给你翻译一下:
cpu:管 CPU 使用时间memory:管内存使用量blkio:管磁盘 IO 速度pids:管进程数量上限freezer:可以冻结 / 解冻一整组进程
2.3 Cgroups 文件系统挂在哪
所有的配置文件都在 /sys/fs/cgroup 目录下。
操作窗口:终端窗口 A
执行命令:
mount | grep cgroup
预期看到的效果(节选):
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup
cgroup on /sys/fs/cgroup/memory type cgroup
cgroup on /sys/fs/cgroup/blkio type cgroup
...
记住这个目录:/sys/fs/cgroup。接下来所有操作,都是在这个目录下建文件夹、改文件。
2.4 怎么看一个进程属于哪个控制组
每个进程都有自己的 Cgroups 归属,我们可以查看当前 shell 进程的归属。
操作窗口:终端窗口 A
执行命令:
cat /proc/$$/cgroup
小知识:
$$在 shell 里代表 "当前进程的 PID"。
预期看到的效果:
11:hugetlb:/
10:memory:/user.slice
9:freezer:/
8:cpuset:/
7:perf_event:/
...
3:cpuacct,cpu:/user.slice
...
意思就是:当前这个 shell 进程,在内存子系统里属于 /user.slice 这个组,在 CPU 子系统里也属于 /user.slice 这个组。系统默认已经帮我们分好组了。
3 实战一:手动限制进程内存使用量
重头戏来了。我们现在要亲手创建一个 "内存控制组",限制里面的进程最多只能用 20M 内存,然后放一个想吃 50M 的进程进去,看看会发生什么。
3.1 第一步:创建内存控制组
Cgroups 的规则很简单:建个目录,系统就自动生成配置文件。
操作窗口:终端窗口 A(操作窗口)
# 进入内存子系统目录
cd /sys/fs/cgroup/memory
# 创建一个叫 test_memory 的控制组(就是建个文件夹)
mkdir test_memory
# 进去看看里面有啥
ll test_memory
预期看到的效果:
drwxr-xr-x 2 root root 0 ... ./
dr-xr-xr-x 7 root root 0 ... ../
-rw-r--r-- 1 root root 0 ... cgroup.procs
-rw-r--r-- 1 root root 0 ... memory.limit_in_bytes
-rw-r--r-- 1 root root 0 ... memory.usage_in_bytes
-rw-r--r-- 1 root root 0 ... memory.failcnt
...(还有一堆文件)
神奇吧?我们只是建了个空文件夹,系统自动生成了几十个配置文件。核心就两个:
memory.limit_in_bytes:内存上限,往里面写数字就是设限制tasks:把进程 PID 写进去,这个进程就归这个组管了
3.2 第二步:设置内存上限为 20M
我们限制这个组最多使用 20MB 内存。20M 换算成字节是 20 * 1024 * 1024 = 20971520。
操作窗口:终端窗口 A
# 先算一下 20M 等于多少字节(可选,也可以直接写数字)
expr 20 \* 1024 \* 1024
# 输出:20971520
# 设置内存上限
echo "20971520" > test_memory/memory.limit_in_bytes
# 验证一下设置成功没
cat test_memory/memory.limit_in_bytes
预期看到的效果:
20971520
限制已经设好了。现在这个组里的所有进程,加起来内存不能超过 20M,超过了就会触发 OOM(内存不足)被杀掉。
3.3 第三步:启动一个吃内存的进程
我们用 stress 启动一个进程,让它尝试占用 50M 内存。
操作窗口:终端窗口 B(压力窗口,保持运行不要关)
stress -m 1 --vm-bytes 50M
预期看到的效果:
stress: info: [xxxxx] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
程序正常运行中,没有报错。因为现在它还没被加入我们的限制组,想吃多少吃多少。
3.4 第四步:观察当前内存占用
在加限制之前,先看看它正常吃内存是什么样的。
操作窗口:终端窗口 C(监控窗口)
pidstat -r -C stress -p ALL 1 5
参数说明:
-r看内存,-C stress只看 stress 进程,每秒刷新一次,刷 5 次。
预期看到的效果(关注 RSS 列,单位 KB):
02:47:02 PM UID PID minflt/s majflt/s VSZ RSS %MEM Command
02:47:03 PM 0 62518 483459.00 0.00 55060 15156 0.75 stress
可以看到 RSS(实际物理内存)大概在 15000 KB 左右,也就是约 15M,并且稳定运行。
3.5 第五步:把进程丢进限制组,见证奇迹
现在我们把这个 stress 工作进程的 PID,写入 tasks 文件,让它受 20M 限制的管控。
注意:stress 启动后会有两个进程 —— 一个主进程(管理用),一个工作进程(真正吃内存的)。我们要限制的是工作进程。从上面 pidstat 输出里找到那个 RSS 大的 PID,比如
62518。
操作窗口:终端窗口 A(操作窗口)
# 进入目录
cd /sys/fs/cgroup/memory
# 把工作进程 PID 写入 tasks 文件
echo 62518 >> test_memory/tasks
写入之后,立刻观察两个窗口的变化:
窗口 B(压力窗口)预期效果:
stress: FAIL: [62517] (415) <-- worker 62518 got signal 9
stress: WARN: [62517] (417) now reaping child worker processes
stress: FAIL: [62517] (451) failed run completed in 176s
进程收到了 9 号信号(SIGKILL),直接被杀死了!
窗口 C(监控窗口)预期效果: 再过几秒刷新,你会发现 PID 为 62518 的进程消失了,只剩下主进程。
这就是 Cgroups 的威力: 进程试图申请超过限制的内存,内核直接把它干掉,绝不手软。Docker 里的容器内存溢出被杀,底层就是这个机制。
4 实战二:手动限制进程 CPU 使用率
内存控制看完了,我们再来玩 CPU 控制。这次我们让一个本来跑满 100% CPU 的进程,被限制到只能用 30%。
4.1 第一步:创建 CPU 控制组
和内存一样,先建文件夹。
操作窗口:终端窗口 A(操作窗口)
# 进入 CPU 子系统目录
cd /sys/fs/cgroup/cpu
# 创建 test_cpu 控制组
mkdir test_cpu
# 看看自动生成了哪些文件
ll test_cpu
预期看到的关键文件:
cpu.cfs_period_us:一个调度周期的长度,默认 100000 微秒(也就是 100ms)cpu.cfs_quota_us:这个组在一个周期内能用多少微秒的 CPU 时间tasks:还是老规矩,写 PID 进去就归这个组管
4.2 第二步:理解 CPU 限制的计算公式
这是最核心的原理,一句话讲明白:
CPU 使用率上限 = cpu.cfs_quota_us/cpu.cfs_period_us
cpu.cfs_period_us默认是100000(100 毫秒)cpu.cfs_quota_us默认是-1(不限制)
如果我们想限制到 30%,那就是: 100000 × 30% = 30000 微秒
所以把 cpu.cfs_quota_us 设为 30000 就行。
4.3 第三步:设置 CPU 使用率上限为 30%
操作窗口:终端窗口 A
# 设置配额为 30000 微秒
echo 30000 > test_cpu/cpu.cfs_quota_us
# 验证一下
cat test_cpu/cpu.cfs_quota_us
预期输出:
30000
4.4 第四步:启动一个跑满 CPU 的进程
用 stress 让一个 CPU 核跑满。
操作窗口:终端窗口 B(压力窗口,保持运行)
stress -c 1
预期看到的效果:
stress: info: [62576] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd
程序正常运行,此时这个进程应该占满一个 CPU 核心。
4.5 第五步:观察限制前的 CPU 占用
操作窗口:终端窗口 C(监控窗口)
pidstat -u -C stress -p ALL 1 5
预期看到的效果:
02:59:40 PM UID PID %usr %system %guest %wait %CPU CPU Command
02:59:40 PM 0 62577 99.00 0.00 0.00 1.00 99.00 0 stress
可以看到 %CPU 接近 100%,一个核已经跑满了。记下这个工作进程的 PID(比如 62577)。
4.6 第六步:加入限制组,CPU 立刻 "降速"
操作窗口:终端窗口 A(操作窗口)
cd /sys/fs/cgroup/cpu
echo 62577 > test_cpu/tasks
写入之后,立刻看监控窗口。
窗口 C(监控窗口)预期效果:
03:12:53 PM UID PID %usr %system %guest %wait %CPU CPU Command
03:12:53 PM 0 62577 30.00 0.00 0.00 70.00 30.00 0 stress
见证奇迹的时刻!CPU 使用率从接近 100% 直接掉到了 30% 左右,并且稳定维持在这个水平。
内核会严格按照你设定的配额分配时间片:每 100 毫秒里,只给这个进程 30 毫秒的运行时间,剩下 70 毫秒让它等着。所以你看到 %wait 变成了 70% 左右 —— 大部分时间在排队等 CPU。
5 恍然大悟:Docker 和 Cgroups 是什么关系
做完这两个实验,你应该能品出味儿来了。
Docker 所谓的 "容器资源限制",本质上就是:
- 帮你在
/sys/fs/cgroup下面自动建文件夹 - 帮你把
memory.limit_in_bytes、cpu.cfs_quota_us这些值算好写进去 - 帮你把容器里的进程 PID 自动写入
tasks文件
你手动敲三四条命令完成的事,Docker 一条 docker run --memory=1g --cpus=0.3 就帮你做完了。
所以记住一句话:
真正做资源限制的不是 Docker,是 Linux 内核的 Cgroups。Docker 只是个封装好了的 "快捷操作面板"。
6 全文总结
今天我们用最朴素的方式,手动操作了 Linux Cgroups 的两大核心功能:
- 内存限制:修改
memory.limit_in_bytes,超了就 OOM 杀进程 - CPU 限制:通过
cfs_quota_us / cfs_period_us控制使用率上限
核心操作流程永远是三步:
- 建目录 → 创建一个控制组
- 改文件 → 设置资源限制值
- 写 tasks → 把进程丢进去接受管控
理解了 Cgroups,你再去学 Docker、K8s 的资源限制,就会发现:哦,原来都是换汤不换药,底层就这点东西。
希望这篇手把手的实战文章,能帮你捅破 Linux 资源控制的窗户纸。
更多推荐

所有评论(0)