CFN Cloud
Cloud Future New Life
en zh
2026-01-12 · 0 次浏览

Linux CGroup 深入解析:从 V1 到 V2 的资源治理路径

系统梳理 CGroup 的核心概念、V1/V2 的结构差异、关键控制器与实战排错路径。

CGroup(Control Group)是 Linux 资源治理体系的核心。它解决了一个很现实的问题:同一台机器上的不同进程如何被限制和隔离,并且能被清晰地度量。容器时代,CGroup 成为 Docker、Kubernetes 的“资源底座”。但它并不是容器专属技术,而是 Linux 的通用能力。理解它,才能理解“为什么我的容器会被 OOM Kill”“为什么限了 1 核却还是抢 CPU”“为什么迁移到 CGroup V2 后限制方式变了”。

这篇文章在参考了前人对 CGroup 的基础介绍之后,进一步扩展成一篇从概念到工程落地的长文,覆盖 V1 与 V2 的结构差异、核心控制器、典型配置、线程与进程关系、systemd 与容器的结合、迁移与排错。目标是:让你能用它解决真实问题,而不仅是记住术语。

1. CGroup 想解决的是什么

在没有 CGroup 的时代,资源管理主要靠:

  • 进程优先级(nice/renice)
  • 调度器策略(CFS、RT 等)
  • 手工运维经验(“这台机器跑慢了,重启吧”)

这些方法对“隔离”和“强约束”是无能为力的。举个例子:一个进程可以无限吃内存导致全机 OOM;一个 CPU 密集进程可以把其他进程挤得几乎得不到时间片;一个写磁盘的任务可以把 IO 打满让线上服务抖成“心电图”。

CGroup 的核心思路是:把一组进程当成一个资源控制单元,统一施加限制和统计。这样你可以说:

  • 这组进程最多使用 1 核
  • 这组进程最多使用 512Mi 内存
  • 这组进程每秒最多写入 10MB
  • 这组进程最多只能创建 200 个子进程

如果你只记住一句话:CGroup 是 Linux 中“进程组级别的资源配额与度量框架”。

2. CGroup 的核心概念(V1 术语)

下面是 CGroup V1 的四个核心概念,也是理解 V2 的基础:

  • Task(任务):CGroup 的最小单位。它不是“传统意义上的进程”,而是 Linux 线程(LWP)层面的执行实体。
  • CGroup(控制组):一组 Tasks 的集合。对该组统一施加资源限制。
  • Hierarchy(层级树):CGroup 的组织方式,呈树形结构。子 CGroup 继承父 CGroup 的资源配置。
  • Subsystem(子系统)/ Controller(控制器):具体资源的控制模块,如 CPU、内存、IO、pids、devices 等。

V1 的一个关键特点是:多个 Hierarchy 并行存在,不同 Subsystem 可以挂载在不同的 Hierarchy 上。这给灵活性带来了优势,但也造成了结构复杂和理解混乱,后面会看到为什么 V2 做了“大手术”。

3. 进程、线程与 Task:CGroup 语义的关键

Linux 线程是通过轻量级进程(LWP)实现的:

  • 进程 PID 指向主线程
  • 其他线程也有独立的 TID
  • 在 /proc//task/ 下可以看到该进程所有线程

这意味着“进程”和“线程”在内核里并没有完全不同的身份。CGroup 在 V1 中暴露了两个入口:

  • tasks:写入线程 ID
  • cgroup.procs:写入进程 ID

因此你会看到奇怪的现象:写入 cgroup.procs 会把整个进程加入;写入 tasks 只会移动某个线程。大多数情况下我们希望“对进程整体生效”,所以更建议使用 cgroup.procs,而不是 tasks。V2 也因此对语义做了重新划分。

3.1 小实验:线程 ID 与进程 ID 的差异

下面是一个最小 C 程序,会创建多个线程并让它们自旋。你可以用它观察 tasks 与 cgroup.procs 的差异。

#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>

static void *spin(void *arg) {
  long tid = syscall(SYS_gettid);
  printf("thread tid=%ld\n", tid);
  while (1) { }
  return NULL;
}

int main() {
  printf("main pid=%d\n", getpid());
  pthread_t t1, t2, t3;
  pthread_create(&t1, NULL, spin, NULL);
  pthread_create(&t2, NULL, spin, NULL);
  pthread_create(&t3, NULL, spin, NULL);
  while (1) { }
  return 0;
}

编译并运行:

gcc -O2 -pthread t.c -o t && ./t

观察线程:

ps -T -p <pid>
ls /proc/<pid>/task

接下来创建一个 CGroup(V1 示例)并分别写入:

mkdir /sys/fs/cgroup/cpu/demo
echo <pid> > /sys/fs/cgroup/cpu/demo/cgroup.procs

这会把整个进程的所有线程都放进去;如果改成:

echo <tid> > /sys/fs/cgroup/cpu/demo/tasks

那么只有这个线程被移动,其它线程仍留在原来的 CGroup。
这就是为什么“只写 tasks 导致 CPU 限制失效”的问题经常发生。

4. CGroup V1 的文件系统结构

V1 通过 cgroupfs 虚拟文件系统实现。一个 CGroup 就是一个目录,目录里的文件代表配置项和任务列表。

常见目录结构如下:

ls /sys/fs/cgroup

里面会看到 cpu、memory、blkio、cpuset 等目录。每个目录是一个 Hierarchy,挂载了一个或多个 Subsystem。

4.1 一个 CGroup 目录里有哪些文件

以 cpu 子系统为例,创建一个子 CGroup:

mkdir /sys/fs/cgroup/cpu/demo
ls /sys/fs/cgroup/cpu/demo

通常包括四类内容:

  1. Subsystem 配置文件,例如 cpu.shares、cpu.cfs_quota_us。
  2. 任务列表文件,tasks 和 cgroup.procs。
  3. 通用配置文件,如 notify_on_release、cgroup.clone_children。
  4. 子 CGroup 子目录。

4.2 tasks vs cgroup.procs 的行为差异

  • 将进程 PID 写入 cgroup.procs:进程的所有线程被加入。
  • 将线程 ID 写入 tasks:只有该线程被加入。
  • 将线程 ID 写入 cgroup.procs:相当于把线程所属进程整体加入。
  • 将进程 PID 写入 tasks:通常会失败或无效。

这也是为什么 V1 的线程语义被认为“混乱”:它既提供线程级入口,又不鼓励线程级治理。V2 则用 cgroup.threads 做了语义分离。

4.3 快速查看当前 Subsystem 与挂载关系

在 V1 系统上可以用 cgroup-tools 查看挂载关系:

lssubsys -m

输出类似:

cpu,cpuacct /sys/fs/cgroup/cpu,cpuacct
memory /sys/fs/cgroup/memory
blkio /sys/fs/cgroup/blkio

如果没有安装工具,也可以直接查看 mount:

mount | grep cgroup

这样可以确认:哪些控制器挂载在哪个层级树上。

5. CGroup V1 的典型控制器

5.1 CPU 控制器(cpu + cpuacct)

最常用的两个参数:

  • cpu.cfs_period_us:调度周期(默认 100000us)
  • cpu.cfs_quota_us:一个周期内可用的 CPU 时间

公式:CPU 限制 = quota / period

例如限制在 50% CPU:

echo 50000 > /sys/fs/cgroup/cpu/demo/cpu.cfs_quota_us

一个可复现的测试方式是让进程自旋,然后观察 cpu.stat:

# 0.2 核
echo 100000 > /sys/fs/cgroup/cpu/demo/cpu.cfs_period_us
echo 20000 > /sys/fs/cgroup/cpu/demo/cpu.cfs_quota_us
echo $$ > /sys/fs/cgroup/cpu/demo/cgroup.procs

# 启动 CPU 自旋(会看到周期性节流)
while :; do :; done

另开一个终端观察:

cat /sys/fs/cgroup/cpu/demo/cpu.stat

如果 nr_throttled 持续增长,说明节流发生了。

cpu.shares 是权重而不是硬限制,多个组竞争时按权重分配。

cpuacct 负责统计使用量,便于监控和审计。

5.2 cpuset 控制器

cpuset 限制的是“能用哪些 CPU 核”。注意:必须先配置 cpuset.mems 与 cpuset.cpus,否则会报错。

echo 0 > /sys/fs/cgroup/cpuset/demo/cpuset.mems
echo 2-3 > /sys/fs/cgroup/cpuset/demo/cpuset.cpus

cpuset 常用于高性能隔离,例如把数据库固定在一组核心上。

5.3 memory 控制器

常见文件:

  • memory.limit_in_bytes:硬限制
  • memory.soft_limit_in_bytes:软限制
  • memory.oom_control:OOM 行为控制
  • memory.stat:详细统计

当内存超过硬限制时,会触发 OOM Kill。很多容器“突然退出”来自这里。

下面是一个简单的内存占用程序示例(按 MB 逐步分配):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char **argv) {
  int mb = argc > 1 ? atoi(argv[1]) : 512;
  size_t step = 1 << 20;
  char *buf = NULL;
  for (int i = 0; i < mb; i++) {
    buf = realloc(buf, (size_t)(i + 1) * step);
    if (!buf) return 1;
    memset(buf + (size_t)i * step, 0, step);
    usleep(20000);
  }
  pause();
  return 0;
}

配合 CGroup 内存限制进行测试:

gcc memhog.c -o memhog
mkdir /sys/fs/cgroup/memory/demo
echo 200M > /sys/fs/cgroup/memory/demo/memory.limit_in_bytes
echo $$ > /sys/fs/cgroup/memory/demo/cgroup.procs
./memhog 500

如果超过限制,进程会被 OOM Kill,你可以通过:

cat /sys/fs/cgroup/memory/demo/memory.failcnt
dmesg | tail -n 5

确认是否命中内存限制。

5.4 blkio 控制器

提供 IO 权重和限速能力:

  • blkio.weight
  • blkio.throttle.read_bps_device
  • blkio.throttle.write_bps_device

适合限制日志、备份等写盘密集任务。

一个简单的限速示例(限制写入 10MB/s):

mkdir /sys/fs/cgroup/blkio/demo
# 设备主次号以 /dev/sda 为例
echo "8:0 10485760" > /sys/fs/cgroup/blkio/demo/blkio.throttle.write_bps_device
echo $$ > /sys/fs/cgroup/blkio/demo/cgroup.procs
dd if=/dev/zero of=/tmp/test.data bs=1M count=200 oflag=direct

这类限速对“日志风暴”“备份占满磁盘带宽”的场景非常有用。

5.5 pids 控制器

限制进程数量,防止 fork-bomb:

echo 200 > /sys/fs/cgroup/pids/demo/pids.max

测试时可以用一个循环快速创建子进程(注意只在测试机执行):

mkdir /sys/fs/cgroup/pids/demo
echo 50 > /sys/fs/cgroup/pids/demo/pids.max
echo $$ > /sys/fs/cgroup/pids/demo/cgroup.procs
for i in $(seq 1 200); do sleep 60 & done

当进程数量超过限制时,新进程创建会失败,通常会报 fork: Resource temporarily unavailable

5.6 devices / freezer / net_cls

  • devices:限制访问设备节点(如 /dev/nvidia0)
  • freezer:冻结/解冻进程组
  • net_cls / net_prio:网络分类与优先级

这些控制器在云环境和多租户环境下非常重要。

6. V1 的典型实践:限制一组进程的 CPU

下面是一个更完整的例子,演示如何限制一个多线程进程的 CPU:

# 创建 CGroup
mkdir /sys/fs/cgroup/cpu/wdj
# 限制 50% CPU
echo 50000 > /sys/fs/cgroup/cpu/wdj/cpu.cfs_quota_us
# 将进程 PID 写入 cgroup.procs
echo <pid> > /sys/fs/cgroup/cpu/wdj/cgroup.procs

如果你只把某个线程 ID 写入 tasks,那么只有该线程受限,其余线程仍会满速运行,这会让“限制失效”的错觉出现。很多线上故障其实来自这种误用。

7. V1 的结构性问题

V1 的灵活性很大,但也带来长期问题:

  1. 多 Hierarchy 并行,进程可能在多个树中有不同路径,理解成本高。
  2. Subsystem 可以挂在不同树上,分组与资源控制分离导致逻辑混乱。
  3. tasks/cgroup.procs 语义不清,线程级控制难以解释。

这些问题最终推动了 V2 的诞生。

8. CGroup V2 的设计理念

V2 的关键变更:统一层级(Unified Hierarchy)。

  • 只有一棵树
  • 所有控制器挂在同一层级
  • 一个进程只能属于一个 CGroup

这让资源治理结构更清晰,也让容器、systemd、Kubernetes 的映射更直观。

9. CGroup V2 的核心文件

挂载:

mount -t cgroup2 none /sys/fs/cgroup

顶层关键文件:

  • cgroup.controllers:支持的控制器
  • cgroup.subtree_control:对子层级启用控制器
  • cgroup.procs:进程列表

示例:

cat /sys/fs/cgroup/cgroup.controllers
echo "+cpu +memory" > /sys/fs/cgroup/cgroup.subtree_control

9.1 叶子节点才允许挂任务

V2 规则:

  • 中间节点负责开启控制器
  • 叶子节点负责挂进程并配置资源

这样每个节点要么“管理”,要么“执行”。结构更清晰。

一个 V2 的完整示例(父节点开控制器,子节点配置资源):

# 假设已挂载在 /sys/fs/cgroup
cd /sys/fs/cgroup
echo "+cpu +memory" > cgroup.subtree_control

# 创建叶子节点并设置限制
mkdir demo
echo "50000 100000" > demo/cpu.max
echo "300M" > demo/memory.max

# 把当前 shell 加入
echo $$ > demo/cgroup.procs
cat demo/cgroup.procs

如果你把进程写进父节点,就会触发 “non-empty cgroup” 的限制,这是 V2 的设计约束。

9.2 cgroup.threads 与线程治理

V2 用 cgroup.threads 显式管理线程,避免 V1 的语义混乱。默认情况下仍建议按进程治理。

9.3 cgroup.events 的语义

cgroup.events 的 populated 字段可用于判断该 CGroup 是否还有活跃进程,是资源回收与自动清理的重要依据。

10. V2 控制器的典型配置方式

10.1 CPU(cpu.max / cpu.weight)

V2 用 cpu.max 代替 cpu.cfs_quota_us:

# 限制 1 核
echo "100000 100000" > cpu.max

cpu.weight 的范围是 1-10000,比 V1 的 cpu.shares 更直观。

配合观察:

cat cpu.stat
# 示例字段:usage_usec, user_usec, system_usec, nr_throttled, throttled_usec

如果你看到 nr_throttled 持续增长,说明 CPU 限额已经在起作用。

10.2 memory(memory.max / memory.high)

  • memory.high:软限制(压力触发)
  • memory.max:硬限制(直接 OOM Kill)
echo 500M > memory.high
echo 800M > memory.max

关键事件可以通过 memory.events 观察:

cat memory.events
# 示例:high 12  max 1  oom 1  oom_kill 1

memory.high 不会直接杀进程,memory.max 会触发 OOM Kill。

10.3 io(io.max / io.weight)

V2 统一为 io.max / io.weight,控制设备级别吞吐或 IOPS。

10.4 pids.max

限制进程数量,语义更清晰。

10.5 压力指标(PSI)

V2 引入 PSI(pressure stall information),可以观察 CPU/内存/IO 的“被压制时间”,是判断系统是否在“慢性卡顿”的关键指标。

11. systemd 与 CGroup 的关系

现代 Linux 使用 systemd 管理进程,systemd 自动使用 CGroup 组织服务:

  • system.slice:系统服务
  • user.slice:用户会话
  • machine.slice:虚拟机或容器

常用命令:

systemd-cgls
systemctl status <service>

systemd-run 也可以直接创建带限制的 scope:

systemd-run --scope -p MemoryMax=200M -p CPUQuota=50% bash

查看对应的 CGroup 路径:

systemctl show -p ControlGroup <service>
systemd-cgls

这说明:CGroup 已经是系统默认能力,而不只是“手工挂载的技术”。

12. CGroup 与容器(Docker/Kubernetes)

容器并不是“隔离进程的魔法”,它本质是:

  • namespace(隔离视图)
  • cgroup(限制资源)

12.1 Docker 的限制映射

docker run --memory 512m --cpus 1 nginx

对应的就是 memory 与 cpu 控制器配置。

你可以在宿主机上找到容器对应的 CGroup 路径:

docker run -d --name demo --memory 256m --cpus 0.5 nginx
docker inspect --format '{{.Id}}' demo
cat /proc/$(docker inspect --format '{{.State.Pid}}' demo)/cgroup

在容器内部也能看到自身的 CGroup 视图:

docker exec demo cat /proc/1/cgroup

12.2 Kubernetes 的资源模型

  • limits.cpu → cpu.max / cpu.cfs_quota_us
  • limits.memory → memory.max / memory.limit_in_bytes

QoS 分类(Guaranteed / Burstable / BestEffort)也与 CGroup 的资源优先级和回收策略密切相关。

13. 线程治理的现实意义

线程级别控制是可能的,但不建议作为默认手段。原因:

  • 线程之间共享内存,限制某线程可能导致整体不可预测
  • 线程迁移造成统计混乱

除非你非常清楚线程责任,否则建议以进程为单位治理。

如果一定要在 V2 里做线程级操作,可以使用 cgroup.threads:

# 进入某个叶子节点
cd /sys/fs/cgroup/demo
echo <tid> > cgroup.threads
cat cgroup.threads

注意:线程级操作更容易引起不可预期的性能问题,谨慎使用。

14. CPU 限额与调度的细节陷阱

很多“限了 CPU 但还是抖”的问题,根因是对 CFS 配额理解不够。CPU 资源不是连续的水龙头,而是被切片调度的时间片。以 V2 为例:

  • cpu.max = quota/period
  • 若 quota 小于 period,进程会在一个周期内被强行节流(throttle)
  • 节流不是平滑的,它会造成“突发可用 + 一段时间完全不可运行”

因此在低配额下,延迟敏感的服务会出现“周期性抖动”。这是 CFS 的设计结果,而非配置错误。排查时可以看 cpu.stat 里的 nr_throttled 和 throttled_usec,它们会告诉你节流是否发生,以及节流总时长。

工程上常见的缓解方法包括:

  • 提高 period(让节流周期更长、更平滑)
  • 给延迟敏感服务留出更高的 quota
  • 用 cpu.weight + 合理的资源隔离,减少硬限制

15. 内存限制与 OOM 行为的层级影响

内存控制不仅是“设一个上限”。在 V2 中,关键指标包括:

  • memory.current:当前使用量
  • memory.high:软限制,触发回收压力
  • memory.max:硬限制,触发 OOM Kill
  • memory.events:统计 OOM 与回收事件

当 memory.high 被触发时,内核会通过回收和延迟来“惩罚”该组,这会造成延迟上升但不会立刻杀进程。只有超过 memory.max,才会触发 OOM Kill。

一个经验是:把 memory.high 设置为你愿意接受的“高压区”,把 memory.max 设置为你能容忍的“硬上限”。V2 还提供 memory.oom.group,可以把 OOM 以“组”为单位处理,避免一个进程被杀掉但其余进程还在占用内存导致服务“半死不活”。在容器场景中,这往往更符合运维预期。

16. IO 控制器的设备视角

IO 控制器在 V1 使用 blkio,V2 使用 io。它们都依赖“设备主次号”来定位磁盘。例如:

ls -l /dev/sda
# 输出中会包含主次号,如 8:0

你可以用 io.max 给某个设备限速:

echo "8:0 rbps=10485760 wbps=10485760" > io.max

这里 rbps/wbps 就是读写带宽限制。IO 控制器尤其适合日志密集或批处理任务,否则它们会把线上业务“拖进泥潭”。排查时可以查看 io.stat,它能告诉你实际读写量与延迟。

17. Delegation:把 CGroup 控制权交给“子系统”

V2 的一大改进是支持更清晰的 delegation(授权)。比如 systemd 把某个 slice 交给容器运行时,容器运行时再在其子树内做控制。这要求:

  • 父节点开启 controller
  • 父节点不再挂任务
  • 子节点拥有写权限

这也是为什么在 V2 中“中间节点不能挂任务”的设计如此重要。它保证了权限与职责的边界清晰。如果你在 rootless 容器里遇到权限问题,往往就是 delegation 没有正确建立。

18. CGroup Namespace 与可见性

CGroup namespace 让容器内看到的 CGroup 路径与宿主机不同。例如容器内看到自己在 /,但宿主机可能在 /kubepods.slice/…。这会带来两个排错习惯:

  • 宿主机侧看 /proc//cgroup 才是完整路径
  • 容器内看到的路径是“切片后的视图”

理解 namespace 能避免“容器内找不到路径”的误解。

19. V1 到 V2 的迁移要点

迁移不是简单把 cpu.cfs_quota_us 换成 cpu.max。注意:

  • V2 控制器默认关闭,需要手动开启 subtree_control
  • 只有叶子节点能挂任务
  • 老脚本常常只支持 V1 路径

如果系统同时启用 V1 与 V2,必须明确你在操作哪一套,否则配置会“看似成功但无效果”。

20. 实战排错清单(Checklist)

当你怀疑资源限制有问题时,可以按以下顺序排查:

  1. 进程在哪个 CGroup? cat /proc//cgroup

  2. CGroup 在 V1 还是 V2? stat -fc %T /sys/fs/cgroup(输出 cgroup2fs 即 V2)

  3. 控制器是否开启? cat cgroup.controllers 与 cat cgroup.subtree_control

  4. 限制是否生效? 读取 cpu.max、memory.max、io.max 等文件

  5. 是否发生节流/回收/杀死? cpu.stat / memory.events / dmesg | grep -i oom

很多时候,限制本身没错,真正的问题是:控制器没开启、节点不是叶子、或者你观察的是错误的路径。

对应命令示例:

cat /proc/<pid>/cgroup
stat -fc %T /sys/fs/cgroup
cat /sys/fs/cgroup/cgroup.controllers
cat /sys/fs/cgroup/cgroup.subtree_control
cat /sys/fs/cgroup/<group>/cpu.max
cat /sys/fs/cgroup/<group>/memory.max
cat /sys/fs/cgroup/<group>/cpu.stat
cat /sys/fs/cgroup/<group>/memory.events
dmesg | grep -i oom | tail -n 5

21. 从工程视角理解 CGroup 的边界

CGroup 不是万能钥匙。它解决的是“资源上限与公平”,但它不能解决:

  • 应用本身的内存泄漏
  • 代码级别的性能瓶颈
  • IO 或网络的外部依赖不稳定

所以 CGroup 应该是“资源治理层的一把保险锁”,而不是“性能优化的全部答案”。在工程实践里,正确的姿势是:

  • 用 CGroup 限制上限,保证系统不被拖死
  • 用监控体系观察趋势,识别异常
  • 用应用层优化解决根因

这样你才能把“资源限制”变成“可靠性工程”的一部分。

22. 与 Kubernetes 资源模型的对齐建议

Kubernetes 把资源拆成 requests 与 limits,这往往引发误解:

  • limits 是硬上限,会写入 CGroup
  • requests 影响调度,但不直接限制进程

当 requests 远低于 limits 时,服务在压力下更容易被“挤压”,表现为 latency 抖动。最佳实践是:

  • 关键服务让 requests 接近真实稳定负载
  • 对延迟敏感服务保留一定的 CPU headroom
  • 对内存敏感服务设置合理的 memory.high

23. 小结与实践建议

如果把 Linux 资源治理看作一个“交通系统”,那么 CGroup 就是红绿灯、限速、分流和统计监控。你不能只靠限速解决堵车,但没有限速,系统一定会崩。理解 CGroup 的本质,是理解现代容器化系统的必经之路。

建议你把这篇文章里的命令和思路在测试环境里亲手跑一遍,因为 CGroup 不是“概念”,它是一把可以直接影响生产系统稳定性的工具。理解它,你才能真正理解容器的资源边界,理解 Kubernetes 的 QoS 策略,理解为什么一个 Pod 会 OOM,理解为什么 CPU 限制并不总是“线性生效”。

24. CGroup 在云平台中的常见场景

在云平台与多租户环境中,CGroup 不是“可选项”,而是支撑稳定性的基础设施能力。常见场景包括:

  • 在线业务与离线批处理混部:用 CGroup 给在线服务保底,避免批处理抢光 CPU。
  • 数据库与日志写盘共存:用 IO 控制器限制日志或备份吞吐,避免延迟雪崩。
  • 租户隔离:用 memory.max 与 pids.max 防止单租户“爆炸式占用资源”。
  • GPU/高性能设备:配合 devices 控制器限制设备节点访问,避免越权。

这些场景说明:CGroup 不是单纯的“限资源”,而是一种“资源治理秩序”。它帮助你把“谁能用多少”从经验变成规则,把“出问题时如何止损”从拍脑袋变成策略。

25. 资源治理的心法

CGroup 的配置不是越严格越好。过紧的限制会让应用频繁节流或 OOM,带来更高的故障率;过松的限制又无法解决噪音邻居问题。一个可行的思路是:

  • 先用观察数据找到真实负载区间,再设置限制。
  • 对延迟敏感服务优先保证 CPU 配额与内存 headroom。
  • 对吞吐型任务允许更高的波动,用更低的优先级竞争。
  • 对共享环境明确“硬上限”,并在超限时给出可解释的告警。

当你把 CGroup 的使用从“手工限资源”升级为“有策略的资源治理”,它的价值才会真正显现。

26. 自测练习(建议在测试机完成)

  • 创建一个 demo CGroup,把一个简单脚本限制到 0.2 核并观察 cpu.stat 的 throttled 变化。
  • 设置 memory.high 与 memory.max,观察进程在逼近上限时的延迟与 OOM 行为差异。
  • 用 io.max 限制写盘带宽,比较限速前后的 p99 延迟。

这些练习的价值在于:让“配置文件”变成你能感知的系统行为。

参考