1. CGroups 基础概念与工作原理
在 Linux 系统中,资源管理一直是个重要课题。传统方法如 nice 命令只能简单调整进程优先级,而无法实现精细化的资源控制。CGroups(Control Groups)的出现彻底改变了这一局面,它允许我们对进程组进行系统资源的分配、限制和监控。
1.1 CGroups 核心架构
CGroups 通过子系统(subsystem)机制实现对不同资源的控制。每个子系统负责管理特定类型的资源:
- cpu:限制 CPU 时间分配
- memory:控制内存使用量
- blkio:限制块设备 I/O
- cpuset:绑定进程到特定 CPU 核
- net_cls:标记网络数据包用于流量控制
这些子系统通过层级结构(hierarchy)组织起来。每个层级是一棵由 cgroup 节点构成的树,可以附加一个或多个子系统。内核通过这种设计实现了灵活的资源控制策略。
1.2 进程与 CGroups 的关联机制
进程与 CGroups 的关联通过 css_set(cgroup subsystem set)结构实现。关键点在于:
- 每个进程描述符(task_struct)包含指向 css_set 的指针
- 同一 css_set 的进程共享相同的资源限制
- 一个进程可以属于多个层级结构的不同 cgroup
这种多对多关系使得我们可以对进程施加多种资源限制。例如,一个 web 服务进程可以同时受限于 CPU 配额和内存使用上限。
2. 环境准备与子系统挂载
2.1 系统要求检查
在开始使用 CGroups 前,需要确认:
bash复制# 检查内核支持
grep CGROUP /boot/config-$(uname -r)
# 查看已挂载的cgroup
mount | grep cgroup
# 安装管理工具
sudo apt install cgroup-tools # Ubuntu/Debian
sudo yum install libcgroup # CentOS/RHEL
2.2 手动挂载子系统
虽然现代发行版通常会自动挂载基本子系统,但了解手动挂载方法很有必要:
bash复制# 创建挂载点
sudo mkdir -p /cgroup/cpu_mem
# 挂载cpu和memory子系统
sudo mount -t cgroup -o cpu,memory cpu_mem /cgroup/cpu_mem
挂载后会在指定目录下生成控制文件:
code复制/cgroup/cpu_mem/
├── cgroup.procs
├── cpu.shares
├── cpu.cfs_period_us
├── cpu.cfs_quota_us
├── memory.limit_in_bytes
└── ...
2.3 通过配置文件管理
对于生产环境,推荐使用 /etc/cgconfig.conf 进行持久化配置:
conf复制mount {
cpu = /cgroup/cpu;
memory = /cgroup/memory;
}
group limited_web {
cpu {
cpu.shares = 512;
}
memory {
memory.limit_in_bytes = 1G;
}
}
加载配置:
bash复制sudo cgconfigparser -l /etc/cgconfig.conf
3. CPU 资源控制实战
3.1 CPU 份额分配
cpu.shares 文件定义了相对权重。假设我们创建两个控制组:
bash复制sudo cgcreate -g cpu:/group_a -g cpu:/group_b
echo 512 > /cgroup/cpu/group_a/cpu.shares
echo 1024 > /cgroup/cpu/group_b/cpu.shares
这意味着当 CPU 繁忙时,group_b 获得的 CPU 时间是 group_a 的两倍。但需要注意:
- 只在 CPU 竞争时生效
- 不是硬性限制,进程仍可能使用更多 CPU
- 默认值为 1024
3.2 CPU 硬性限额
对于更严格的控制,使用 CFS(完全公平调度器)配额:
bash复制# 设置周期为100ms(100000us)
echo 100000 > /cgroup/cpu/group_a/cpu.cfs_period_us
# 设置每周期可用50ms(50000us),即限制为50% CPU
echo 50000 > /cgroup/cpu/group_a/cpu.cfs_quota_us
这种设置会严格限制该 cgroup 中所有进程在每 100ms 周期内最多使用 50ms 的 CPU 时间。
3.3 实时进程调度
对于实时性要求高的进程,可以使用 RT(实时)调度策略:
bash复制echo 1000000 > /cgroup/cpu/group_a/cpu.rt_period_us
echo 200000 > /cgroup/cpu/group_a/cpu.rt_runtime_us
这表示每 1 秒周期内,实时任务最多可运行 0.2 秒。
重要提示:修改实时调度参数需要谨慎,不当设置可能导致系统不稳定。建议先在测试环境验证。
4. 内存资源控制详解
4.1 内存限制基础
设置内存限制(单位:字节):
bash复制# 限制为1GB
echo 1073741824 > /cgroup/memory/group_a/memory.limit_in_bytes
当 cgroup 中的进程尝试分配超过限制的内存时:
- 首先触发 OOM killer 终止组内进程
- 如果无法回收足够内存,则返回 ENOMEM 错误
4.2 内存+Swap 限制
要同时限制交换空间使用:
bash复制echo 2147483648 > /cgroup/memory/group_a/memory.memsw.limit_in_bytes
注意:启用此功能需要在内核启动时添加 swapaccount=1 参数。
4.3 高级内存控制
-
软限制:允许临时超限
bash复制echo 900M > /cgroup/memory/group_a/memory.soft_limit_in_bytes -
OOM 控制:调整 OOM killer 行为
bash复制echo 1 > /cgroup/memory/group_a/memory.oom_control -
统计信息:监控内存使用
bash复制cat /cgroup/memory/group_a/memory.stat
5. 综合实战案例
5.1 Web 服务资源隔离
假设我们需要部署 Nginx 和 MySQL,要求:
- Nginx 不超过 50% CPU 和 2GB 内存
- MySQL 不超过 30% CPU 和 4GB 内存
实现步骤:
bash复制# 创建cgroup
sudo cgcreate -g cpu,memory:/web_server -g cpu,memory:/db_server
# Nginx限制
echo 50000 > /cgroup/cpu/web_server/cpu.cfs_quota_us
echo 2147483648 > /cgroup/memory/web_server/memory.limit_in_bytes
# MySQL限制
echo 30000 > /cgroup/cpu/db_server/cpu.cfs_quota_us
echo 4294967296 > /cgroup/memory/db_server/memory.limit_in_bytes
# 启动服务
cgexec -g cpu,memory:/web_server systemctl start nginx
cgexec -g cpu,memory:/db_server systemctl start mysql
5.2 批处理任务控制
对于耗时的批处理任务,可以防止其占用过多资源:
bash复制# 创建cgroup
cgcreate -g cpu,memory:/batch_jobs
# 设置限制
echo 20000 > /cgroup/cpu/batch_jobs/cpu.cfs_quota_us # 20% CPU
echo 1G > /cgroup/memory/batch_jobs/memory.limit_in_bytes
# 运行任务
cgexec -g cpu,memory:/batch_jobs ./data_processor.sh
5.3 多租户环境隔离
在共享主机环境中,可以为每个租户创建独立的 cgroup:
bash复制for tenant in {1..5}; do
cgcreate -g cpu,memory:/tenant_$tenant
echo $((20000 + $tenant * 5000)) > /cgroup/cpu/tenant_$tenant/cpu.cfs_quota_us
echo $((2**30 * $tenant)) > /cgroup/memory/tenant_$tenant/memory.limit_in_bytes
done
6. 监控与调试技巧
6.1 资源使用统计
查看 CPU 使用情况:
bash复制cat /cgroup/cpu/group_a/cpu.stat
输出示例:
code复制nr_periods 1250
nr_throttled 32
throttled_time 125000000
内存统计更详细:
bash复制cat /cgroup/memory/group_a/memory.stat
6.2 压力测试验证
使用 stress 工具测试限制效果:
bash复制# 在限制组内启动压力测试
cgexec -g cpu,memory:/test_group stress --cpu 4 --vm 2 --vm-bytes 512M
同时监控资源使用:
bash复制watch -n 1 "cat /cgroup/cpu/test_group/cpu.stat; echo; cat /cgroup/memory/test_group/memory.usage_in_bytes"
6.3 常见问题排查
问题1:设置的内存限制不生效
- 检查内核是否启用 memory cgroup:
grep CONFIG_MEMCG /boot/config-$(uname -r) - 确认挂载了 memory 子系统:
mount | grep memory
问题2:CPU 限制波动大
- 检查 cpu.cfs_period_us 和 cpu.cfs_quota_us 的比例关系
- 确认没有其他高优先级进程竞争 CPU
问题3:OOM killer 频繁触发
- 检查 memory.stat 找出内存使用模式
- 考虑设置 memory.soft_limit_in_bytes 作为缓冲
7. 高级应用与最佳实践
7.1 与 systemd 集成
现代 Linux 发行版通常使用 systemd 管理 cgroup:
bash复制# 查看systemd管理的cgroup
systemd-cgls
# 为服务设置限制
sudo systemctl set-property nginx.service CPUQuota=50% MemoryLimit=1G
配置会自动持久化到 /etc/systemd/system.control/ 目录。
7.2 Docker 容器资源限制
Docker 底层使用 cgroups 实现资源控制:
bash复制# 运行容器时设置限制
docker run -it --cpu-quota=50000 --memory=1g alpine /bin/sh
等效于手动创建 cgroup 并设置相应参数。
7.3 安全注意事项
-
权限管理:
bash复制# 设置cgroup目录权限 chown root:webadmin /cgroup/web_group chmod 750 /cgroup/web_group/tasks -
防止资源耗尽攻击:
- 为关键系统服务保留资源
- 设置合理的监控告警
-
定期审计:
bash复制# 检查异常cgroup配置 find /sys/fs/cgroup -type f -name "*.limit*" -exec ls -l {} \;
在实际生产环境中,我通常会采用分层限制策略:先为每个应用设置全局限制,再为关键组件设置更细粒度的控制。同时建议建立完善的监控体系,记录 cgroup 的资源使用情况,这对容量规划和故障排查都很有帮助。
