Cgroups 组成架构
基本概念
- Task:使用 task 来表示系统的一个进程或线程。
- 子系统:cgroups 中的子系统就是一个资源调度控制器(又叫 controllers)
- Cgroup:一个 Cgroup 包括一个或多个子系统,如下所示,包含两个子系统的 Cgroup 两个子系统用逗号隔开构成 Cgroup 名字
[root@localhost cgroup]# ls /sys/fs/cgroup/
blkio cpu cpuacct cpu,cpuacct cpuset devices freezer hugetlb memory net_cls net_cls,net_prio net_prio perf_event pids rdma systemd
- Hierarchy 层级 :我们可以将 Cgroups 想象成为一个森林,上述提到的 Cgroup 为森林中每棵树的根节点,每棵树包括零个或多个子节点,子节点继承父节点挂载的子系统
cgroup 的分层结构使得 Linux 内核能够更有效地管理和控制进程组的资源使用,提供了细粒度的资源管理能力,同时保持了足够的灵活性和扩展性,以适应各种复杂的部署和应用需求。
Cgroups 的接口
Cgroups 是以文件系统的方式提供接口的,我们可以使用 mount
命令查看其挂载点
查看一个进程所属的 Cgroup
cat /proc/1/cgroup
12:devices:/
11:blkio:/
10:memory:/
9:perf_event:/
8:cpu,cpuacct:/
7:hugetlb:/
6:pids:/
5:freezer:/
4:cpuset:/
3:net_cls,net_prio:/
2:rdma:/
1:name=systemd:/init.scope
- 控制器(controller):控制器的名称,表示 cgroup 子系统。
- 路径(path):cgroup 层次结构中的路径,表示该进程所在的 cgroup 位置。
管理 Cgroups
安装工具 libcgroup-tools
使用以下命令安装 Cgroups 管理工具
dnf install libcgroup-tools
限制 CPU
新建 Cgroup
我们在 /sys/fs/cgroup/cpu
文件夹中新建目录 test_cpu
, test_cpu
继承 cpu 子系统,cgroups 的文件系统会在创建文件目录的时候自动创建这些配置文件
![[Pasted image 20240709194851.png]]
echo 100000 > nick_cpu/cpu.cfs_period_us
echo 10000 > nick_cpu/cpu.cfs_quota_us
通过设置cpu.cfs_period_us
为100毫秒和cpu.cfs_quota_us
为10毫秒,这些指令将限制nick_cpu
控制组中的所有进程,在每个100毫秒的调度周期内,最多只能使用10毫秒的CPU时间。这相当于将该控制组的CPU使用率限制在10%(10毫秒/100毫秒)
编写 CPU 密集程序
- CPU密集型程序是指在执行过程中主要消耗CPU资源的程序。这类程序通常执行大量计算操作,需要频繁访问和处理数据。
下面是一个简单的CPU密集型C程序示例,使用该程序来模拟CPU密集型工作负载:
#include<stdio.h>
int main()
{
long long i,end;
end = 1024 * 1024 * 1024;
for(i = 1;i <= end; i++)
{
continue;
}
return 0;
}
对比运行
-
无限制运行
-
限制运行
我们可以很清楚的观察到 real time 变慢了将近十倍
限制内存使用
新建 Cgroup 组
mkdir /sys/fs/cgroup/memory/test_memory
进行内存限制
限制使用内存为 400 MB,并且禁止使用 swap ,不使用 swap 意味着不允许操作系统将内存中的数据交换到磁盘上的swap空间
swappiness
参数的取值范围是 0 到 100,表示内存交换(swap)和物理内存使用之间的平衡:
echo 419430400 > test_memory/memory.limit_in_bytes
echo 0 > test_memory/memory.swappiness
编写内存调用的函数进行验证
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define ALLOCATION_SIZE (100 * 1024 * 1024) // 每次分配100MB
int main() {
int allocation_count = 0;
int i = 8;
while (i--) {
void* mem = malloc(ALLOCATION_SIZE);
if (mem == NULL) {
fprintf(stderr, "Memory allocation failed after %d allocations: %s\n", allocation_count, strerror(errno));
break;
}
memset(mem, 0, ALLOCATION_SIZE); // 将分配的内存初始化为0
allocation_count+=100;
printf("Allocated %d MB\n", allocation_count);
sleep(1); // 每次分配后等待1秒
}
return 0;
}
- 操作系统的内存分配机制可能会使用惰性分配(Lazy Allocation),即在调用
malloc
分配内存时,操作系统并不实际分配物理内存,而仅仅记录需要的内存量,并在第一次访问这块内存时才真正分配。 - 通过使用
memset
将分配的内存初始化为0,可以确保操作系统实际分配这块内存,从而触发可能的内存分配失败。
正常不进行限制运行程序
限制后运行程序
![[Pasted image 20240709205810.png]]
发现在分配内存到 400MB 时候程序被杀死
实际应用中往往要同时限制多种的资源,比如既限制 CPU 资源又限制内存资源。使用 cgexec 实现这样的用例其实很简单,直接指定多个 -g 选项就可以了:
$ cgexec -g cpu:test_cpu -g memory:test_memory ./cpumem
Cgroups 与 systemd
systemd 对服务进程的管理依赖 Cgroups 的特性
我们知道,systemd 开启和监督服务是基于 Unit 概念 ,通过将 cgroup 层级系统与 Unit 绑定,实现服务对系统资源的控制
我们这里要关注三种 Unit:
- Service unit:系统服务
- Scope Unit:不是由 Systemd 启动的外部进程
- Slice Unit:进程组,slice 并不包含进程,但会组建一个层级,并将 scope 和 service 都放置其中。
使用 systemd-cgls
systemd-cgls
命令可以以树状结构显示 cgroup 层次结构及其包含的进程。
![[Snipaste_2024-07-09_21-34-09.png]]
demo 查看特定 cgroup 包含的进程
我们对上文中提到的验证限制内存的 c 程序,进行简单的修改
- 每次分配 10MB 内存
- 直到内存耗尽停止分配
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define ALLOCATION_SIZE (10 * 1024 * 1024) // 每次分配10MB
int main() {
int allocation_count = 0;
int i = 8;
while (1) {
void* mem = malloc(ALLOCATION_SIZE);
if (mem == NULL) {
fprintf(stderr, "Memory allocation failed after %d allocations: %s\n", allocation_count, strerror(errno));
break;
}
memset(mem, 0, ALLOCATION_SIZE); // 将分配的内存初始化为0
allocation_count+=10;
printf("Allocated %d MB\n", allocation_count);
sleep(1); // 每次分配后等待1秒
}
return 0;
}
使用命令 cgexec -g memory:/test_memory ./test_memory
将该程序绑定限制内存的 cgroup 执行
我们再打开一个终端,查看对应 cgroup 中包含的进程,可以发现我们正在执行的程序
![[Pasted image 20240709212318.png]]
创建临时的 Cgroup
我们可以使用命令 systemd-run --unit=testop --slice=test top -b
将进程 top -b
在一个新的 Unit
中运行,并且指定特定的 cgroup slice
![[Pasted image 20240710012328.png]]
查看 service 状态
使用命令 systemctl status test.slice
查看 slice
状态
![[Pasted image 20240710012614.png]]
也可以使用命令 systemctl status testop.service
查看 service
状态
![[Snipaste_2024-07-10_01-27-11.png]]
我们知道该进程的 PID 之后,可以使用 cat /proc/87319/cgroup
查看该进程在哪些 cgroup
下运行
![[Pasted image 20240710013017.png]]
修改资源配置
我们将 CPU 资源分配权重设置为 500,可用内存的上限为 550M:
systemctl set-property testop.service CPUShares=500 MemoryLimit=500M
再次查看 cgroup 信息:
![[Pasted image 20240710013823.png]]
会发现,增加了一些 testop.service
文件,这是因为在 test.slice
中我们对 testop.service
做了更加精细的资源配置,这样做可以不对 test.slice
中其他进程的资源配置产生影响
结束进程
-
systemctl kill testop.service
我们 kill 掉 top 进程,然后再查看/sys/fs/cgroup/memory/test.slice
和/sys/fs/cgroup/cpu/test.slice
目录,刚才的toptest.service
目录已经不见了。 -
systemctl stop test.slice
我们 stop slice 之后 cgoups 中就不会再有该test.slice