当前位置 博文首页 > 草根工厂——技术虐我千百遍,我依然待你如初恋:Linux cgroup详

    草根工厂——技术虐我千百遍,我依然待你如初恋:Linux cgroup详

    作者:[db:作者] 时间:2021-09-14 10:23

    1、导言:

    Linux系统每个进程都可以自由竞争系统资源,有时候会导致一些次要进程占用了系统某个资源(如CPU)的绝大部分,主要进程就不能很好地执行,从而影响系统效率,重则在linux资源耗尽时可能会引起错杀进程。因此linux引入了linux cgroups来控制进程资源,让进程更可控。

    2、Linux cgroups基础知识

    Linux Cgroup 可???让???您???为???系???统???中???所???运???行???任???务???(进???程???)的???用???户???定???义???组???群???分???配???资???源??? — 比???如??? CPU 时???间???、???系???统???内???存???、???网???络???带???宽???或???者???这???些???资???源???的???组???合???。???您???可???以???监???控???您???配???置???的??? cgroup,拒???绝??? cgroup 访???问???某???些???资???源???,甚???至???在???运???行???的???系???统???中???动???态???配???置???您???的??? cgroup。所以,可以将 controll groups 理解为 controller (system resource) (for) (process)groups,也就是是说它以一组进程为目标进行系统资源分配和控制。

    ?

    2.1 它主要提供了如下功能:

    • Resource limitation: 限制资源使用,比如内存使用上限以及文件系统的缓存限制。
    • Prioritization: 优先级控制,比如:CPU利用和磁盘IO吞吐。
    • Accounting: 一些审计或一些统计,主要目的是为了计费。
    • Control: 挂起进程,恢复执行进程。

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

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

    • 隔离一个进程集合(比如:nginx的所有进程),并限制他们所消费的资源,比如绑定CPU的核。
    • 为这组进程分配其足够使用的内存
    • 为这组进程分配相应的网络带宽和磁盘存储限制
    • 限制访问某些设备(通过设置设备的白名单)

    2.2 查看linux是否启用了linux cgroups

    $ uname -r

    4.18.0-24-generic

    $ cat /boot/config-4.18.0-24-generic | grep CGROUP

    CONFIG_CGROUPS=y

    CONFIG_BLK_CGROUP=y

    # CONFIG_DEBUG_BLK_CGROUP is not set

    CONFIG_CGROUP_WRITEBACK=y

    CONFIG_CGROUP_SCHED=y

    CONFIG_CGROUP_PIDS=y

    CONFIG_CGROUP_RDMA=y

    CONFIG_CGROUP_FREEZER=y

    CONFIG_CGROUP_HUGETLB=y

    CONFIG_CGROUP_DEVICE=y

    CONFIG_CGROUP_CPUACCT=y

    CONFIG_CGROUP_PERF=y

    CONFIG_CGROUP_BPF=y

    # CONFIG_CGROUP_DEBUG is not set

    CONFIG_SOCK_CGROUP_DATA=y

    CONFIG_NETFILTER_XT_MATCH_CGROUP=m

    CONFIG_NET_CLS_CGROUP=m

    CONFIG_CGROUP_NET_PRIO=y

    CONFIG_CGROUP_NET_CLASSID=y

    对应的CGROUP项为“y”代表已经打开linux cgroups功能。

    2.3 Cgroups 组成

    Cgroups主要由task,cgroup,subsystem及hierarchy构成。下面分别介绍下各自的概念。

    1. Task : 在Cgroups中,task就是系统的一个进程。
    2. Cgroup : Cgroups中的资源控制都以cgroup为单位实现的。cgroup表示按照某种资源控制标准划分而成的任务组,包含一个或多个Subsystems。一个任务可以加入某个cgroup,也可以从某个cgroup迁移到另外一个cgroup。
    3. Subsystem : Cgroups中的subsystem就是一个资源调度控制器(Resource Controller)。比如CPU子系统可以控制CPU时间分配,内存子系统可以限制cgroup内存使用量。
    4. Hierarchy : hierarchy由一系列cgroup以一个树状结构排列而成,每个hierarchy通过绑定对应的subsystem进行资源调度。hierarchy中的cgroup节点可以包含零或多个子节点,子节点继承父节点的属性。整个系统可以有多个hierarchy。

    2.4 组织结合和基本规则

    ?????? 主要介绍Subsystems, Hierarchies,Control Group和Tasks之间组织结构和规则:

    规则一:

    ?????? 同一个hierarchy能够附加一个或多个subsystem。如 cpu 和 memory subsystems(或者任意多个subsystems)附加到同一个hierarchy。

    规则二:

    ?????? 一个 subsystem 可以附加到多个 hierarchy,当且仅当这些 hierarchy 只有这唯一一个 subsystem。即某个hierarchy(hierarchy A)中的subsystem(如CPU)不能附加到已经附加了其他subsystem的hierarchy(如hierarchy B)中。也就是说已经附加在某个 hierarchy 上的 subsystem 不能附加到其他含有别的 subsystem 的 hierarchy 上。

    规则三:

    ?????? 系统每次新建一个hierarchy时,该系统上的所有task默认构成了这个新建的hierarchy的初始化cgroup,这个cgroup也称为root cgroup。对于你创建的每个hierarchy,task只能存在于其中一个cgroup中,即一个task不能存在于同一个hierarchy的不同cgroup中,但是一个task可以存在在不同hierarchy中的多个cgroup中。如果操作时把一个task添加到同一个hierarchy中的另一个cgroup中,则会从第一个cgroup中移除

    如:

    cpu 和 memory subsystem被附加到 cpu_mem_cg 的hierarchy。而 net_cls subsystem被附加到 net_cls hierarchy。并且httpd进程被同时加到了 cpu_mem_cg hierarchy的 cg1 cgroup中和 net hierarchy的 cg2 cgroup中。并通过两个hierarchy的subsystem分别对httpd进程进行cpu,memory及网络带宽的限制。

    规则四:

    ?????? 进程(task)在 fork 自身时创建的子任务(child task)默认与原 task 在同一个 cgroup 中,但是 child task 允许被移动到不同的 cgroup 中。即 fork 完成后,父子进程间是完全独立的。

    2.5 hierarchy和cgroup操作

    hierarchy

    1)新建hierarchy

    mkdir cgroup/hy_cpu_mem

    2)使用mount命令挂载hierarchy(hy_cpu_mem),并附加cpu、memory到该hierarchy上

    ?????? mount -t cgroup -o cpu,cpuset,memory hy_cpu_mem cgroup/hy_cpu_mem

    3)如果想在已有的hierarchy上attch或detach,使用remount命令detach subsystem

    ?????? mount -t cgroup -o cpu,cpuset,hy_cpu_mem cgroup/hy_cpu_mem? #detach memory

    4)卸载hierarchy

    ?????? umount cgroup/hy_cpu_mem

    cgroup

    1. 创建cgroup

    mkdir cgroup/hy_cpu_mem/cgroup1

    1. 设置cgroup参数

    sudo echo 100000 > cpu.cfs_period_us

    1. 移动task(进程)

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

    2.6 subsystem介绍

    Linxu中为了方便用户使用cgroups,已经把其实现成了文件系统,其目录在/var/fs/cgroup下:

    $ ll /sys/fs/cgroup

    总用量 0

    dr-xr-xr-x 5 root root? 0 6月? 26 15:52 blkio

    lrwxrwxrwx 1 root root 11 6月? 26 15:52 cpu -> cpu,cpuacct

    lrwxrwxrwx 1 root root 11 6月? 26 15:52 cpuacct -> cpu,cpuacct

    dr-xr-xr-x 5 root root? 0 6月? 26 15:52 cpu,cpuacct

    dr-xr-xr-x 3 root root? 0 6月? 26 15:52 cpuset

    dr-xr-xr-x 5 root root? 0 6月? 26 15:52 devices

    dr-xr-xr-x 3 root root? 0 6月? 26 15:52 freezer

    dr-xr-xr-x 3 root root? 0 6月? 26 15:52 hugetlb

    dr-xr-xr-x 5 root root? 0 6月? 26 15:52 memory

    lrwxrwxrwx 1 root root 16 6月? 26 15:52 net_cls -> net_cls,net_prio

    dr-xr-xr-x 3 root root? 0 6月? 26 15:52 net_cls,net_prio

    lrwxrwxrwx 1 root root 16 6月? 26 15:52 net_prio -> net_cls,net_prio

    dr-xr-xr-x 3 root root? 0 6月? 26 15:52 perf_event

    dr-xr-xr-x 5 root root? 0 6月? 26 15:52 pids

    dr-xr-xr-x 2 root root? 0 6月? 26 15:52 rdma

    dr-xr-xr-x 6 root root? 0 6月? 26 15:52 systemd

    dr-xr-xr-x 5 root root? 0 6月? 26 15:52 unified

    我们可以看到/sys/fs/cgroug目录下有多个子目录,这些目录都可以认为是收cgroups管理的subsystem资源。每格subsystem对应如下:

    • 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系统进行限制,这是一个大页文件系统。

    2.7 subsystem配置参数介绍

    2.7.1 blkio - BLOCK IO 资源控制

    限额类 限额类是主要有两种策略,一种是基于完全公平队列调度(CFQ:Completely Fair Queuing )的按权重分配各个 cgroup 所能占用总体资源的百分比,好处是当资源空闲时可以充分利用,但只能用于最底层节点 cgroup 的配置;另一种则是设定资源使用上限,这种限额在各个层次的 cgroup 都可以配置,但这种限制较为生硬,并且容器之间依然会出现资源的竞争。

    ?

    • 按比例分配块设备 IO 资源
    1. blkio.weight:填写 100-1000 的一个整数值,作为相对权重比率,作为通用的设备分配比。
    2. blkio.weight_device: 针对特定设备的权重比,写入格式为device_types:node_numbers weight,空格前的参数段指定设备,weight参数与blkio.weight相同并覆盖原有的通用分配比。{![查看一个设备的device_types:node_numbers可以使用:ls -l /dev/DEV,看到的用逗号分隔的两个数字就是。有的文章也称之为major_number:minor_number。]}
    • 控制 IO 读写速度上限
    1. blkio.throttle.read_bps_device:按每秒读取块设备的数据量设定上限,格式device_types:node_numbers bytes_per_second。
    2. blkio.throttle.write_bps_device:按每秒写入块设备的数据量设定上限,格式device_types:node_numbers bytes_per_second。
    3. blkio.throttle.read_iops_device:按每秒读操作次数设定上限,格式device_types:node_numbers operations_per_second。
    4. blkio.throttle.write_iops_device:按每秒写操作次数设定上限,格式device_types:node_numbers operations_per_second
    • 针对特定操作 (read, write, sync, 或 async) 设定读写速度上限
    1. blkio.throttle.io_serviced:针对特定操作按每秒操作次数设定上限,格式device_types:node_numbers operation operations_per_second
    2. blkio.throttle.io_service_bytes:针对特定操作按每秒数据量设定上限,格式device_types:node_numbers operation bytes_per_second
    • 统计与监控 以下内容都是只读的状态报告,通过这些统计项更好地统计、监控进程的 io 情况。
    1. blkio.reset_stats:重置统计信息,写入一个 int 值即可。
    2. blkio.time:统计 cgroup 对设备的访问时间,按格式device_types:node_numbers milliseconds读取信息即可,以下类似。
    1. blkio.io_serviced:统计 cgroup 对特定设备的 IO 操作(包括 read、write、sync 及 async)次数,格式device_types:node_numbers operation number
    2. blkio.sectors:统计 cgroup 对设备扇区访问次数,格式 device_types:node_numbers sector_count
    3. blkio.io_service_bytes:统计 cgroup 对特定设备 IO 操作(包括 read、write、sync 及 async)的数据量,格式device_types:node_numbers operation bytes
    4. blkio.io_queued:统计 cgroup 的队列中对 IO 操作(包括 read、write、sync 及 async)的请求次数,格式number operation
    5. blkio.io_service_time:统计 cgroup 对特定设备的 IO 操作(包括 read、write、sync 及 async)时间 (单位为 ns),格式device_types:node_numbers operation time
    6. blkio.io_merged:统计 cgroup 将 BIOS 请求合并到 IO 操作(包括 read、write、sync 及 async)请求的次数,格式number operation
    7. blkio.io_wait_time:统计 cgroup 在各设???备???中各类型???IO 操作(包括 read、write、sync 及 async)在队列中的等待时间?(单位 ns),格式device_types:node_numbers operation time
    8. __blkio.__recursive_*:各类型的统计都有一个递归版本,Docker 中使用的都是这个版本。获取的数据与非递归版本是一样的,但是包括 cgroup 所有层级的监控数据。

    2.7.2 cpu - CPU 资源控制

    CPU 资源的控制也有两种策略,一种是完全公平调度 (CFS:Completely Fair Scheduler)策略,提供了限额和按比例分配两种方式进行资源控制;另一种是实时调度(Real-Time Scheduler)策略,针对实时进程按周期分配固定的运行时间。配置时间都以微秒(μs)为单位,文件名中用us表示。

    ?

    CFS 调度策略下的配置

    ?

    • 设定 CPU 使用周期使用时间上限
    1. cpu.cfs_period_us:设定周期时间,必须与cfs_quota_us配合使用。
    2. cpu.cfs_quota_us :设定周期内最多可使用的时间。这里的配置指 task 对单个 cpu 的使用上限,若cfs_quota_us是cfs_period_us的两倍,就表示在两个核上完全使用。数值范围为 1000 - 1000,000(微秒)。
    3. cpu.stat:统计信息,包含nr_periods(表示经历了几个cfs_period_us周期)、nr_throttled(表示 task 被限制的次数)及throttled_time(表示 task 被限制的总时长)。
    • 按权重比例设定 CPU 的分配
    1. cpu.shares:设定一个整数(必须大于等于 2)表示相对权重,最后除以权重总和算出相对比例,按比例分配 CPU 时间。(如 cgroup A 设置 100,cgroup B 设置 300,那么 cgroup A 中的 task 运行 25% 的 CPU 时间。对于一个 4 核 CPU 的系统来说,cgroup A 中的 task 可以 100% 占有某一个 CPU,这个比例是相对整体的一个值。)
    • RT 调度策略下的配置 实时调度策略与公平调度策略中的按周期分配时间的方法类似,也是在周期内分配一个固定的运行时间。

    ?

    1. cpu.rt_period_us :设定周期时间。
    2. cpu.rt_runtime_us:设定周期中的运行时间。

    2.7.3 cpuacct - CPU 资源报告

    这个子系统的配置是cpu子系统的补充,提供 CPU 资源用量的统计,时间单位都是纳秒。

    • cpuacct.usage:统计 cgroup 中所有 task 的 cpu 使用时长
    • cpuacct.stat:统计 cgroup 中所有 task 的用户态和内核态分别使用 cpu 的时长
    • cpuacct.usage_percpu:统计 cgroup 中所有 task 使用每个 cpu 的时长

    2.7.4 cpuset - CPU 绑定

    为 task 分配独立 CPU 资源的子系统,参数较多,这里只选讲两个必须配置的参数,同时 Docker 中目前也只用到这两个。

    ?

    • cpuset.cpus:在这个文件中填写 cgroup 可使用的 CPU 编号,如0-2,16代表 0、1、2 和 16 这 4 个 CPU。
    • cpuset.mems:与 CPU 类似,表示 cgroup 可使用的memory node,格式同上

    2.7.5 device - 限制 task 对 device 的使用

    ** 设备黑 / 白名单过滤 **

    • devices.allow:允许名单,语法type device_types:node_numbers access type ;type有三种类型:b(块设备)、c(字符设备)、a(全部设备);access也有三种方式:r(读)、w(写)、m(创建)。
    • devices.deny:禁止名单,语法格式同上。

    统计报告

    • devices.list:报???告???为???这???个??? cgroup 中???的?task 设???定???访???问???控???制???的???设???备

    2.7.6 freezer - 暂停 / 恢复 cgroup 中的 task

    只有一个属性,表示进程的状态,把 task 放到 freezer 所在的 cgroup,再把 state 改为 FROZEN,就可以暂停进程。不允许在 cgroup 处于 FROZEN 状态时加入进程。 * **freezer.state **,包括如下三种状态: - FROZEN 停止 - FREEZING 正在停止,这个是只读状态,不能写入这个值。 - THAWED 恢复

    ?

    2.7.7 memory - 内存资源管理

    限额类

    ?

    • memory.limit_bytes:强制限制最大内存使用量,单位有k、m、g三种,填-1则代表无限制。
    • memory.soft_limit_bytes:软限制,只有比强制限制设置的值小时才有意义。填写格式同上。当整体内存紧张的情况下,task 获取的内存就被限制在软限制额度之内,以保证不会有太多进程因内存挨饿。可以看到,加入了内存的资源限制并不代表没有资源竞争。
    • memory.memsw.limit_bytes:设定最大内存与 swap 区内存之和的用量限制。填写格式同上。

    报警与自动控制

    ?

    • memory.oom_control:改参数填 0 或 1, 0表示开启,当 cgroup 中的进程使用资源超过界限时立即杀死进程,1表示不启用。默认情况下,包含 memory 子系统的 cgroup 都启用。当oom_control不启用时,实际使用内存超过界限时进程会被暂停直到有空闲的内存资源。

    统计与监控类

    ?

    • memory.usage_bytes:报???告???该??? cgroup 中???进???程???使???用???的???当???前???总???内???存???用???量(以字节为单位)
    • memory.max_usage_bytes:报???告???该??? cgroup 中???进???程???使???用???的???最???大???内???存???用???量
    • memory.failcnt:报???告???内???存???达???到???在??? memory.limit_in_bytes设???定???的???限???制???值???的???次???数???
    • memory.stat:包含大量的内存统计数据。
    • cache:页???缓???存???,包???括??? tmpfs(shmem),单位为字节。
    • rss:匿???名???和??? swap 缓???存???,不???包???括??? tmpfs(shmem),单位为字节。
    • mapped_file:memory-mapped 映???射???的???文???件???大???小???,包???括??? tmpfs(shmem),单???位???为???字???节???
    • pgpgin:存???入???内???存???中???的???页???数???
    • pgpgout:从???内???存???中???读???出???的???页???数
    • swap:swap 用???量???,单???位???为???字???节???
    • active_anon:在???活???跃???的???最???近???最???少???使???用???(least-recently-used,LRU)列???表???中???的???匿???名???和??? swap 缓???存???,包???括??? tmpfs(shmem),单???位???为???字???节???
    • inactive_anon:不???活???跃???的??? LRU 列???表???中???的???匿???名???和??? swap 缓???存???,包???括??? tmpfs(shmem),单???位???为???字???节
    • active_file:活???跃??? LRU 列???表???中???的??? file-backed 内???存???,以???字???节???为???单???位
    • inactive_file:不???活???跃??? LRU 列???表???中???的??? file-backed 内???存???,以???字???节???为???单???位
    • unevictable:无???法???再???生???的???内???存???,以???字???节???为???单???位???
    • hierarchical_memory_limit:包???含??? memory cgroup 的???层???级???的???内???存???限???制???,单???位???为???字???节???
    • hierarchical_memsw_limit:包???含??? memory cgroup 的???层???级???的???内???存???加??? swap 限???制???,单???位???为???字???节???

    ?

    linux cgroups应用实例

    前提

    假设我们编写了一个死循环程序test

    #include <stdio.h>

    int main(){

    ??????? long i=0;

    ??????? while(1){

    ??????????????? i++;

    ??????? }

    ??????? return 0;

    }

    占用cpu达到100%:

    例1:显示CPU使用比例

    $ mkdir /sys/fs/cgroup/cpu/test

    $ cd /sys/fs/cgroup/cpu/ test

    $ ls

    cgroup.clone_children? cgroup.procs? cpuacct.stat? cpuacct.usage? cpuacct.usage_percpu? cpu.cfs_period_us? cpu.cfs_quota_us? cpu.shares? cpu.stat? notify_on_release? tasks

    $ cat cpu.cfs_quota_us

    -1

    $ sudo echo 100000 > cpu.cfs_period_us

    $ sudo echo 20000 > cpu.cfs_quota_us

    $ cat cpu.cfs_quota_us

    $ echo 30167 > tasks

    注意:看下cgroups是不是只读,如果是只读需要重新挂载为读写:sudo mount -o remount,rw /sys/fs/cgroup。另外注意权限,如果出现sudo还是写不了,那是因为“>”也是一个命令,sudo只是让echo具有root权限,而“>”还是普通权限,遇到这种情况可以通过以下方法解决:

    1)利用sh –c命令让bash把字符串当成完整一个命令执行,如:sudo sh –c “echo hello > 1.txt”

    2)利用管道和tee命令:

    ?????? echo a | sudo tee 1.txt;或追加:echo a | sudo tee –a 1.txt

    3)进入root用户,sudo –s

    重新查看test占用CPU率:

    可以通过资源控制后,test进程最高占用20%的CPU资料,也就是我们设置的赋值。另外,同样的进程共同占用所分配的资料,如同时启动两个test,那么两个test共同占用20%的CPU资源。

    例2:限制进程使用memory

    $ mkdir test

    $ cd test

    $ cat memory.limit_in_bytes

    $ echo 64k > memory.limit_in_bytes

    $ echo 30167 > tasks

    这样就限制了test进程最多可使用64K内存,超过会被杀掉。

    例3:现在进程IO资源

    启动压力测试命令:

    ?????? sudo dd if=/dev/sda1 of=/dev/null?? #将sda1整盘数据输出到/dev/null

    查看IO使用情况:

    可以看到此时自盘读取速度为7.97/M/s。

    接下来开始控制IO使用:

    $ cd /sys/fs/cgroup/blkio

    $ mkdir io

    $ cd io

    $ ll /dev/sda1

    $ brw-rw---- 1 root disk 8, 1 6月? 18 13:55 /dev/sda1

    $ sudo echo '8:0 1048576' > blkio.throttle.read_bps_device

    $ sudo echo 30618 > tasks

    这样,这个进程的IO读速度被限制在1M/S秒

    cs