浅谈 Cgroups 和 systemd

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 命令查看其挂载点

![[Pasted image 20240708220722.png]]

查看一个进程所属的 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_cputest_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;
}

对比运行

  • 无限制运行
    ![[Pasted image 20240709141455.png]]

  • 限制运行
    ![[Pasted image 20240709141533.png]]

我们可以很清楚的观察到 real time 变慢了将近十倍

限制内存使用

新建 Cgroup 组

mkdir /sys/fs/cgroup/memory/test_memory
![[Pasted image 20240709203508.png]]

进行内存限制

限制使用内存为 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 20240709210005.png]]

限制后运行程序

![[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

参考文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值