带你了解linux cgroups

一、什么是cgroups

cgroups(Control Groups)是linux内核提供的一种机制,它可以根据需求把一系列的系统任务整合或分隔到资源等级不同的组内,从而为系统提供一个统一的框架。通过cgroups,我们可以很方便的限制某个容器可以使用的CPU、系统内存等资源。

二、为什么要引用cgroups

Linux系统每个进程都会竞争系统资源,这样就会导致一些次要的进程占用了系统的大部分资源,从而影响系统效率,也可能导致资源耗尽的时候引起查杀到主进程,因此linux引入了linux cgroups来控制进程资源,让进程更可控。

Docker 在实现不同的 Container 之间资源隔离和控制的时候,是可以创建比较复杂的 cgroups 节点和配置文件来完成的。然后对于同一个 Container 中的进程,可以把这些进程 PID 添加到同一组 cgroups 子节点中已达到对这些进程进行同样的资源限制。

三、cgroups 的主要作用

实现 cgroups 的主要目的是为不同用户层面的资源管理提供一个统一化的接口。从单个任务的资源控制到操作系统层面的虚拟化,cgroups 提供了四大功能:


资源限制:cgroups 可以对任务是要的资源总额进行限制。比如设定任务运行时使用的内存上限,一旦超出就发 OOM。
优先级分配:通过分配的 CPU 时间片数量和磁盘 IO 带宽,实际上就等同于控制了任务运行的优先级。
资源统计:cgoups 可以统计系统的资源使用量,比如 CPU 使用时长、内存用量等。这个功能非常适合当前云端产品按使用量计费的方式。
任务控制:cgroups 可以对任务执行挂起、恢复等操作。

四、相关概念介绍

任务(task): 在cgroup中,使用 task 来表示系统的一个进程或线程。
控制组(control group): cgroups 中的资源控制以 cgroup 为单位实现。Cgroup 表示按某种资源控制标准划分而成的任务组,包含一个或多个子系统。一个任务可以加入某个 cgroup,也可以从某个 cgroup 迁移到另一个 cgroup。
层级(hierarchy): 控制组有层级关系,类似树的结构,子节点的控制组继承父控制组的属性(资源配额、限制等)。
子系统(subsystem): 一个子系统其实就是一种资源的控制器,比如memory子系统可以控制进程内存的使用。子系统需要加入到某个层级,然后该层级的所有控制组,均受到这个子系统的控制。

五、子系统

  • cpu: 限制进程的 cpu 使用率。
  • cpuacct 子系统,可以统计 cgroups 中的进程的 cpu 使用报告。
  • cpuset: 为cgroups中的进程分配单独的cpu节点或者内存节点。
  • memory: 限制进程的memory使用量。
  • blkio: 限制进程的块设备io。
  • devices: 控制进程能够访问某些设备。
  • net_cls: 标记cgroups中进程的网络数据包,然后可以使用tc模块(traffic control)对数据包进行控制。
  • net_prio: 限制进程网络流量的优先级。
  • huge_tlb: 限制HugeTLB的使用。
  • freezer:挂起或者恢复cgroups中的进程。
  • ns: 控制cgroups中的进程使用不同的namespace。

5.1 cpu子系统

cpu子系统限制对CPU的访问,每个参数独立存在于cgroups虚拟文件系统的伪文件中,参数解释如下:

  • cpu.shares: cgroup对时间的分配。比如cgroup A设置的是1,cgroup B设置的是2,那么B中的任务获取cpu的时间,是A中任务的2倍。
  • cpu.cfs_period_us: 完全公平调度器的调整时间配额的周期。
  • cpu.cfs_quota_us: 完全公平调度器的周期当中可以占用的时间。
  • cpu.stat 统计值
    • nr_periods 进入周期的次数
    • nr_throttled 运行时间被调整的次数
    • throttled_time 用于调整的时间

5.2 cpuacct子系统

子系统生成cgroup任务所使用的CPU资源报告,不做资源限制功能。

  • cpuacct.usage: 该cgroup中所有任务总共使用的CPU时间(ns纳秒)
  • cpuacct.stat: 该cgroup中所有任务总共使用的CPU时间,区分user和system时间。
  • cpuacct.usage_percpu: 该cgroup中所有任务使用各个CPU核数的时间。

5.3 cpuset子系统

适用于分配独立的CPU节点和Mem节点,比如将进程绑定在指定的CPU或者内存节点上运行,各参数解释如下:

  • cpuset.cpus: 可以使用的cpu节点
  • cpuset.mems: 可以使用的mem节点
  • cpuset.memory_migrate: 内存节点改变是否要迁移?
  • cpuset.cpu_exclusive: 此cgroup里的任务是否独享cpu?
  • cpuset.mem_exclusive: 此cgroup里的任务是否独享mem节点?
  • cpuset.mem_hardwall: 限制内核内存分配的节点(mems是用户态的分配)
  • cpuset.memory_pressure: 计算换页的压力。
  • cpuset.memory_spread_page: 将page cache分配到各个节点中,而不是当前内存节点。
  • cpuset.memory_spread_slab: 将slab对象(inode和dentry)分散到节点中。
  • cpuset.sched_load_balance: 打开cpu set中的cpu的负载均衡。
  • cpuset.sched_relax_domain_level: the searching range when migrating tasks
  • cpuset.memory_pressure_enabled: 是否需要计算 memory_pressure?

5.4 memory子系统

memory子系统主要涉及内存一些的限制和操作,主要有以下参数:

  • memory.usage_in_bytes # 当前内存中的使用量
  • memory.memsw.usage_in_bytes # 当前内存和交换空间中的使用量
  • memory.limit_in_bytes # 设置or查看内存使用量
  • memory.memsw.limit_in_bytes # 设置or查看 内存加交换空间使用量
  • memory.failcnt # 查看内存使用量被限制的次数
  • memory.memsw.failcnt # - 查看内存和交换空间使用量被限制的次数
  • memory.max_usage_in_bytes # 查看内存最大使用量
  • memory.memsw.max_usage_in_bytes # 查看最大内存和交换空间使用量
  • memory.soft_limit_in_bytes # 设置or查看内存的soft limit
  • memory.stat # 统计信息
  • memory.use_hierarchy # 设置or查看层级统计的功能
  • memory.force_empty # 触发强制page回收
  • memory.pressure_level # 设置内存压力通知
  • memory.swappiness # 设置or查看vmscan swappiness 参数
  • memory.move_charge_at_immigrate # 设置or查看 controls of moving charges?
  • memory.oom_control # 设置or查看内存超限控制信息(OOM killer)
  • memory.numa_stat # 每个numa节点的内存使用数量
  • memory.kmem.limit_in_bytes # 设置or查看 内核内存限制的硬限
  • memory.kmem.usage_in_bytes # 读取当前内核内存的分配
  • memory.kmem.failcnt # 读取当前内核内存分配受限的次数
  • memory.kmem.max_usage_in_bytes # 读取最大内核内存使用量
  • memory.kmem.tcp.limit_in_bytes # 设置tcp 缓存内存的hard limit
  • memory.kmem.tcp.usage_in_bytes # 读取tcp 缓存内存的使用量
  • memory.kmem.tcp.failcnt # tcp 缓存内存分配的受限次数
  • memory.kmem.tcp.max_usage_in_bytes # tcp 缓存内存的最大使用量

5.5 blkio子系统 - block io

主要用于控制设备IO的访问。有两种限制方式:权重和上限,权重是给不同的应用一个权重值,按百分比使用IO资源,上限是控制应用读写速率的最大值。

按权重分配IO资源:

  • blkio.weight:填写 100-1000 的一个整数值,作为相对权重比率,作为通用的设备分配比。
  • blkio.weight_device: 针对特定设备的权重比,写入格式为 device_types:node_numbers weight,空格前的参数段指定设备,weight参数与blkio.weight相同并覆盖原有的通用分配比。

按上限限制读写速度:

  • blkio.throttle.read_bps_device:按每秒读取块设备的数据量设定上限,格式device_types:node_numbers bytes_per_second。
  • blkio.throttle.write_bps_device:按每秒写入块设备的数据量设定上限,格式device_types:node_numbers bytes_per_second。
  • blkio.throttle.read_iops_device:按每秒读操作次数设定上限,格式device_types:node_numbers operations_per_second。
  • blkio.throttle.write_iops_device:按每秒写操作次数设定上限,格式device_types:node_numbers operations_per_second

针对特定操作 (read, write, sync, 或 async) 设定读写速度上限

  • blkio.throttle.io_serviced:针对特定操作按每秒操作次数设定上限,格式device_types:node_numbers operation operations_per_second
  • blkio.throttle.io_service_bytes:针对特定操作按每秒数据量设定上限,格式device_types:node_numbers operation bytes_per_second

六、Cgroups相关查询命令

查看 cgroups 默认的挂载点:mount | grep cgroup

查看各个子系统的根目录:ls /sys/fs/cgroup

查看指定进程属于哪些 cgroup:/proc/[pid]/cgroup 

创建cgroup:mkdir cgroup/hy_cpu_mem/cgroup1

设置cgroup参数:sudo echo 100000 > cpu.cfs_period_us

移动task(进程)只要把对应的进程PID加入到新cgroup的task中即可,如:echo 30167 > newcgroup/tasks
 

七、 cgroups的安装和使用

以 ubuntu 14.04 为例,安装 cgroup:

$ apt-get install cgroup-bin cgroup-lite libcgroup1

安装完成后,cgroup 默认挂载在 /sys/fs/cgroup 上,该目录下共有 11 个 subsystem,关于它们的介绍请见官网文档,更为详细的介绍请见 redhat resource management guide,本文将用到 blkio, cpu, memory 和 net_cls 这四个 subsystem。

$ ls /sys/fs/cgroup/
blkio  cpu  cpuacct  cpuset  devices  freezer  hugetlb  memory  net_cls
net_prio  perf_event  systemd

7.1 限制CPU Time

void main(){
    unsigned int i, end;

    end = 1024 * 1024 * 1024;
    for(i = 0; i < end; )
        i ++;
}

未限制 CPU 使用率前,上述代码的执行时间为:

$ time ./a.out

real	0m3.317s
user	0m3.312s
sys  	0m0.000s

我们在 /sys/fs/cgroup 下新建一个名为 cpu_limit 的 cgroup,并设置该 cgroup 下的进程只能占用单个 CPU 10% 的使用率。

# cfs_period_us 表示 CPU 总时间片段,cfs_quota_us 表示分配给该 cgroup 的时间片段。
# 10000/100000 = 10%

$ mkdir /sys/fs/cgroup/cpu_limit
$ echo 100000 > /sys/fs/cgroup/cpu_limit/cpu.cfs_period_us
$ echo 10000 > /sys/fs/cgroup/cpu_limit/cpu.cfs_quota_us

限制后上述代码的执行时间如下,约为前者的 10 倍:

$ time cgexec -g cpu:cpu_limit  ./a.out

real	0m31.904s
user	0m3.192s
sys 	0m0.000s

某个时间点 top 的输出为:

$ top
......
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
28280 root      20   0    4196    668    592 R  10.0  0.0   0:01.28 a.out

7.2 限制Memory

首先在 /sys/fs/cgroup/memory 下新建一个名为 limit_memory 的 cgroup:

$ mkdir /sys/fs/cgroup/memory/limit_memory

限制该 cgroup 的内存使用量为 300 MB:

# 物理内存 + SWAP <= 300 MB
$ echo 314572800 > /sys/fs/cgroup/memory/limit_memory/memory.limit_in_bytes
$ echo 0 > /sys/fs/cgroup/memory/limit_memory/memory.swappiness

下面是测试代码,它分十次申请内存,每次申请 100 MB:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define CHUNK_SIZE 1024 * 1024 * 100

void main(){
    char *p;
    int i;

    for(i = 0; i < 10; i ++)
    {
        p = malloc(sizeof(char) * CHUNK_SIZE);
        if(p == NULL){
            printf("fail to malloc!");
            return ;
        }
        memset(p, 0, CHUNK_SIZE);
        printf("malloc memory %d MB\n", (i + 1) * 100);
    }
}

执行结果如下,当进程占用的内存超过限制时,将被 kill。

$ cgexec -g memory:limit_memory ./a.out
malloc memory 100 MB
malloc memory 200 MB
Killed

7.3 限制 Block IO

我们采用 blkio 限制进程访问块设备的速率,以磁盘为例,未限制前,其读的带宽为:

$ dd if=in.file of=/dev/null count=1000 bs=1M
1000+0 records in
1000+0 records out
1048576000 bytes (1.0 GB) copied, 1.4419 s, 727 MB/s

采用以下方式配置 cgroup,限制磁盘的读取速率为 10MB/s

# 获取所读文件所在的磁盘编号,本文的编号为 252:0
$ df  -m
Filesystem                  1M-blocks  Used Available Use% Mounted on
/dev/mapper/ubuntu--vg-root     17755  6288     10543  38% /
...
$ ls -l /dev/mapper/ubuntu--vg-root
lrwxrwxrwx 1 root root 7 Jul 10 21:20 /dev/mapper/ubuntu--vg-root -> ../dm-0
$ ls -l /dev/dm-0
brw-rw---- 1 root disk 252, 0 Jul 10 21:20 /dev/dm-0

# 在 /sys/fs/cgroup 目录下新建一个 cgroup,名为 limit_blkio
$ mkdir /sys/fs/cgroup/limit_blkio

# 设置读速率为 10MB/s,其中 252:0 表示所读文件在的磁盘
$ echo "252:0 10485760" > /sys/fs/cgroup/blkio/limit_blkio/blkio.throttle.read_bps_device

再次执行 dd,其平均读速率为 10.5MB/s。

# 清楚内存的缓存数据
$ echo 3 > /proc/sys/vm/drop_caches

$ cgexec -g blkio:limit_blkio dd if=in.file of=/dev/null count=1000 bs=1M
1000+0 records in
1000+0 records out
1048576000 bytes (1.0 GB) copied, 100.03 s, 10.5 MB/s

其中某个时刻 iotop 的输出如下:

Total DISK READ :       9.80 M/s | Total DISK WRITE :       0.00 B/s
Actual DISK READ:       9.80 M/s | Actual DISK WRITE:       0.00 B/s
  TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
19987 be/4 root        9.80 M/s    0.00 B/s  0.00 % 96.99 % dd if=in.file of=/dev/null count=1000 bs=1M

test_limit 目录下有多个 blkio 相关的文件,较为常用的是以下四个:

  • blkio.throttle.read_bps_device:读取块设备的带宽
  • blkio.throttle.read_iops_device:读取块设备的 IOPS
  • blkio.throttle.write_bps_device:写块设备的带宽
  • blkio.throttle.write_iops_device:写块设备的 IOPS

7.4 限制 Network IO

未限速时,采用 scp 测试的网络速度为:

$ scp test.file [email protected]:~/
in.file                                            100% 1000MB  71.4MB/s   00:14

我们用 net_cls 标记某个 cgroup 下的包,借助 tc 来限制被标记的包的量,从而限制网络带宽:

$ mkdir /sys/fs/cgroup/net_cls/net_limit
$ echo 0x001000001 > net_cls.classid

# 采用 tc 限制 classid 为 10:1 网络带宽为 40Mbit/s
$ tc qdisc add dev eth0 root handle 10: htb
$ tc class add dev eth0 parent 10: classid 10:1 htb rate 40mbit
$ tc filter add dev eth0 parent 10: protocol ip prio 10 handle 1: cgroup

限速后,采用 scp 测试的网络速度为 3.6 MB/s,注意到 3.6 MB/s 和 40 Mbit/s(5MB/s) 有较大差距,而 IP 和 TCP 头部额外的开销(共 40 字节头部,每个包的平均大小为 1448 字节)不可能造成如此大的差距,所以本人也、对此深感疑惑,但未能查明原因。

$ scp test.file [email protected]:~/
in.file                                         100% 1000MB   3.6MB/s   04:39

浅谈Linux Cgroups机制 - 知乎 (zhihu.com)

Linux Control Group 简介 (wsfdl.com)

linux cgroups 简介 - sparkdev - 博客园 (cnblogs.com)

Linux资源管理之cgroups简介 - 美团技术团队 (meituan.com)

Linux cgroup详解(理论与实例)_config_blk_cgroup_sampson MrLiang的博客-CSDN博客

猜你喜欢

转载自blog.csdn.net/weixin_47465999/article/details/130454716