02 资源使用听指挥 - cgroup 与资源限制
什么是 cgroup(Control Groups)
cgroup是 Linux 内核中的一种机制,用于将进程划分到不同的组(称为 "控制组"),从而对这些组应用各种资源的限制、监控和隔离。cgroup可以限制和跟踪 CPU、内存、I/O、网络带宽等资源的使用,广泛应用于容器(如 Docker、LXC)和虚拟化环境中,以确保每个应用或服务不会占用过多的系统资源。
cgroup 的基本功能
- 资源限制:可以为进程组设置资源使用的上限,例如限制 CPU 使用率、内存使用量、磁盘 I/O 带宽等。
- 优先级设置:为不同的 cgroup 设置优先级,确保关键任务优先获得资源。
- 资源隔离:将不同进程组的资源进行隔离,互不干扰。
- 资源审计:可以监控 cgroup 的资源使用情况,统计进程组的资源使用情况,例如 CPU 时间、内存使用量等,便于监控和分析,方便管理员跟踪资源消耗。
- 进程控制:允许动态地添加、移除进程到 cgroup 中。
cgroup 的层次结构
cgroup 采用树状结构来组织进程组。每个 cgroup 可以包含多个子 cgroup,形成一个层次结构。每个 cgroup 节点可以设置不同的资源限制和优先级。
- 根 cgroup:位于层次结构的顶端,包含所有进程。
- 子 cgroup:根 cgroup 下的子节点,可以进一步细分资源管理。
cgroup 的子系统
cgroup 是分层结构,每个层级称为一个 subsystem 或 controller。每个子系统管理某一种资源的使用。
cgroup 通过不同的子系统(subsystem)来管理不同的资源类型。
常见的 cgroup 子系统包括:
- CPU 子系统(cpu、cpuacct):控制 CPU 资源的分配。
- 内存子系统(memory):控制内存的使用,包含物理内存和交换内存的限制。
- 块设备 I/O 子系统(blkio):控制块设备(如硬盘)的 I/O 读写带宽。
- 设备访问控制子系统(devices):控制进程是否可以访问某些设备。
- 网络子系统(net_cls, net_prio):控制进程的网络带宽和优先级。
- 挂载命名空间(namespace):隔离系统的名字空间,使每个组的进程感知到的系统环境不同。
cgroup 版本
目前 Linux 内核支持两种 cgroup 版本,分别是 cgroup v1 和 cgroup v2。cgroup v2 是对 cgroup v1 的改进版本,设计更简洁,功能更强大,但一些控制器在 cgroup v2 中进行了简化和调整。
- cgroup v1:每个控制器(如 CPU、内存等)可以独立挂载,并且对每个子系统(controller)都有自己的层级,导致管理较为复杂。
- cgroup v2:所有控制器在一个统一的层次结构中进行管理,简化了管理操作。
cgroup 和 taskset 的区别
- taskset 控制对象是进程的 CPU;cgroup 控制对象可以是 CPU,也可以是内存和 IO;:
- taskset 的作用对象是单个进程;cgroup 作用对象可以是单个进程也可以是进程组;
- cgroup 可能存在层级关系;taskset 的作用对象无层级关系;
- 在某些场景下,taskset 的作用可由 cgroup 替代;
cgroup 的应用场景
- 容器化:Docker 和 Kubernetes 等容器技术广泛使用 cgroup 来隔离和限制容器的资源使用。
- 虚拟化:在虚拟化环境中,cgroup 可以用于限制虚拟机的资源使用。
- 资源调度:在多租户环境中,cgroup 可以用于公平分配资源,防止某个租户占用过多资源。
使用 cgroup v1
在 cgroup v1 中,每个资源控制器有自己独立的挂载点,可以通过在文件系统中写入特定文件来配置资源限制。
创建新的 cgroup:
bashmkdir /sys/fs/cgroup/cpu/my_cgroup # 用于限制 CPU 资源 mkdir /sys/fs/cgroup/memory/my_cgroup # 用于限制内存资源 mkdir /sys/fs/cgroup/blkio/my_cgroup # 用于限制块设备 I/O设置 CPU 限制:
bashecho "50000" | tee /sys/fs/cgroup/cpu/my_cgroup/cpu.cfs_quota_us # 限制为 50% 的 CPU 资源 echo "100000" > /sys/fs/cgroup/cpu/my_cgroup/cpu.cfs_quota_us # 限制为 100% 的 CPU 资源设置内存限制:
bashecho "524288000" > /sys/fs/cgroup/memory/my_cgroup/memory.limit_in_bytes # 限制为 500MB echo "104857600" | tee /sys/fs/cgroup/memory/mygroup/memory.limit_in_bytes # 限制为 100MB限制块设备 I/O 速率:
bashecho "8:0 52428800" | tee /sys/fs/cgroup/blkio/my_cgroup/blkio.throttle.read_bps_device # 限制读 50MB/s echo "8:0 20971520" > /sys/fs/cgroup/blkio/my_cgroup/blkio.throttle.write_bps_device # 限制写 20MB/s8:0是块设备的主次设备号,52428800和20971520分别是读写速率(字节为单位)。
将进程加入到 cgroup:
bashecho <PID> > /sys/fs/cgroup/cpu/my_cgroup/tasks # 将进程加入到 限制 CPU 的 cgroup 中 echo <PID> | sudo tee /sys/fs/cgroup/memory/mygroup/tasks # 将进程加入到 限制内存的 cgroup 中 echo <PID> | sudo tee /sys/fs/cgroup/blkio/my_cgroup/tasks # 将进程加入到 限制块设备 I/O 的 cgroup 中
使用 cgroup v2
- cgroup v2 的配置方式与 cgroup v1 略有不同,所有控制器在同一个层次结构中进行管理。
- 通过将控制器激活在 cgroup v2 中,我们可以一次性管理多个资源。
创建新的 cgroup:
bashmkdir /sys/fs/cgroup/my_cgroup设置资源限制:
bash# 设置 CPU 限制 echo 20000 > /sys/fs/cgroup/my_cgroup/cpu.max # 限制为 20% 的 CPU 资源 # 设置内存限制 echo 524288000 > /sys/fs/cgroup/my_cgroup/memory.max # 限制为 500MB # 限制 I/O 速率 echo "8:0 rbps=52428800 wbps=20971520" > /sys/fs/cgroup/my_cgroup/io.max # 限制读 50MB/s,写 20MB/s将进程加入到该 cgroup:
bashecho <PID> > /sys/fs/cgroup/my_cgroup/cgroup.procs
cgroup 示例
cgroup 是内核提供的资源管理的接口,其位置一般位于 /sys/fs/cgroup 可通过 mount | grep cgroup 进行确定。
➜ ~ mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)限制进程的 CPU 调度
# 进入到 cgroup 文件系统中的 cpu,cpuacct 目录。
# cgroup 是 Linux 提供的一种资源管理机制,这里的 cpu,cpuacct 是用于管理 CPU 资源使用和统计的控制组。
# 通过进入这个目录,可以对系统中进程的 CPU 资源使用进行控制和监控。
cd /sys/fs/cgroup/cpu,cpuacct
# 设置当前 cgroup 的 clone_children 属性为 1。
# clone_children 属性控制是否允许子 cgroup 继承父 cgroup 的资源限制和配置。
# 将此值设置为 1,意味着新创建的子 cgroup 将自动继承父 cgroup 的限制和属性。
# 例如,如果父 cgroup 限制了 CPU 使用,子 cgroup 也会自动受到相同的限制。
echo 1 > cgroup.clone_children
# 创建一个名为 quota_limit 的子目录。
# 该子目录实际上是一个新的 cgroup,这个 cgroup 可以为其包含的进程设置独立的资源限制。
# 创建该目录后,进入这个新的 cgroup 目录,后续操作将在这个 cgroup 下进行。
mkdir quota_limit && cd quota_limit
# 将目标进程的 PID 写入当前 cgroup 的 cgroup.procs 文件。
# cgroup.procs 文件用于将进程加入到当前 cgroup 中,这样该进程将受到当前 cgroup 的资源限制。
# [PID] 是占位符,表示进程的实际 PID(进程标识符)。例如,如果进程的 PID 是 1234,那么应该写入 echo 1234 > cgroup.procs。
echo [PID] > cgroup.procs
# 设置当前 cgroup 的 CPU 使用配额。
# cpu.cfs_quota_us 文件控制该 cgroup 内进程的 CPU 使用时间配额,以微秒为单位。
# 这里设置为 20000 微秒,表示该 cgroup 中的所有进程每 100000 微秒(默认周期)最多只能使用 20000 微秒的 CPU 时间。
# 默认情况下,cpu.cfs_period_us(CFS 调度周期)是 100000 微秒,即 100 毫秒。
# 因此,这样的设置相当于将进程的 CPU 使用限制在 20%(20000 / 100000 = 0.2)。
# 这意味着即使系统有多个 CPU 核心,这个进程也最多只能使用总 CPU 资源的 20%。
echo "20000" > cpu.cfs_quota_us # 此操作为设置进程最多使用20%的CPU资源,可通过 top -H -p [PID] 进行验证
# top -H 命令显示各线程的资源使用情况,而 -p 选项允许指定某个进程。cpu/cpuacct 目录文件
/sys/fs/cgroup/cpu,cpuacct 目录下文件:
| 文件名 | 描述 |
|---|---|
cgroup.clone_children | 控制子 cgroup 是否继承父 cgroup 的设置 |
cgroup.event_control | 事件通知机制的配置文件,通常用于通知 cgroup 中发生的事件 |
cgroup.procs | 包含属于该 cgroup 的进程的 PID 列表 |
cgroup.sane_behavior | 控制 cgroup 的行为是否遵循更为严格的规则 |
cpuacct.stat | CPU 统计信息,包含进程在用户态和内核态下的 CPU 时间使用 |
cpuacct.usage | 该 cgroup 中所有进程累计使用的 CPU 时间(以纳秒 ns 为单位) |
cpuacct.usage_percpu | 每个 CPU 核心上进程的累计使用时间(以纳秒 ns 为单位) |
cpu.cfs_period_us | 完全公平调度器 (CFS) 的调度周期,控制时间片长度(以微秒 μs 为单位)。和 cpu.cfs_quota_us 配合使用 |
cpu.cfs_quota_us | CFS 可以使用的 CPU 时间配额(以微秒 μs 为单位),用于限制 CPU 使用。cpu.cfs_quota_us/cpu.cfs_period_us 即为允许使用 CPU 上限。 |
cpu.rt_period_us | 实时调度器的调度周期(以微秒 μs 为单位)。和 cpu.rt_runtime_us 配合使用 |
cpu.rt_runtime_us | 实时调度器中允许的最大 CPU 运行时间(以微秒 μs 为单位),默认值为 950000,即 1s 内 RT 进程最多运行 0.95s。 |
cpu.shares | CPU 共享权重,决定该 cgroup 相对于其他 cgroup 的 CPU 资源分配比例,默认值为 1024,若为 2048,则表示该 cgroup 的 CPU 资源分配是其他 cgroup 的 2 倍(相比同级进程多 1 倍运行时间)。 |
cpu.stat | 包含关于任务调度和 CPU 使用的统计信息。 |
cgroup.clone_children- 描述: 控制子 cgroup 是否继承父 cgroup 的资源限制和属性。
- 默认值: 0,表示子 cgroup 不继承父 cgroup 的设置。
- 用途: 设置为 1 后,当在当前 cgroup 目录下创建子 cgroup 时,子 cgroup 会继承父 cgroup 的资源限制。
- 操作:
echo 1 > cgroup.clone_children: 设置子 cgroup 继承父 cgroup 的参数配置,这样在创建子 cgroup 时,无需再次手动配置相同的资源限制。
cgroup.event_control- 描述: 用于 cgroup 事件通知的配置,通常用于监控和响应 cgroup 中的资源使用事件。
- 用途: 配置特定事件的通知机制,但在常规资源管理中较少直接操作。
cgroup.procs- 描述: 包含属于当前 cgroup 的所有进程的 PID 列表。
- 用途: 将进程的 PID 写入此文件,即可将该进程分配到当前 cgroup,并受到相应的资源限制。
- 操作:
echo [PID] > cgroup.procs将[PID]替换为实际进程的 ID,以对该进程进行资源限制或管理。
cgroup.sane_behavior- 描述: 控制 cgroup 的行为是否遵循更为严格的规则,通常用于确保资源管理更加合理和一致。
- 用途: 对于一些较新的内核版本和配置可能会使用到,但默认不一定启用。
cpuacct.stat- 描述: 提供当前 cgroup 中所有进程的 CPU 使用统计信息,包括用户态和内核态的 CPU 时间。
- 用途: 监控 CPU 使用情况,以了解该 cgroup 中进程的 CPU 时间消耗。
cpuacct.usage- 描述: 显示当前 cgroup 中所有进程累计使用的 CPU 时间,以纳秒为单位。
- 用途: 用于监控该 cgroup 总体的 CPU 使用量。
cpuacct.usage_percpu- 描述: 显示该 cgroup 中所有进程在每个 CPU 核心上累计使用的 CPU 时间,以纳秒为单位。
- 用途: 分析和监控不同 CPU 核心的使用情况,帮助在多核系统上进行负载平衡或调优。
cpu.cfs_period_us:- 这个文件表示的是完全公平调度器 (CFS, Completely Fair Scheduler) 的调度周期(以微秒为单位)。它控制了一个时间片的长度。
- 默认值通常为 100000 微秒 (即 100 毫秒)。
cpu.cfs_quota_us:- 这个文件表示的是 CFS 可以使用的 CPU 时间配额(以微秒为单位)。当进程组消耗的 CPU 时间达到这个配额时,CFS 会将它的调度推迟到下一个调度周期。
- 通过设置这个值,你可以限制进程组的 CPU 使用率。
cpu.rt_period_us:- 这个文件定义了实时调度器的调度周期(以微秒为单位)。实时进程使用的是 RT (Real-Time) 调度策略。
- 默认值通常为 1000000 微秒(即 1 秒)。
cfs_quota_us/cfs_period_us即为允许使用 CPU 上限:通过cpu.cfs_quota_us与cpu.cfs_period_us的比值,可以计算出进程组在某个时间段内允许使用的 CPU 的百分比。- 例如,
cfs_quota_us设置为 50000,cfs_period_us设置为 100000,表示该进程组最多能使用 50% 的 CPU 时间。
cpu.rt_runtime_us:- 这个文件表示在每个
rt_period_us内,实时进程最多可以运行的时间(以微秒为单位)。 - 默认值通常为 950000 微秒,这意味着在 1 秒的周期内,实时进程最多可以运行 0.95 秒。
- 这个文件表示在每个
cpu.shares:- 这个文件表示相对 CPU 共享权重。默认值通常为 1024。
- 权重值越高,进程组可以获得的 CPU 资源相对于其他进程组越多。如果一个进程组的权重设置为 2048,而另一个是 1024,那么前者相对于后者将获得两倍的 CPU 时间。
cpu.stat:- 这个文件包含一些关于 CPU 使用的统计信息,比如任务的调度时间和等待时间等。
限制进程的 CPU 核心
cd /sys/fs/cgroup/cpuset
# 切换到 cpuset 子系统的 cgroup 控制目录。
# cpuset 是一种 cgroup 子系统,用于控制进程在特定 CPU 核心上运行。
echo 1 > cgroup.clone_children
# 将 cgroup.clone_children 设置为 1。
# 启用子 cgroup 的设置继承,即当创建新的子 cgroup 时,
# 子 cgroup 会自动继承父 cgroup 的 CPU 集合和内存节点设置。
mkdir sched_limit && cd sched_limit
# 创建一个新的 cgroup 目录 sched_limit,并切换到该目录。
# 这个新目录将作为一个独立的 cgroup,允许我们对其中的进程应用特定的资源限制。
echo [PID] > cgroup.procs
# 将指定的进程 ID (PID) 添加到当前 cgroup (sched_limit) 中。
# 这将把该进程移动到 sched_limit cgroup 中,使其受这个 cgroup 的资源限制影响。
# 请将 [PID] 替换为实际的进程 ID。
cat cpuset.cpus
# 显示当前 sched_limit cgroup 中允许进程运行的 CPU 核心列表。
# cpuset.cpus 文件包含允许该 cgroup 中进程运行的 CPU 核心列表。
echo 1 > cpuset.cpus
# 将 sched_limit cgroup 的 cpuset.cpus 文件设置为 1。
# 这意味着该 cgroup 中的所有进程将被限制在 CPU 1 核心上运行。
# 此时,可以使用下面的命令验证进程的 CPU 核心限制:
# top -H -p [PID]
# 通过 top 命令查看指定进程的所有线程(-H 选项),
# 并验证这些线程是否只在 CPU 1 上运行。cgroup 与 taskset 对比
在简单的单进程情境下,cgroup 和 taskset 的效果相似。然而,当涉及到多进程或多 cgroup 时,cgroup 的组调度机制使得它能够提供更复杂的 CPU 核心和资源控制,可能导致与 taskset 不同的行为。这种差异主要来源于进程调度和组调度的原理。
taskset:是一个用户空间工具,用于设置或获取进程的 CPU 亲和性。通过taskset,可以将特定进程绑定到某些指定的 CPU 核心上。例如,taskset -c 0x2 [PID]将进程绑定到 CPU 1 上(假设 CPU 核心从 0 开始编号)。cgroup:cpuset子系统通过控制组(cgroup)来实现更复杂的 CPU 亲和性设置,可以控制一个或多个进程在指定 CPU 核心和内存节点上的运行。此外,它还提供了更细粒度的资源控制,比如限制一组进程只能使用指定的 CPU 核心。
- 若仅存在单个 cgroup 文件夹且仅有单个进程,则其效果与
taskset -cp 0x2 [PID]一致 - 若存在多个 cgroup 文件夹或文件夹中存在多个进程,则可能与
taskset的效果不同,相关原理涉及- 进程调度
- 组调度
当只有单个 cgroup 文件夹且仅有一个进程时
- 在这种情况下,
cgroup中设置的 CPU 亲和性实际上只影响到该单一进程,因此其效果与使用taskset的效果相同。无论是通过taskset绑定 CPU 亲和性,还是通过cgroup设置,结果都是该进程只能在指定的 CPU 核心上运行。
当存在多个 cgroup 文件夹或单个 cgroup 中存在多个进程时
进程调度(Process Scheduling):
- 在传统的进程调度中,如果不使用
cgroup,Linux 内核会根据优先级、负载等因素动态地在多个 CPU 核心之间分配进程。如果使用taskset,单个进程的亲和性被设置后,它会被限制在指定的 CPU 核心上。 - 但是,使用
cgroup可以对一组进程进行调度控制。如果一个 cgroup 中存在多个进程,那么这些进程会受到cgroup设置的 CPU 限制,并且内核会根据组内的调度策略(例如公平调度)来决定这些进程如何在指定的 CPU 核心上运行。
- 在传统的进程调度中,如果不使用
组调度(Group Scheduling):
- 当多个 cgroup 文件夹存在时,每个 cgroup 可以独立地设置自己的 CPU 亲和性。这使得不同的 cgroup 中的进程可以被限制在不同的 CPU 核心上运行,从而实现更复杂的资源隔离和控制。
- 如果一个 cgroup 中有多个进程,那么这些进程在被调度到指定的 CPU 核心时,会按照组调度策略进行竞争。在这种情况下,即使不同的进程被限制在同一组 CPU 核心上,它们的调度行为可能会有所不同,具体取决于内核的调度算法(如 CFS - 完全公平调度器)。
关键差异点
- 单进程 vs 多进程:当只有一个进程时,
taskset和cgroup的效果一致。但当有多个进程时,cgroup提供了对多个进程的更复杂的调度控制,并且可以在多个 CPU 核心之间分配进程组的资源。 - cgroup 的组调度特性:
cgroup不仅影响单个进程,还影响一组进程,这使得它能够在资源管理方面提供比taskset更高级的功能,特别是在需要对进程组进行资源隔离的场景中。