Linux cgroups的全称是Linux Control Groups,它是Linux内核的特性,主要作用是限制、记录和隔离进程组(process groups)使用的物理资源(cpu、memory、IO 等)
2006的时候,Google的一些工程师启动了这个项目,最初的名字叫process containers。因为 container在内核中名字有歧义,2007的时候改名为control groups,并合并到2008年发布的 2.6.24 内核版本
最初cgroups的版本被称为 v1,这个版本的cgroups设计并不友好,理解起来非常困难。后续的开发工作由Tejun Heo接管,重新设计并重写了cgroups,新版本被称为v2,并首次出现在kernel 4.5版本
cgroups 从设计之初使命就很明确,为进程提供资源控制,它主要的功能包括:

  • 资源限制:限制进程使用的资源上限,比如最大内存、文件系统缓存使用限制
  • 优先级控制:不同的组可以有不同的优先级,比如 CPU 使用和磁盘 IO 吞吐
  • 审计:计算 group 的资源使用情况,可以用来计费
  • 控制:挂起一组进程,或者重启一组进程

目前cgroups已经成为很多技术的基础,比如 LXC、Docker、systemd等

cgroups 核心概念

cgroups是用来对进程进行资源管理的,因此cgroup需要考虑如何抽象这两种概念:进程和资源,同时如何组织自己的结构。cgroups中有几个非常重要的概念:

  • task:任务,对应于系统中运行的一个实体,一般是指进程
  • subsystem:子系统,具体的资源控制器(resource class 或者 resource controller),控制某个特定的资源使用。比如 CPU 子系统可以控制 CPU 时间,memory 子系统可以控制内存使用量
  • cgroup:控制组,一组任务和子系统的关联关系,表示对这些任务进行怎样的资源管理策略
  • hierarchy:层级树,一系列 cgroup 组成的树形结构。每个节点都是一个 cgroup,cgroup 可以有多个子节点,子节点默认会继承父节点的属性。系统中可以有多个 hierarchy

虽然 cgroup 支持 hierarchy,允许不同的子资源挂到不同的目录,但是多个树之间有各种限制,增加了理解和维护的复杂性。在实际使用中,所有的子资源都会统一放到某个路径下(比如 ubuntu20 的 /sys/fs/cgroup/)

子资源系统(Resource Classes or SubSystem)

目前有下面这些资源子系统:

  • Block IO(blkio):限制块设备(磁盘、SSD、USB 等)的 IO 速率
  • CPU Set(cpuset):限制任务能运行在哪些 CPU 核上
  • CPU Accounting(cpuacct):生成 cgroup 中任务使用 CPU 的报告
  • CPU (CPU):限制调度器分配的 CPU 时间
  • Devices (devices):允许或者拒绝 cgroup 中任务对设备的访问
  • Freezer (freezer):挂起或者重启 cgroup 中的任务
  • Memory (memory):限制 cgroup 中任务使用内存的量,并生成任务当前内存的使用情况报告
  • Network Classifier(net_cls):为 cgroup 中的报文设置上特定的 classid 标志,这样 tc 等工具就能根据标记对网络进行配置
  • Network Priority (net_prio):对每个网络接口设置报文的优先级
  • perf_event:识别任务的 cgroup 成员,可以用来做性能分析

cgroups文件系统

Linux 使用了多种数据结构在内核中实现了 cgroups 的配置,关联了进程和 cgroups 节点,那么 Linux 又是如何让用户态的进程使用到 cgroups 的功能呢? Linux内核有一个很强大的模块叫 VFS (Virtual File System)。 VFS 能够把具体文件系统的细节隐藏起来,给用户态进程提供一个统一的文件系统 API 接口。 cgroups 也是通过 VFS 把功能暴露给用户态的,cgroups 与 VFS 之间的衔接部分称之为 cgroups 文件系统。下面先介绍一下 VFS 的基础知识,然后再介绍下 cgroups 文件系统的实现

VFS

VFS 是一个内核抽象层,能够隐藏具体文件系统的实现细节,从而给用户态进程提供一套统一的 API 接口。VFS 使用了一种通用文件系统的设计,具体的文件系统只要实现了 VFS 的设计接口,就能够注册到 VFS 中,从而使内核可以读写这种文件系统。 这很像面向对象设计中的抽象类与子类之间的关系,抽象类负责对外接口的设计,子类负责具体的实现。其实,VFS本身就是用 c 语言实现的一套面向对象的接口

通用文件模型

VFS 通用文件模型中包含以下四种元数据结构:

  1. 超级块对象(superblock object),用于存放已经注册的文件系统的信息。比如ext2,ext3等这些基础的磁盘文件系统,还有用于读写socket的socket文件系统,以及当前的用于读写cgroups配置信息的 cgroups 文件系统等
  2. 索引节点对象(inode object),用于存放具体文件的信息。对于一般的磁盘文件系统而言,inode 节点中一般会存放文件在硬盘中的存储块等信息;对于socket文件系统,inode会存放socket的相关属性,而对于cgroups这样的特殊文件系统,inode会存放与 cgroup 节点相关的属性信息。这里面比较重要的一个部分是一个叫做 inode_operations 的结构体,这个结构体定义了在具体文件系统中创建文件,删除文件等的具体实现
  3. 文件对象(file object),一个文件对象表示进程内打开的一个文件,文件对象是存放在进程的文件描述符表里面的。同样这个文件中比较重要的部分是一个叫 file_operations 的结构体,这个结构体描述了具体的文件系统的读写实现。当进程在某一个文件描述符上调用读写操作时,实际调用的是 file_operations 中定义的方法。 对于普通的磁盘文件系统,file_operations 中定义的就是普通的块设备读写操作;对于socket文件系统,file_operations 中定义的就是 socket 对应的 send/recv 等操作;而对于cgroups这样的特殊文件系统,file_operations 中定义的就是操作 cgroup 结构体等具体的实现
  4. 目录项对象(dentry object),在每个文件系统中,内核在查找某一个路径中的文件时,会为内核路径上的每一个分量都生成一个目录项对象,通过目录项对象能够找到对应的 inode 对象,目录项对象一般会被缓存,从而提高内核查找速度

Subsystems,Hierarchies,Control Groups 和Tasks的关系

Subsystems, Hierarchies,Control Group和Tasks之间有许多的规则

规则1

  • 同一个hierarchy能够附加一个或多个subsystem

如下图将cpu和memory subsystems(或者任意多个subsystems)附加到同一个hierarchy
clipboard

规则2

  • 一个subsystem只能附加到一个hierarchy上

如下图cpu subsystem已经附加到了hierarchy A,并且memory subsystem已经附加到了hierarchy B。因此cpu subsystem不能在附加到hierarchy B
clipboard1

规则3

系统每次新建一个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的cg3 cgroup中。并通过两个hierarchy的subsystem分别对httpd进程进行cpu,memory及网络带宽的限制
clipboard2

规则4

系统中的任何一个task(Linux中的进程)fork自己创建一个子task(子进程)时,子task会自动的继承父task cgroup的关系,在同一个cgroup中,但是子task可以根据需要移到其它不同的cgroup中。父子task之间是相互独立不依赖的。
如下图,httpd进程在cpu_and_mem hierarchy的/cg1 cgroup中并把PID 4537写到该cgroup的tasks中。之后httpd(PID=4537)进程fork一个子进程httpd(PID=4840)与其父进程在同一个hierarchy的统一个cgroup中,但是由于父task和子task之间的关系独立不依赖的,所以子task可以移到其它的cgroup中
clipboard3

使用cgroups

cgroup内核功能比较有趣的地方是它没有提供任何的系统调用接口,而是对 linux vfs 的一个实现,因此可以用类似文件系统的方式进行操作
使用 cgroups 的方式有几种:

  • 使用 cgroups 提供的虚拟文件系统,直接通过创建、读写和删除目录、文件来控制 cgroups
  • 使用命令行工具,比如 libcgroup 包提供的 cgcreate、cgexec、cgclassify 命令
  • 使用 rules engine daemon 提供的配置文件
  • 当然,systemd、lxc、docker 这些封装了 cgroups 的软件也能让你通过它们定义的接口控制 cgroups 的内容

直接操作cgroup文件系统

查看cgroups挂载信息

在centos的机器上,cgroups已经挂载到文件系统上了,可以通过mount命令查看

[root@VM-4-3-centos ~]# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)

如果没有的话,也可以通过以下命令来把想要的subsystem mount到系统中:

[root@VM-4-3-centos ~]# mount -t cgroup -o cpu,cpuset,memory cpu_and_mem /cgroup/cpu_and_mem

上述命令表示把 cpu、cpuset、memory 三个子资源 mount 到 /cgroup/cpu_and_mem 目录下
每个 cgroup 目录下面都会有描述该cgroup的文件,除了每个cgroup独特的资源控制文件,还有一些通用的文件:

  • tasks:当前cgroup包含的任务(task)pid列表,把某个进程的pid添加到这个文件中就等于把进程移到该cgroup
  • cgroup.procs:当前cgroup中包含的thread group列表,使用逻辑和tasks相同
  • notify_on_release:0或者1,是否在cgroup销毁的时候执行notify。如果为1,那么当这个cgroup最后一个任务离开时(退出或者迁移到其他 cgroup),并且最后一个子cgroup被删除时,系统会执行release_agent中指定的命令
  • release_agent:需要执行的命令

创建cgroup

创建cgroup,可以直接用mkdir在对应的子资源中创建一个目录:

[root@VM-4-3-centos ~]# mkdir /sys/fs/cgroup/cpu/mycgroup
[root@VM-4-3-centos ~]# ll /sys/fs/cgroup/cpu/mycgroup
total 0
-rw-r--r-- 1 root root 0 Jul  4 14:49 cgroup.clone_children
--w--w--w- 1 root root 0 Jul  4 14:49 cgroup.event_control
-rw-r--r-- 1 root root 0 Jul  4 14:49 cgroup.procs
-r--r--r-- 1 root root 0 Jul  4 14:49 cpuacct.stat
-rw-r--r-- 1 root root 0 Jul  4 14:49 cpuacct.usage
-r--r--r-- 1 root root 0 Jul  4 14:49 cpuacct.usage_percpu
-rw-r--r-- 1 root root 0 Jul  4 14:49 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Jul  4 14:49 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Jul  4 14:49 cpu.rt_period_us
-rw-r--r-- 1 root root 0 Jul  4 14:49 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 Jul  4 14:49 cpu.shares
-r--r--r-- 1 root root 0 Jul  4 14:49 cpu.stat
-rw-r--r-- 1 root root 0 Jul  4 14:49 notify_on_release
-rw-r--r-- 1 root root 0 Jul  4 14:49 tasks

上面命令在cpu子资源中创建了mycgroup,创建cgroup之后,目录中会自动创建需要的文件。我们后面会详细讲解这些文件的含义,目前只需要知道它们能够控制对应子资源就行

删除cgroup

删除子资源,就是删除对应的目录:

[root@VM-4-3-centos ~]# rmdir /sys/fs/cgroup/cpu/mycgroup/

删除之后,如果tasks文件中有进程,它们会自动迁移到父cgroup中

删除cgroup

删除子资源,就是删除对应的目录:

[root@VM-4-3-centos ~]# rmdir /sys/fs/cgroup/cpu/mycgroup/

删除之后,如果tasks文件中有进程,它们会自动迁移到父cgroup中

把进程加入到cgroup

要把某个已经运行的进程加入到cgroup,可以直接往需要的cgroup tasks文件中写入进程的 PID:

[root@VM-4-3-centos ~]# echo 2358 > /sys/fs/cgroup/memory/mycgroup/tasks

在cgroup中运行进程

如果想直接把进程运行在某个cgroup,但是运行前还不知道进程的Pid应该怎么办呢?
我们可以利用cgroup的继承方式来实现,因为子进程会继承父进程的cgroup,因此我们可以把当前shell加入到要想的cgroup:

[root@VM-4-3-centos ~]# echo $$ > /sys/fs/cgroup/cpu/mycgroup/tasks

上面的方案有个缺陷,运行完之后原来的shell还在cgroup 中。如果希望进程运行完不影响当前使用的 shell,可以另起一个临时的shell:

[root@VM-4-3-centos ~]# sh -c "echo \$$ > /sys/fs/cgroup/memory/mycgroup/tasks 

把进程移动到cgroup

如果想要把进程移动到另外一个 cgroup,只要使用echo把进程PID写入到cgroup tasks 文件中即可,原来cgroup tasks文件会自动删除该进程

cgroup-tools

cgroup-tools软件包提供了一系列命令可以操作和管理 cgroup,centos系统中可以通过下面的命令安装:

[root@VM-4-3-centos ~]# yum install libcgroup-tools.x86_64 -y

列出cgroup mount信息

最简单的,lssubsys可以查看系统中存在的subsystems:

[root@VM-4-3-centos ~]# lssubsys -am
cpuset /sys/fs/cgroup/cpuset
cpu,cpuacct /sys/fs/cgroup/cpu,cpuacct
memory /sys/fs/cgroup/memory
devices /sys/fs/cgroup/devices
freezer /sys/fs/cgroup/freezer
net_cls,net_prio /sys/fs/cgroup/net_cls,net_prio
blkio /sys/fs/cgroup/blkio
perf_event /sys/fs/cgroup/perf_event
hugetlb /sys/fs/cgroup/hugetlb
pids /sys/fs/cgroup/pids

创建cgroup

cgcreate可以用来为用户创建指定的cgroups:

[root@VM-4-3-centos ~]# sudo cgcreate -a dev -t dev -g cpu,memory:test1 
[root@VM-4-3-centos ~]# ls /sys/fs/cgroup/cpu/test1/
cgroup.clone_children  cgroup.procs  cpuacct.usage         cpu.cfs_period_us  cpu.rt_period_us   cpu.shares  notify_on_release
cgroup.event_control   cpuacct.stat  cpuacct.usage_percpu  cpu.cfs_quota_us   cpu.rt_runtime_us  cpu.stat    tasks
[root@VM-4-3-centos ~]# ls /sys/fs/cgroup/memory/test1/
cgroup.clone_children           memory.kmem.slabinfo                memory.memsw.failcnt             memory.soft_limit_in_bytes
cgroup.event_control            memory.kmem.tcp.failcnt             memory.memsw.limit_in_bytes      memory.stat
cgroup.procs                    memory.kmem.tcp.limit_in_bytes      memory.memsw.max_usage_in_bytes  memory.swappiness
memory.failcnt                  memory.kmem.tcp.max_usage_in_bytes  memory.memsw.usage_in_bytes      memory.usage_in_bytes
memory.force_empty              memory.kmem.tcp.usage_in_bytes      memory.move_charge_at_immigrate  memory.use_hierarchy
memory.kmem.failcnt             memory.kmem.usage_in_bytes          memory.numa_stat                 notify_on_release
memory.kmem.limit_in_bytes      memory.limit_in_bytes               memory.oom_control               tasks
memory.kmem.max_usage_in_bytes  memory.max_usage_in_bytes           memory.pressure_level

上面的命令表示在/sys/fs/cgroup/cpu和/sys/fs/cgroup/memory目录下面分别创建test1目录,也就是为cpu和memory子资源创建对应的cgroup

  • -t 指定 tasks 文件的用户和组,也就是指定哪些人可以把任务添加到 cgroup中,默认是从父cgroup继承
  • 指定除了tasks之外所有文件(资源控制文件)的用户和组,也就是哪些人可以管理资源参数
  • 指定要添加的cgroup,冒号前是逗号分割的子资源类型,冒号后面是cgroup 的路径(这个路径会添加到对应资源mount到的目录后面)。也就是说在特定目录下面添加指定的子资源

删除cgroup

知道怎么创建,也要知道怎么删除。不然系统中保留着太多用不到的cgroup浪费系统资源,也会让管理很麻烦
cgdelete可以删除对应的cgroups,它和cgcreate命令类似,可以用 -g 指定要删除的 cgroup:

[root@VM-4-3-centos ~]# sudo cgdelete -g cpu,memory:test1
[root@VM-4-3-centos ~]# ls /sys/fs/cgroup/memory/test1/
ls: cannot access /sys/fs/cgroup/memory/test1/: No such file or directory

cgdelete也提供了-r参数可以递归地删除某个cgroup以及它所有的子cgroup,如删除的cgroup中有任务,这些任务会自动移到父cgroup中

设置cgroup的参数

cgset命令可以设置某个子资源的参数,比如如果要限制某个cgroup中任务能使用的CPU 核数:

[root@VM-4-3-centos ~]# cgset -r cpuset.cpus=0-1 /mycgroup

-r后面跟着参数的键值对,每个子资源能够配置的键值对都有自己的规定,后面会有详细解释
cgset还能够把一个cgroup的参数拷贝到另外一个cgroup 中:

[root@VM-4-3-centos ~]# cgset --copy-from group1/ group2/

注意:cgset如果设置没有成功也不会报错

在某个cgroup中运行进程

cgexec执行某个程序,并把程序添加到对应的cgroups中:

[root@VM-4-3-centos ~]# cgroup cgexec -g memory,cpu:cizixs bash

cgroups是可以有层级结构的,因此可以直接创建具有层级关系的 cgroup,然后运行在该cgroup中:

[root@VM-4-3-centos ~]# cgcreate -g memory,cpu:groupname/foo
[root@VM-4-3-centos ~]# cgexec -g memory,cpu:groupname/foo bash

把已经运行的进程移动到某个cgroup

要把某个已经存在的程序(能够知道它的pid)移到某个cgroup,可以使用cgclassify命令:
比如把当前bash shell移入到特定的cgroup中

[root@VM-4-3-centos ~]# cgclassify -g memory,cpu:/mycgroup $$

“$$”表示当前进程的pid号,上面命令可以方便地测试一些耗费内存或者CPU的进程,如果 /mycgroup对CPU和memory做了限制
这个命令也可以同时移动多个进程,它们pid之间用空格隔开:

[root@VM-4-3-centos ~]# cgclassify -g cpu,memory:group1 1701 1138

cgroup子资源参数详解

每个subssytem负责系统的一部分资源管理,又分别提供多个参数可以控制,每个参数对应一个文件,往文件中写入特定格式的内容就能控制该资源

blkio:限制设备IO访问

限制磁盘IO有两种方式:权重(weight)和上限(limit)。权重是给不同的应用(或者 cgroup)一个权重值,各个应用按照百分比来使用 IO 资源;上限是直接写死应用读写速率的最大值

设置cgroup访问设备的权重

设置的权重并不能保证什么,当只有某个应用在读写磁盘时,不管它权重多少,都能使用磁盘。只有当多个应用同时读写磁盘时,才会根据权重为应用分配读写的速率。
blkio.weight:设置 cgroup 读写设备的权重,取值范围在 100-1000
blkio.weight_device:设置 cgroup 使用某个设备的权重。当访问该设备时,它会使用当前值,覆盖 blkio.weight 的值。内容的格式为 major:minor weight,前面是设备的 major 和 minor 编号,用来唯一表示一个设备,后面是 100-1000 之间的整数值

设置cgroup访问设备的限制

除了设置权重之外,还能设置 cgroup 磁盘的使用上限,保证 cgroup 中的进程读写磁盘的速率不会超过某个值

  • kio.throttle.read_bps_device:最多每秒钟从设备读取多少字节
  • kio.throttle.read_iops_device:最多每秒钟从设备中执行多少次读操作
  • kio.throttle.write_bps_device:最多每秒钟可以往设备写入多少字节
  • kio.throttle.write_iops_device:最多每秒钟可以往设备执行多少次写操作

读写字节数的限制格式一样 major:minor bytes_per_second,前面两个数字代表某个设备,后面跟着一个整数,代表每秒读写的字节数,单位为比特,如果需要其他单位(KB、MB等)需要自行转换。比如要限制 /dev/sda 读速率上线为 10 Mbps,可以运行:

echo "8:0 10485760" >
/sys/fs/cgroup/blkio/mygroup/blkio.throttle.read_bps_device

iops代表IO per second,是每秒钟执行读写的次数,格式为 major:minor operations_per_second。比如,要限制每秒只能写 10 次,可以运行:

echo "8:0 10" >
/sys/fs/cgroup/blkio/mygroup/blkio.throttle.write_iops_device

除了限制磁盘使用之外,blkio 还提供了 throttle 规则下磁盘使用的统计数据

  • blkio.throttle.io_serviced:cgroup 中进程读写磁盘的次数,文件中内容格式为 major:minor operation number,表示对磁盘进行某种操作(read、write、sync、async、total)的次数
  • blkio.throttle.io_service_bytes:和上面类似,不过这里保存的是操作传输的字节数
  • blkio.reset_stats:重置统计数据,往该文件中写入一个整数值即可
  • blkio.time:统计 cgroup 对各个设备的访问时间,格式为 major:minor milliseconds
  • blkio.io_serviced:CFQ 调度器下,cgroup 对设备的各种操作次数,和 blkio.throttle.io_serviced 刚好相反,所有不是 throttle 下的请求
  • blkio.io_services_bytes:CFQ 调度器下,cgroup 对各种设备的操作字节数
  • blkio.sectors:cgroup 中传输的扇区次数,格式为 major:minor sector_count
  • blkio.queued:cgroup IO 请求进队列的次数,格式为 number operation
  • blkio.dequeue:cgroup 的 IO 请求被设备出队列的次数,格式为 major:minor number
  • blkio.avg_queue_size:
  • blkio.merged:cgroup 把 BIOS 请求合并到 IO 操作请求的次数,格式为 number operation
  • blkio.io_wait_time:cgroup 等待队列服务的时间
  • blkio.io_service_time:CFQ 调度器下,cgroup 处理请求的时间(从请求开始调度,到 IO 操作完成)

cpu:限制进程组CPU使用

CPU 子资源可以管理 cgroup 中任务使用 CPU 的行为,任务使用 CPU 资源有两种调度方式:完全公平调度(CFS,Completely Fair Scheduler)和 实时调度(RT,Real-Time Scheduler)。前者可以根据权重为任务分配响应的 CPU 时间片,后者能够限制使用 CPU 的核数

CFS 调优参数:

CFS 调度下,每个 cgroup 都会分配一个权重,但是这个权重并不能保证任务使用 CPU 的具体数据。如果只有一个进程在运行(理论上,现实中机器上不太可能只有一个进程),不管它所在 cgroup 对应的 CPU 权重是多少,都能使用所有的 CPU 资源;在 CPU 资源紧张的情况,内核会根据 cgroup 的权重按照比例分配个给任务各自使用 CPU 的时间片
CFS 调度模式下,也可以给 cgroup 分配一个使用上限,限制任务能使用 CPU 的核数。
设置 CPU 数字的单位都是微秒(microsecond),用 us 表示

  • cpu.cfs_quota_us:每个周期 cgroup 中所有任务能使用的 CPU 时间,默认为 -1,表示不限制 CPU 使用。需要配合 cpu.cfs_period_us 一起使用,一般设置为 100000(docker 中设置的值)
  • cpu.cfs_period_us:每个周期中 cgroup 任务可以使用的时间周期,如果想要限制 cgroup 任务每秒钟使用 0.5 秒 CPU,可以在 cpu.cfs_quota_us 为 100000 的情况下把它设置为 50000。如果它的值比 cfs_quota_us 大,表明进程可以使用多个核 CPU,比如 200000 表示进程能够使用 2.0 核
  • cpu.stat:CPU 使用的统计数据,nr_periods 表示已经过去的时间周期;nr_throttled 表示 cgroup 中任务被限制使用 CPU 的次数(因为超过了规定的上限);throttled_time 表示被限制的总时间
  • cpu.shares:cgroup 使用 CPU 时间的权重值。如果两个 cgroup 的权重都设置为 100,那么它们里面的任务同时运行时,使用 CPU 的时间应该是一样的;如果把其中一个权重改为 200,那么它能使用的 CPU 时间将是对方的两倍

RT 调度模式下的参数:

RT 调度模式下和 CFS 中上限设置类似,区别是它只是限制实时任务的 CPU

  • cpu.rt_period_us:设置一个周期时间,表示多久 cgroup 能够重新分配 CPU 资源
  • cpu.rt_runtime_us:设置运行时间,表示在周期时间内 cgroup 中任务能访问 CPU 的时间。这个限制是针对单个 CPU 核数的,如果是多核,需要乘以对应的核数

cpuacct: 任务使用CPU情况统计

cpuacct不做任何资源限制,它的功能是资源统计,自动地统计cgroup中任务对CPU资源的使用情况,统计数据也包括子cgroup中的任务

  • cpuacct.usage:该 cgroup 中所有任务(包括子 cgroup 中的任务,下同)总共使用 CPU 的时间,单位是纳秒(ns)。往文件中写入 0 可以重置统计数据
  • cpuacct.stat:该 cgroup 中所有任务使用 CPU 的user 和 system 时间,也就是用户态 CPU 时间和内核态 CPU 时间
  • cpuacct.usage_percpu:该 cgroup 中所有任务使用各个 CPU 核数的时间,单位为纳秒(ns)

cpuset: cpu绑定

除了限制CPU的使用量,cgroup还能把任务绑定到特定的CPU,让它们只运行在这些 CPU上,这就是cpuset子资源的功能。除了CPU之外,还能绑定内存节点(memory node)
在把任务加入到cpuset的task文件之前,用户必须设置cpuset.cpus和cpuset.mems 参数

  • cpuset.cpus:设置cgroup中任务能使用的CPU,格式为逗号(,)隔开的列表,减号(-)可以表示范围。比如,0-2,7 表示CPU第 0,1,2,和7核
  • cpuset.mems:设置cgroup中任务能使用的内存节点,和cpuset.cpus格式一样

上面两个是最常用的参数,cpuset中有很多其他参数,需要对CPU调度机制有深入的了解,这里就不详细介绍

memory:制内存使用

memory子资源系统能限制cgroup中任务对内存的使用,也能生成它们使用数据的报告

控制内存使用

  • memory.limit_in_bytes:cgroup能使用的内存上限值,默认为字节;也可以添加 k/K、m/M 和 g/G 单位后缀。往文件中写入-1来移除设置的上限,表示不对内存做限制
  • memory.memsw.limit_in_bytes:cgroup能使用的内存加swap上限,用法和上面一样。写 -1来移除上限
  • memory.failcnt:任务使用内存量达到limit_in_bytes上限的次数
  • memory.memsw.failcnt:任务使用内存加swap量达到memsw.limit_in_bytes 上限的次数
  • memory.soft_limit_in_bytes:设置内存软上限。如果内存充足, cgroup中的任务可以用到memory.limit_in_bytes设定的内存上限;当时当内存资源不足时,内核会让任务使用的内存不超过soft_limit_in_bytes中的值,文件内容的格式和 limit_in_bytes 一样
  • memory.swappiness:设置内核swap out进程内存(而不是从 page cache 中回收页) 的倾向。默认值为60,低于60表示降低倾向,高于60表示增加倾向;如果值高于100,表示允许内核swap out进程地址空间的页。如果值为0表示倾向很低,而不是禁止该行为

OOM操作

OOM是out of memory的缩写,可以翻译成内存用光。cgroup可以控制内存用完之后应该怎么处理进程,默认情况下用光内存的进程会被杀死
memory.oom_control:是否启动OOM killer,如果启动(值为 0,是默认情况)超过内存限制的进程会被杀死;如果不启动(值为 1),使用超过限定内存的进程不会被杀死,而是被暂停,直到它释放了内存能够被继续使用

统计内存使用情况

  1. memory.stat
    汇报内存的使用情况,里面的数据包括:
  • cache:页缓存(page cache)字节数,包括tmpfs(shmem)
  • rss:匿名和 swap cache 字节数,不包括 tmpfs
  • mapped_file:内存映射(memory-mapped)的文件大小,包括 tmpfs,单位是字节
  • pgpgin: paged into 内存的页数
  • pgpgout:paged out 内存的页数
  • swap:使用的 swap 字节数
  • active_anon:活跃的 LRU 列表中匿名和 swap 缓存的字节数,包括 tmpfs
  • nactive_anon:不活跃的 LRU 列表中匿名和 swap 缓存的字节数,包括 tmpfs
  • active_file:活跃 LRU 列表中文件支持的(file-backed)的内存字节数
  • inactive_file:不活跃列表中文件支持的(file-backed)的内存字节数
  • unevictable:不可以回收的内存字节数
  1. memory.usage_in_bytes:cgroup 中进程当前使用的总内存字节数
  2. memory.memsw.usage_in_bytes:cgroup 中进程当前使用的总内存加上总 swap 字节数
  3. memory.max_usage_in_bytes:cgroup 中进程使用的最大内存字节数
  4. memory.memsw.max_usage_in_bytes:cgroup 中进程使用的最大内存加 swap 字节数

net_cls:为网络报文分类

net_cls子资源能够给网络报文打上一个标记(classid),这样内核的tc(traffic control)模块就能根据这个标记做流量控制
net_cls.classid:包含一个整数值。从文件中读取是的十进制,写入的时候需要是十六进制。比如0x100001写入到文件中,读取的将是1048577, ip命令操作的形式为10:1,
这个值的格式为0xAAAABBBB,一共 32 位,分成前后两个部分,前置的0可以忽略,因此0x10001 和0x00010001 一样,表示为 1:1

net_prio:网络报文优先级

net_prio(Network Priority)子资源能够动态设置cgroup中应用在网络接口的优先级。网络优先级是报文的一个属性值,tc可以设置网络的优先级,socket也可以通过 SO_PRIORITY 选项设置它(但是很少应用会这么做)

  • net_prio.prioidx:只读文件,里面包含了一个整数值,内核用来标识这个 cgroup
  • net_prio.ifpriomap:网络接口的优先级,里面可以包含很多行,用来为从网络接口中发出去的报文设置优先级。每行的格式为 network_interface priority,比如 echo “eth0 5” > /sys/fs/cgroup/net_prio/mycgroup/net_prio.ifpriomap

devices:设备黑白名单

device子资源可以允许或者阻止cgroup中的任务访问某个设备,也就是黑名单和白名单的作用

  • devices.allow
    cgroup 中的任务能够访问的设备列表,格式为
type major:minor access

type 表示类型,可以为 a(all), c(char), b(block)
major:minor 代表设备编号,两个标号都可以用* 代替表示所有,比如 : 代表所有的设备
accss 表示访问方式,可以为 r(read),w(write), m(mknod) 的组合

  • devices.deny:cgroup 中任务不能访问的设备,和上面的格式相同
  • devices.list:列出 cgroup 中设备的黑名单和白名单

freezer

freezer子资源比较特殊,它并不和任何系统资源相关,而是能暂停和恢复cgroup中的任务

  • freezer.state

这个文件值存在于非根cgroup中(因为所有的任务默认都在根cgroup中,停止所有的任务显然是错误的行为),里面的值表示cgroup中进程的状态:
FROZEN:cgroup 中任务都被挂起(暂停)
FREEZING:cgroup 中任务正在被挂起的过程中
THAWED:cgroup 中的任务已经正常恢复

要想挂起某个进程,需要先把它移动到某个有freezer的cgroup中,然后Freeze这个 cgroup

如果某个cgroup处于挂起状态,不能往里面添加任务。用户可以写入FROZEN和 THAWED来控制进程挂起和恢复,FREEZING不受用户控制

总结

cgroup提供了强大的功能,能够让我们控制应用的资源使用情况,也能统计资源使用数据,是容器技术的基础。但是cgroup整个系统也很复杂,甚至显得有些混乱,目前 cgroup整个在被重写,新的版本被称为cgroup V2,而之前的版本也就被称为了V1
cgroup本身并不提供对网络资源的使用控制,只能添加简单的标记和优先级,具体的控制需要借助linux的TC模块来实现

文章作者: 鲜花的主人
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 爱吃可爱多
Linux Linux
喜欢就支持一下吧
打赏
微信 微信
支付宝 支付宝