Cgroups介绍
Cgroups(Control Groups)最初叫 Process Container,由 Google 工程师(Paul Menage 和 Rohit Seth)于 2006 年提出,后来因为 Container 有多重含义容易引起误解,就在 2007 年更名为 Control Groups,并被整合进 Linux 内核。顾名思义就是把进程放到一个组里面统一加以控制。
Cgroups是Control Groups的缩写,它是Linux 内核的一个特征,在2.6.24被引入,Cgroups用于提供对Linux的进程组进行资源上的限制/统计/隔离等操作的这样一种功能。
cgroups 是 Linux 内核提供的一种机制,这种机制可以根据特定的行为,把一系列系统任务及其子任务整合(或分隔)到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的框架。
https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
过去有一段时间,内核开发者甚至把 namespace 也作为一个 cgroups 的 subsystem 加入进来,也就是说 cgroups 曾经甚至还包含了资源隔离的能力。但是资源隔离会给 cgroups 带来许多问题,如 PID 在循环出现的时候 cgroup 却出现了命名冲突、cgroup 创建后进入新的 namespace 导致脱离了控制等等。所以在 2011 年就被移除了。
CGroups特点
cgroups 的 API 以一个伪文件系统的方式实现,即用户可以通过文件操作实现 cgroups 的组织管理。
subsystem(子系统)
subsystem 实际上就是 cgroups 的资源控制系统,每种 subsystem 独立地控制一种资源。
blkio: 这个 subsystem 可以为块设备设定输入 / 输出限制,比如物理驱动设备(包括磁盘、固态硬盘、USB 等)。
cpu: 这个 subsystem 使用调度程序控制 task 对 CPU 的使用。
cpuacct: 这个 subsystem 自动生成 cgroup 中 task 对 CPU 资源使用情况的报告。
cpuset: 这个 subsystem 可以为 cgroup 中的 task 分配独立的 CPU(此处针对多处理器系统)和内存。
devices: 这个 subsystem 可以开启或关闭 cgroup 中 task 对设备的访问。
freezer:这个 subsystem 可以挂起或恢复 cgroup 中的 task。
memory:这个 subsystem 可以设定 cgroup 中 task 对内存使用量的限定,并且自动生成这些 task 对内存资源使用情况的报告。
perfevent:这个 subsystem 使用后使得 cgroup 中的 task 可以进行统一的性能测试。
net_cls: 这个 subsystem Docker 没有直接使用,它通过使用等级识别符 (classid) 标记网络数据包,从而允许 Linux 流量控制程序(TC:Traffic Controller)识别从具体 cgroup 中生成的数据包。
实验案例
# cd /sys/fs/cgroup/cpu
# mkdir testcpu
# ls testcpu/
cgroup.clone_children cpuacct.stat cpu.cfs_period_us cpu.rt_runtime_us notify_on_release
cgroup.event_control cpuacct.usage cpu.cfs_quota_us cpu.shares tasks
cgroup.procs cpuacct.usage_percpu cpu.rt_period_us cpu.stat
# grep -c 'processor' /proc/cpuinfo # 查看CPU核数
/dev/zero在类UNIX 操作系统中, /dev/zero 是一个特殊的文件,当你读它的时候,它会提供无限的空字符(NULL, ASCII NUL, 0x00)。
# dd if=/dev/zero of=/dev/null & # 启动1个进程占满CPU
设定进程组CPU使用配额为30%, 这个值需要调整/sys/fs/cgroup/cpu/testcpu/cpu.cfs_quota_us来实现,。
# cat /sys/fs/cgroup/cpu/testcpu/cpu.cfs_quota_us
-1
# echo 30000 > /sys/fs/cgroup/cpu/testcpu/cpu.cfs_quota_us
# cat /sys/fs/cgroup/cpu/testcpu/cpu.cfs_quota_us
30000
设定死循环所在进程5782为限制对象,通过调整tasks的内容来实现,只需要将PID写入该文件即可。
# cat /sys/fs/cgroup/cpu/testcpu/tasks #空的
# echo 5782 >/sys/fs/cgroup/cpu/testcpu/tasks
# ps -ef|grep "/dev/zero"|grep -v grep|awk '{print $2}'|xargs kill #杀掉进程
# rmdir /sys/fs/cgroup/cpu/testcpu #删除测试目录
实验案例
https://docs.docker.com/engine/reference/commandline/run/
Docker通过下面两组参数来控制容器内存的使用量
docker run -it -m 200M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 290M
启动一个大于300M的线程
docker run -it -m 200M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 310M
Docker通过下面两组参数来控制容器CPU的使用量
绑定到0 CPU上面
docker run -it --cpuset-cpus="0" --name Centos centos /bin/bash
dd if=/dev/zero of=/dev/null &
配额
docker run -it --cpuset-cpus="0" --cpu-quota=50000 --name Centos centos /bin/bash
dd if=/dev/zero of=/dev/null &
份额
默认情况下,所有的容器得到同等比例的 CPU 周期。在有多个容器竞争 CPU 时我们可以设置每个容器能使用的 CPU 时间比例。这个比例叫作共享权值,通过-c或--cpu-shares设置。Docker 默认每个容器的权值为 1024。不设置或将其设置为 0,都将使用这个默认值。系统会根据每个容器的共享权值和所有容器共享权值和比例来给容器分配 CPU 时间。
docker run -it --cpuset-cpus="0" -c 1024 --cpu-quota=100000 --name Centos centos /bin/bash
docker run -it --cpuset-cpus="0" -c 2048 --cpu-quota=100000 --name Centos1 centos /bin/bash
CFS(完全公平调度器)
CFS是Completely Fair Scheduler简称,即完全公平调度器。CFS的设计理念是在真实硬件上实现理想的、精确的多任务CPU。CFS调度器和以往的调度器不同之处在于没有时间片的概念,而是分配cpu使用时间的比例。例如:2个相同优先级的进程在一个cpu上运行,那么每个进程都将会分配50%的cpu运行时间。这就是要实现的公平。
以上举例是基于同等优先级的情况下。但是现实却并非如此,有些任务优先级就是比较高。那么CFS调度器的优先级是如何实现的呢?首先,我们引入权重的概念,权重代表着进程的优先级。各个进程之间按照权重的比例分配cpu时间。例如:2个进程A和B。A的权重是1024,B的权重是2048。那么A获得cpu的时间比例是1024/(1024+2048) = 33.3%。B进程获得的cpu时间比例是2048/(1024+2048)=66.7%。我们可以看出,权重越大分配的时间比例越大,相当于优先级越高。在引入权重之后,分配给进程的时间计算公式如下:
分配给进程的时间 = 总的cpu时间 * 进程的权重/就绪队列(runqueue)所有进程权重之和
CFS 默认的调度周期是 100ms。
设置每个容器进程的调度周期,以及在这个周期内各个容器最多能使用多少 CPU 时间。
--cpu-period 设置调度周期,
--cpu-quota 设置在每个周期内容器能使用的 CPU 时间。
例如:
docker run -it --cpu-period=50000 --cpu-quota=25000 Centos centos /bin/bash
将 CFS 调度的周期设为 50000,将容器在每个周期内的 CPU 配额设置为 25000,表示该容器每 50ms 可以得到 50% 的 CPU 运行时间。