• Linux Cgroups


    Linux CGroup全称Linux Control Group, 是Linux内核的一个功能,用来限制,控制与分离一个进程组群的资源(如CPU、内存、磁盘输入输出等)。这个项目最早是由Google的工程师在2006年发起(主要是Paul Menage和Rohit Seth),最早的名称为进程容器(process containers)。在2007年时,因为在Linux内核中,容器(container)这个名词太过广泛,为避免混乱,被重命名为cgroup,并且被合并到2.6.24版的内核中去。然后,其它开始了他的发展。

    Linux CGroupCgroup 可​​​让​​​您​​​为​​​系​​​统​​​中​​​所​​​运​​​行​​​任​​​务​​​(进​​​程​​​)的​​​用​​​户​​​定​​​义​​​组​​​群​​​分​​​配​​​资​​​源​​​ — 比​​​如​​​ CPU 时​​​间​​​、​​​系​​​统​​​内​​​存​​​、​​​网​​​络​​​带​​​宽​​​或​​​者​​​这​​​些​​​资​​​源​​​的​​​组​​​合​​​。​​​您​​​可​​​以​​​监​​​控​​​您​​​配​​​置​​​的​​​ cgroup,拒​​​绝​​​ cgroup 访​​​问​​​某​​​些​​​资​​​源​​​,甚​​​至​​​在​​​运​​​行​​​的​​​系​​​统​​​中​​​动​​​态​​​配​​​置​​​您​​​的​​​ cgroup。

    主要提供了如下功能:

    ● Resource limitation: 限制资源使用,比如内存使用上限以及文件系统的缓存限制。

    ● Prioritization: 优先级控制,比如:CPU利用和磁盘IO吞吐。

    ● Accounting: 一些审计或一些统计,主要目的是为了计费。

    ● Control: 挂起进程,恢复执行进程。

    使​​​用​​​ cgroup,系​​​统​​​管​​​理​​​员​​​可​​​更​​​具​​​体​​​地​​​控​​​制​​​对​​​系​​​统​​​资​​​源​​​的​​​分​​​配​​​、​​​优​​​先​​​顺​​​序​​​、​​​拒​​​绝​​​、​​​管​​​理​​​和​​​监​​​控​​​。​​​可​​​更​​​好​​​地​​​根​​​据​​​任​​​务​​​和​​​用​​​户​​​分​​​配​​​硬​​​件​​​资​​​源​​​,提​​​高​​​总​​​体​​​效​​​率​​​。

    在实践中,系统管理员一般会利用CGroup做下面这些事(有点像为某个虚拟机分配资源似的):

    ● 隔离一个进程集合(比如:nginx的所有进程),并限制他们所消费的资源,比如绑定CPU的核。

    ● 为这组进程 分配其足够使用的内存

    ● 为这组进程分配相应的网络带宽和磁盘存储限制

    ● 限制访问某些设备(通过设置设备的白名单)

    那么CGroup是怎么干的呢?我们先来点感性认识吧。

    首先,Linux把CGroup这个事实现成了一个file system,你可以mount。在我的CentOS7.9下,你输入以下命令你就可以看到cgroup已为你mount好了。

    1. [root@sentry ~]# mount -t cgroup
    2. cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
    3. cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuacct,cpu)
    4. cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)
    5. cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)
    6. cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_prio,net_cls)
    7. cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)
    8. cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)
    9. cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)
    10. cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio)
    11. cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)
    12. cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)

    或者使用lssubsys命令:

    1. [root@sentry ~]# lssubsys -m
    2. cpuset /sys/fs/cgroup/cpuset
    3. cpu,cpuacct /sys/fs/cgroup/cpu,cpuacct
    4. memory /sys/fs/cgroup/memory
    5. devices /sys/fs/cgroup/devices
    6. freezer /sys/fs/cgroup/freezer
    7. net_cls,net_prio /sys/fs/cgroup/net_cls,net_prio
    8. blkio /sys/fs/cgroup/blkio
    9. perf_event /sys/fs/cgroup/perf_event
    10. hugetlb /sys/fs/cgroup/hugetlb
    11. pids /sys/fs/cgroup/pids

    我们可以看到,在/sys/fs下有一个cgroup的目录,这个目录下还有很多子目录,比如: cpu,cpuset,memory,blkio……这些,这些都是cgroup的子系统。分别用于干不同的事的。

    你可以到/sys/fs/cgroup的各个子目录下去make个dir,你会发现,一旦你创建了一个子目录,这个子目录里又有很多文件了。

    1. [root@sentry cpu]# mkdir limit
    2. [root@sentry cpu]# cd limit/
    3. [root@sentry limit]# ll
    4. 总用量 0
    5. -rw-r--r--. 1 root root 0 76 18:38 cgroup.clone_children
    6. --w--w--w-. 1 root root 0 76 18:38 cgroup.event_control
    7. -rw-r--r--. 1 root root 0 76 18:38 cgroup.procs
    8. -rw-r--r--. 1 root root 0 76 18:38 cpuset.cpu_exclusive
    9. -rw-r--r--. 1 root root 0 76 18:38 cpuset.cpus
    10. -r--r--r--. 1 root root 0 76 18:38 cpuset.effective_cpus
    11. -r--r--r--. 1 root root 0 76 18:38 cpuset.effective_mems
    12. -rw-r--r--. 1 root root 0 76 18:38 cpuset.mem_exclusive
    13. -rw-r--r--. 1 root root 0 76 18:38 cpuset.mem_hardwall
    14. -rw-r--r--. 1 root root 0 76 18:38 cpuset.memory_migrate
    15. -r--r--r--. 1 root root 0 76 18:38 cpuset.memory_pressure
    16. -rw-r--r--. 1 root root 0 76 18:38 cpuset.memory_spread_page
    17. -rw-r--r--. 1 root root 0 76 18:38 cpuset.memory_spread_slab
    18. -rw-r--r--. 1 root root 0 76 18:38 cpuset.mems
    19. -rw-r--r--. 1 root root 0 76 18:38 cpuset.sched_load_balance
    20. -rw-r--r--. 1 root root 0 76 18:38 cpuset.sched_relax_domain_level
    21. -rw-r--r--. 1 root root 0 76 18:38 notify_on_release
    22. -rw-r--r--. 1 root root 0 76 18:38 tasks

    CPU 限制

    假设,我们有一个非常吃CPU的程序,叫cpu_limit,其源码如下:

    1. #include
    2. int main(void)
    3. {
    4. int i = 0;
    5. for(;;) i++;
    6. return 0;
    7. }

    执行起来后,毫无疑问,CPU被干到了100%(下面是top命令的输出)

    gcc cpu_limit.c -Wall -o cpu_limit && ./cpu_limit
    
    
    1. PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
    2. 31344 root 20 0 4212 352 280 R 100.0 0.0 0:15.82 deadloop

    然后,我们这前不是在/sys/fs/cgroup/cpu下创建了一个limit的group。我们先设置一下这个group的cpu利用的限制:

    1. [root@sentry]# cat /sys/fs/cgroup/cpu/limit/cpu.cfs_quota_us
    2. -1
    3. [root@sentry limit]# echo 20000 > /sys/fs/cgroup/cpu/limit/cpu.cfs_quota_us

    我们看到,这个进程的PID是31344,我们把这个进程加到这个cgroup中:

    1. # echo 31344 >> /sys/fs/cgroup/cpu/limit/tasks

    然后,就会在top中看到CPU的利用立马下降成20%了。(前面我们设置的20000就是20%的意思)

    1. PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
    2. 31344 root 20 0 4212 352 280 R 20.3 0.0 3:38.55 deadloop

    内存使用限制

    我们再来看一个限制内存的例子(下面的代码是个死循环,其它不断的分配内存,每次512个字节,每次休息一秒):

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. #include <sys/types.h>
    5. #include <unistd.h>
    6. int main(void)
    7. {
    8. int size = 0;
    9. int chunk_size = 1024*1024;
    10. void *p = NULL;
    11. while(1) {
    12. if ((p = realloc(p, chunk_size)) == NULL) {
    13. printf("out of memory!!\n");
    14. break;
    15. }
    16. memset(p, 1, chunk_size);
    17. size += chunk_size;
    18. printf("[%d] - memory is allocated [%8d] bytes \n", getpid(), size);
    19. sleep(1);
    20. }
    21. return 0;
    22. }

    编译并执行程序

    gcc mem_limit.c -Wall -o mem_limit && ./mem_limit
    
    

    然后,在我们另外一边:

    1. # 创建memory cgroup
    2. $ mkdir /sys/fs/cgroup/memory/limit
    3. $ echo 64k > /sys/fs/cgroup/memory/limit/memory.limit_in_bytes
    4. # 禁用swap
    5. echo 0 > memory.swappiness
    6. # 把上面的进程的pid加入这个cgroup
    7. $ echo [pid] > /sys/fs/cgroup/memory/limit/tasks

    你会看到,一会上面的进程就会因为内存问题被kill掉了。

    1. [26991] - memory is allocated [ 70144] bytes
    2. [26991] - memory is allocated [ 70656] bytes
    3. [26991] - memory is allocated [ 71168] bytes
    4. [26991] - memory is allocated [ 71680] bytes
    5. [26991] - memory is allocated [ 72192] bytes
    6. [26991] - memory is allocated [ 72704] bytes
    7. [26991] - memory is allocated [ 73216] bytes
    8. 已杀死

    Cgroup的内存限制可能会有一些延迟,特别是在内存使用接近限制时。系统需要一些时间来检测和响应内存限制的超出情况。Cgroup内存限制可能不会精确到字节级别,因此设置的内存限制和实际触发OOM杀死进程的内存使用量之间可能会有一些差异。以下是一些可能的原因和解释:

    1.  内存使用延迟

    Cgroup的内存限制可能会有一些延迟,特别是在内存使用接近限制时。系统需要一些时间来检测和响应内存限制的超出情况。

    2.  内存使用统计

    Cgroup内存限制考虑的不仅仅是用户态内存,还包括内核态内存和其它系统开销。因此,进程的实际内存使用量可能比你看到的数值要高。

    3.  内存分配单位

    内存分配可能是以页(通常为4KB)为单位进行的,因此实际分配的内存可能会超过指定的限制。Cgroup可能会在分配内存的边界上进行四舍五入处理。

    4.  内存缓存和缓冲区

    系统可能会为进程分配额外的内存用于缓存和缓冲区,这些内存可能在Cgroup限制之外。实际的内存使用量可能会略高于配置的限制。

    5.  OOM决策延迟

    即使内存使用超过了限制,OOM杀死进程的决策可能需要一些时间来执行。内核需要检测到内存超出限制并执行相应的操作,这可能会有一定的延迟。

    磁盘I/O限制

    我们先看一下我们的硬盘IO,我们的模拟命令如下:(从/dev/sda上读入数据,输出到/dev/null上)

    dd if=/dev/sda of=/dev/null iflag=direct
    
    

    dd 命令默认会使用一定的缓存来提高性能,这可能会导致短时间内的瞬时读取速度超过你设置的限制。可以尝试使用 dd 命令的 iflag=direct 选项来禁用缓存,这样可以更准确地测试实际的磁盘读取速度

    我们通过iotop命令我们可以看到相关的IO速度是128MB/s(虚拟机内):

    1. TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
    2. 25722 be/4 root 128.87 M/s 0.00 B/s 0.00 % 1.30 % dd if=/dev/sda of=/dev/null

    然后,我们先创建一个blkio(块设备IO)的cgroup

    mkdir /sys/fs/cgroup/blkio/limit
    

    并把读IO限制到1MB/s,并把前面那个dd命令的pid放进去(注:8:0 是设备号,你可以通过ls -l /dev/sda1获得):

    1. echo '8:0 1048576' > /sys/fs/cgroup/blkio/limit/blkio.throttle.read_bps_device
    2. echo [pid] > /sys/fs/cgroup/blkio/limit/tasks

    再用iotop命令,你马上就能看到读速度被限制到了1MB/s左右。

    1. TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
    2. 13571 be/4 root 1043.21 K32 0.00 B/s 0.00 % 97.64 % dd if=/dev/sda of=/dev/null iflag=direct

    CGroup的子系统

    好了,有了以上的感性认识我们来,我们来看看control group有哪些子系统:

    ● blkio — 这​​​个​​​子​​​系​​​统​​​为​​​块​​​设​​​备​​​设​​​定​​​输​​​入​​​/输​​​出​​​限​​​制​​​,比​​​如​​​物​​​理​​​设​​​备​​​(磁​​​盘​​​,固​​​态​​​硬​​​盘​​​,USB 等​​​等​​​)。

    ● cpu — 这​​​个​​​子​​​系​​​统​​​使​​​用​​​调​​​度​​​程​​​序​​​提​​​供​​​对​​​ CPU 的​​​ cgroup 任​​​务​​​访​​​问​​​。​​​

    ● cpuacct — 这​​​个​​​子​​​系​​​统​​​自​​​动​​​生​​​成​​​ cgroup 中​​​任​​​务​​​所​​​使​​​用​​​的​​​ CPU 报​​​告​​​。​​​

    ● cpuset — 这​​​个​​​子​​​系​​​统​​​为​​​ cgroup 中​​​的​​​任​​​务​​​分​​​配​​​独​​​立​​​ CPU(在​​​多​​​核​​​系​​​统​​​)和​​​内​​​存​​​节​​​点​​​。​​​

    ● devices — 这​​​个​​​子​​​系​​​统​​​可​​​允​​​许​​​或​​​者​​​拒​​​绝​​​ cgroup 中​​​的​​​任​​​务​​​访​​​问​​​设​​​备​​​。​​​

    ● freezer — 这​​​个​​​子​​​系​​​统​​​挂​​​起​​​或​​​者​​​恢​​​复​​​ cgroup 中​​​的​​​任​​​务​​​。​​​

    ● memory — 这​​​个​​​子​​​系​​​统​​​设​​​定​​​ cgroup 中​​​任​​​务​​​使​​​用​​​的​​​内​​​存​​​限​​​制​​​,并​​​自​​​动​​​生​​​成​​​​​内​​​存​​​资​​​源使用​​​报​​​告​​​。​​​

    ● net_cls — 这​​​个​​​子​​​系​​​统​​​使​​​用​​​等​​​级​​​识​​​别​​​符​​​(classid)标​​​记​​​网​​​络​​​数​​​据​​​包​​​,可​​​允​​​许​​​ Linux 流​​​量​​​控​​​制​​​程​​​序​​​(tc)识​​​别​​​从​​​具​​​体​​​ cgroup 中​​​生​​​成​​​的​​​数​​​据​​​包​​​。​​​

    ● net_prio — 这个子系统用来设计网络流量的优先级

    ● hugetlb — 这个子系统主要针对于HugeTLB系统进行限制,这是一个大页文件系统。

    关于各个子系统的参数细节,以及更多的Linux CGroup的文档,你可以看看下面的文档:

    ● Linux Kernel的官方文档

    ● Redhat的官方文档

    CGroup的术语

    CGroup有下述术语:

    ● 任务(Tasks):就是系统的一个进程。

    ● 控制组(Control Group):一组按照某种标准划分的进程,比如官方文档中的Professor和Student,或是WWW和System之类的,其表示了某进程组。Cgroups中的资源控制都是以控制组为单位实现。一个进程可以加入到某个控制组。而资源的限制是定义在这个组上,就像上面示例中我用的haoel一样。简单点说,cgroup的呈现就是一个目录带一系列的可配置文件。

    ● 层级(Hierarchy):控制组可以组织成hierarchical的形式,既一颗控制组的树(目录结构)。控制组树上的子节点继承父结点的属性。简单点说,hierarchy就是在一个或多个子系统上的cgroups目录树。

    ● 子系统(Subsystem):一个子系统就是一个资源控制器,比如CPU子系统就是控制CPU时间分配的一个控制器。子系统必须附加到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制族群都受到这个子系统的控制。Cgroup的子系统可以有很多,也在不断增加中。

  • 相关阅读:
    MapReduce分区机制(Hadoop)
    「Spring」Boot Docker 认证指南(下)
    各种业务场景调用API代理的API接口教程
    TodoList案例
    Pytorch实现线性回归
    mysql集群使用nginx配置负载均衡
    上海市通过区块链技术攻关 构建数字经济可信安全技术底座
    【虚幻引擎UE】UE4/UE5 通用插件推荐及使用介绍
    【JAVA高级】——Druid连接池和Apache的DBUtils使用
    阿里大于发送短信(用户微服务--消息微服务)
  • 原文地址:https://blog.csdn.net/huchao_lingo/article/details/140448558