隔离系统资源:Linux cgroup 入门
如何精确控制一个程序能用多少 CPU、内存或者磁盘带宽?比如,一个数据分析任务不应该占用所有 CPU 资源,导致系统卡顿;或者一个后台服务,即使发生内存泄漏,也不会拖垮整个服务器。这就是 Linux cgroup (control groups) 的核心作用。
什么是 cgroup?
在 Linux 中,当我们运行多个程序时,它们默认共享系统的所有可用资源。Network Namespace 隔离了网络环境,而 cgroup 则是一种 Linux 内核机制,用于隔离和管理一组进程的系统资源。
你可以把 cgroup 看作是给你的 Linux 系统创建了一个个独立的“资源配额盒子”。每个盒子都可以设定不同的资源限制,例如:
- CPU 时间: 限制进程组可以使用多少 CPU 核心或 CPU 时间比例。
- 内存使用: 限制进程组可以使用的最大内存量。
- 磁盘 I/O: 控制进程组读写磁盘的速度。
- 网络带宽: (通常与网络 Namespace 结合使用)控制进程组的网络流量。
- PID 数量: 限制进程组可以创建的子进程数量。
为什么需要 cgroup?
cgroup 的主要用途是:
- 资源隔离: 确保一个应用程序或服务不会耗尽所有系统资源,影响其他重要进程的运行。
- 资源限制: 为特定任务设置硬性资源上限,防止资源滥用。
- 优先级管理: 为不同进程组分配不同的资源优先级,例如,重要服务可以获得更多的 CPU 时间。
- 计费和监控: 跟踪不同进程组的资源使用情况,用于资源分配和性能分析。
- 容器技术: Docker 和 Kubernetes 等容器平台广泛使用 cgroup 来为每个容器分配和隔离计算资源。
cgroup 的核心概念
在深入实践之前,了解几个 cgroup 的基本概念很有帮助:
- 子系统 (Subsystem): 也称为控制器 (Controller),是 cgroup 的具体资源管理模块。每个子系统都负责一种特定类型的资源。比如,
cpu
子系统管理 CPU 资源,memory
子系统管理内存资源。 - 控制组 (Control Group): 简称 cgroup。它是一个逻辑分组,包含一个或多个进程,并关联了一组特定子系统的资源限制。你可以创建任意数量的 cgroup。
- 层级 (Hierarchy): cgroup 是以树状结构组织的。你可以创建子 cgroup,它们会继承父 cgroup 的部分属性,并且可以进一步细化资源限制。一个进程只能在一个层级中属于一个 cgroup。
理解 cgroup 最直观的方式是把它看作文件系统中的目录和文件。Linux 内核通过一个虚拟文件系统来暴露 cgroup 的接口,通常挂载在 /sys/fs/cgroup
。每个子系统都在这个目录下有自己的目录,而每个控制组则对应一个子目录,通过修改这些目录下的文件来设置资源限制。
cgroup 的实践示例
让我们通过一个简单的例子来展示如何限制一个进程的 CPU 使用。我们将创建一个新的 cgroup,并将一个长时间运行的 CPU 密集型任务放入其中,然后限制它的 CPU 占用。
-
检查 cgroup 文件系统:
通常,cgroup 文件系统已经挂载在/sys/fs/cgroup
。你可以通过mount | grep cgroup
命令确认。你会看到类似这样的输出:[root@localhost ~]# mount | grep cgroup tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755) cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd) cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids) cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu) cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory) # ... 还有其他子系统
这里,
cpu,cpuacct
表示cpu
和cpuacct
这两个子系统被挂载在同一个层级下。我们会用到cpu
子系统来限制 CPU 资源。 -
创建新的控制组:
我们将在cpu
子系统下创建一个名为limited_cpu
的控制组。[root@localhost ~]# cd /sys/fs/cgroup/cpu [root@localhost cpu]# mkdir limited_cpu
现在,
limited_cpu
目录就是我们新的控制组。这个目录下会自动生成一些文件,用于配置 CPU 相关的限制。 -
设置 CPU 限制:
cpu.cfs_period_us
和cpu.cfs_quota_us
这两个文件用于设置 CPU 带宽限制。cpu.cfs_period_us
:CPU 完全轮转的周期,单位是微秒 (us)。默认是 100000 us (100 毫秒)。cpu.cfs_quota_us
:在一个周期内,允许该 cgroup 使用的 CPU 时间,单位是微秒 (us)。
例如,要限制进程组只能使用 50% 的一个 CPU 核心,我们可以设置
quota
为period
的一半。[root@localhost cpu]# echo 50000 > limited_cpu/cpu.cfs_quota_us [root@localhost cpu]# echo 100000 > limited_cpu/cpu.cfs_period_us
这表示在每 100 毫秒的周期里,
limited_cpu
组中的进程只能使用 50 毫秒的 CPU 时间。 -
将进程添加到控制组:
现在,启动一个 CPU 密集型任务,比如一个无限循环的stress
命令(如果你的系统没有stress
命令,可以使用sudo yum install stress
或sudo apt-get install stress
安装)。首先,在后台启动一个
stress
进程,并获取它的 PID:[root@localhost cpu]# stress -c 1 & [1] 12345 # 假设 PID 是 12345
然后,将这个进程的 PID 写入
limited_cpu
目录下的cgroup.procs
文件。这个文件用于将进程添加到当前控制组。[root@localhost cpu]# echo 12345 > limited_cpu/cgroup.procs
-
验证限制效果:
使用top
或htop
命令观察 CPU 使用情况。你会发现 PID 为 12345 的stress
进程的 CPU 使用率被限制在了 50% 左右。如果不设置 cgroup 限制,
stress -c 1
应该会占用一个 CPU 核心的 100% CPU 时间。清理:
任务完成后,你可以将进程从 cgroup 中移除(将其 PID 写入父 cgroup 的cgroup.procs
文件,或者直接杀死进程)。[root@localhost cpu]# kill 12345 [root@localhost cpu]# rmdir limited_cpu # 删除控制组
cgroup 的高级应用
除了 CPU 限制,cgroup 还能做更多:
- 内存限制: 通过
memory.limit_in_bytes
限制内存使用,memory.memsw.limit_in_bytes
限制内存加交换空间的总量。 - 磁盘 I/O 限制: 使用
blkio.throttle.read_bps_device
和blkio.throttle.write_bps_device
限制读写带宽。 - 进程数限制: 通过
pids.max
限制进程组可以fork的子进程数量。
cgroup 是 Linux 内核提供的一个强大且灵活的资源管理工具。理解并掌握它对于进行系统资源优化、构建容器环境以及确保多任务系统稳定运行至关重要。