专注于大数据及容器云核心技术解密,可提供全栈的大数据+云原生平台咨询方案,请持续关注本套博客。如有任何学术交流,可随时联系。更多内容请关注《数据云技术社区》公众号。
1 cgroup 核心概念
- docker 通过 cgroup 来控制容器使用的资源配额,包括 CPU、内存、磁盘三大方面,基本覆盖了常见的资源配额和使用量控制。
- subsystem:基于cgroup的资源管理器,可以看作一种资源(比如CPU,Memory, I/O等),实现对一个cgroup的task的调度和控制
- cgroup:以某种方式,将某些任务和subsystem进行关联。一个subsystem就是一个内核模块,他被关联到一颗cgroup树之后,就会在树的每个节点(进程组)上做具体的操作。subsystem经常被称作"resource controller",因为它主要被用来调度或者限制每个进程组的资源。
- hierarchy:对crgoups和subsystems以某种形式进行的组织,cgroup组织形式是树结构,subsystem会关联连接到hierarchy上。
- 上面的就是一个1个hierarchy,有两个subsystem(cpu和memory) attach上,其中的cgroups以tree的结构组织。
- 上面的就是CPU 和 Memory 两个子系统有自己独立的层级系统, 而又通过 Task 取得关联关系, 该task既有CPU的限制又有Memory的限制。
- 有时我们将进程分组只是为了做一些监控,观察一下他们的状态,比如perf_event subsystem。到目前为止,Linux支持12种subsystem,比如限制CPU的使用时间,限制使用的内存,统计CPU的使用情况,冻结和恢复一组进程等。
1.1 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对内存资源使用情况的报告。
- perf_event 这个subsystem使用后使得cgroup中的task可以进行统一的性能测试。{![perf: Linux CPU性能探测器,详见https://perf.wiki.kernel.org/index.php/Main_Page]}
- *net_cls 这个subsystem Docker没有直接使用,它通过使用等级识别符(classid)标记网络数据包,从而允许 Linux 流量控制程序(TC:Traffic Controller)识别从具体cgroup中生成的数据包。
2 cgroup基本操作
2.1 挂载cgroup
#尝试将cpu subsystem重新关联一颗cgroup树并且将这棵树mount到./cpu目录
dev@ubuntu:~/cgroup$ mkdir cpu
dev@ubuntu:~/cgroup$ sudo mount -t cgroup -o cpu new-cpu ./cpu
#由于name=test的cgroup树在系统中不存在,所以这里会创建一颗新的name=test的cgroup树,并且将这棵树mount到./test目录
dev@ubuntu:~$ mkdir -p cgroup/test && cd cgroup
dev@ubuntu:~/cgroup$ sudo mount -t cgroup -o none,name=test test ./test
#挂载一棵cgroup树,但不关联任何subsystem,下面就是systemd所用到的方式
mkdir /sys/fs/cgroup/systemd
mount -t cgroup -o none,name=systemd xxx /sys/fs/cgroup/systemd
#创建和删除子cgroup
root@nicktming:~/cgroup# cd demo/
root@nicktming:~/cgroup/demo# mkdir cgroup1
root@nicktming:~/cgroup/demo# ls cgroup1/
cgroup.clone_children cgroup.event_control cgroup.procs notify_on_release tasks
root@nicktming:~/cgroup/demo# mkdir cgroup2
root@nicktming:~/cgroup/demo# tree
.
|-- cgroup1
| |-- cgroup.clone_children
| |-- cgroup.event_control
| |-- cgroup.procs
| |-- notify_on_release
| `-- tasks
|-- cgroup2
| |-- cgroup.clone_children
| |-- cgroup.event_control
| |-- cgroup.procs
| |-- notify_on_release
| `-- tasks
|-- cgroup.clone_children
|-- cgroup.event_control
|-- cgroup.procs
|-- cgroup.sane_behavior
|-- notify_on_release
|-- release_agent
`-- tasks
root@nicktming:~/cgroup/demo# sh -c "echo $$ > cgroup1/tasks"
root@nicktming:~/cgroup/demo# cat cgroup1/tasks
11749
14172
root@nicktming:~/cgroup/demo# cat cgroup2/tasks
// 删除子cgroup 直接删除其文件夹即可
root@nicktming:~/cgroup/demo# rmdir cgroup2
// 如果子cgroup中tasks中有进程的时候删除不了, 必须把进程移到别的cgroup中才可以删除
root@nicktming:~/cgroup/demo# rmdir cgroup1
rmdir: failed to remove ‘cgroup1’: Device or resource busy
// 将该进程从cgroup1移到demo cgroup中
root@nicktming:~/cgroup/demo# sh -c "echo 11749 > tasks"
root@nicktming:~/cgroup/demo# cat cgroup1/tasks
root@nicktming:~/cgroup/demo# rmdir cgroup1
root@nicktming:~/cgroup/demo# tree
.
|-- cgroup.clone_children
|-- cgroup.event_control
|-- cgroup.procs
|-- cgroup.sane_behavior
|-- notify_on_release
|-- release_agent
`-- tasks
复制代码
2.2 cgroup资源配额限制
参考:https://blog.51cto.com/wzlinux/2046566
参考:https://www.jianshu.com/p/b02bf3b3f265
参考:https://www.jianshu.com/p/262d86b82e4c
root@nicktming:~# cat memory.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MB (1024 * 1024)
int main(int argc, char *argv[])
{
char *p;
int i = 0;
while(1) {
p = (char *)malloc(MB);
memset(p, 0, MB);
printf("%dM memory allocated\n", ++i);
sleep(1);
}
return 0;
}
root@nicktming:/sys/fs/cgroup/memory# mkdir test-limit-memory && cd test-limit-memory
root@nicktming:/sys/fs/cgroup/memory/test-limit-memory# sh -c "echo $$ > tasks"
root@nicktming:/sys/fs/cgroup/memory/test-limit-memory# sh -c "echo 5M > memory.limit_in_bytes"
root@nicktming:/sys/fs/cgroup/memory/test-limit-memory# cd /root
// 可以看到在加入5M的限制后 运行程序在申请5M内存时候被killed.
root@nicktming:~# gcc memory.c -o memory
root@nicktming:~# ./memory
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
Killed
docker run -itd --name stress polinux/stress:1.0.4 stress --cpu 1 --vm-bytes 200M
1633f77703ac680c6c9ff77ce5072b6c4d239a546151f945c87f57bb7011e17f
cat /sys/fs/cgroup/cpu/docker/1633f77703ac680c6c9ff77ce5072b6c4d239a546151f945c87f57bb7011e17f/cpu.cfs_period_us
100000
cat /sys/fs/cgroup/cpu/docker/1633f77703ac680c6c9ff77ce5072b6c4d239a546151f945c87f57bb7011e17f/cpu.cfs_quota_us
-1
可见没有对stress容器做cpu限制
top命令
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
47864 root 20 0 736 36 0 R 99.3 0.0 3:00.90 stress
cpu吃到约100%
复制代码
3 CPU限制测试
3.1 配置控制组
$ cd /sys/fs/cgroup/cpu
$ mkdir testlimit
$ ls testlimit/
cgroup.clone_children cgroup.procs cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat cpuacct.usage notify_on_release
cgroup.event_control cpu.cfs_period_us cpu.rt_period_us cpu.shares cpuacct.stat cpuacct.usage_percpu tasks
$ cat /sys/fs/cgroup/cpu/testlimit/cpu.cfs_quota_us
-1
$ cat /sys/fs/cgroup/cpu/testlimit/cpu.cfs_period_us
100000
配置一个只能使用30%cpu的限制,即长度为cfs_period的一段时间内,只能被分配到总量为cfs_quota的CPU时间。
echo 30000 > /sys/fs/cgroup/cpu/testlimit/cpu.cfs_quota_us
限制进程在100000us里只能使用30000us的cpu时间。只是目前没有将它应用于任何进程。
复制代码
3.2 执行脚本
- 执行了一个无限循环,可以把cpu吃到100%,可以看到它在后台的进程id是4477
$ while : ; do : ; done &
[1] 4477
通过top查看cpu使用情况:
%Cpu0 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
复制代码
- 使用cgroups限制该进程的cpu
echo 4477 > /sys/fs/cgroup/cpu/testlimit/tasks
通过top查看cpu使用情况
%Cpu0 : 30.1 us, 3.0 sy, 0.0 ni, 65.5 id, 1.0 wa, 0.0 hi, 0.3 si, 0.0 st
复制代码
3.3 容器加上CPU时钟周期限制
$ docker run -td --cpu-period 100000 --cpu-quota 200000 busybox /bin/sh -c "while : ; do : ; done"
c3e3fb30f3cbdcc707dff9f5937018c0ac6b07002d80656760026111c569ca4f
//查看该容器的进程id: 26430
$ ps -x |grep '/bin/sh'
26430 pts/0 Rs+ 20:52 /bin/sh -c while : ; do : ; done
//查看cgroups
$ cat /sys/fs/cgroup/cpu/docker/c3e3fb30f3cbdcc707dff9f5937018c0ac6b07002d80656760026111c569ca4f/cpu.cfs_period_us
100000
$ cat /sys/fs/cgroup/cpu/docker/c3e3fb30f3cbdcc707dff9f5937018c0ac6b07002d80656760026111c569ca4f/cpu.cfs_quota_us
200000
$ cat /sys/fs/cgroup/cpu/docker/c3e3fb30f3cbdcc707dff9f5937018c0ac6b07002d80656760026111c569ca4f/tasks
26430
$ top
%Cpu0 : 50.8 us, 49.2 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
复制代码
4 内存限额
- 容器可使用的内存包括两部分:物理内存和 swap。 Docker 通过下面两组参数来控制容器内存的使用量。
- -m 或 --memory:设置内存的使用限额,例如 100M, 2G。
- --memory-swap:设置 内存+swap 的使用限额。
docker run -m 200M --memory-swap=300M ubuntu
允许该容器最多使用 200M 的内存和 100M 的 swap。默认情况下,上面两组参数为 -1,即对容器内存和 swap 的使用没有限制。
对容器执行压力测试
docker run -it -m 200M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 280M
--vm 1:启动 1 个内存工作线程。
--vm-bytes 280M:每个线程分配 280M 内存。
默认情况下,容器可以使用主机上的所有空闲内存
复制代码
- 分配的内存一旦超过限额,stress 线程就会报错,容器退出
4 Block IO 的限制
4.1 block IO 权重
- Block IO 是另一种可以限制容器使用的资源。Block IO 指的是磁盘的读写,docker 可通过设置权重、限制 bps 和 iops 的方式控制容器读写磁盘的带宽。
- 默认情况下,所有容器能平等地读写磁盘,可以通过设置--blkio-weight参数来改变容器 block IO 的优先级。
- --blkio-weight 与 --cpu-shares 类似,设置的是相对权重值,默认为 500。在下面的例子中,container_A 读写磁盘的带宽是 container_B 的两倍。
docker run -it --name container_A --blkio-weight 600 ubuntu
docker run -it --name container_B --blkio-weight 300 ubuntu
我们可以在 /sys/fs/cgroup/blkio/docker 看到 block IO 的数值。
复制代码
4.2 限制 bps 和 iops
- bps 是 byte per second,每秒读写的数据量。
- iops 是 io per second,每秒 IO 的次数。
- --device-read-bps,限制读某个设备的 bps。
- --device-write-bps,限制写某个设备的 bps。
- --device-read-iops,限制读某个设备的 iops。
- --device-write-iops,限制写某个设备的 iops。
限制容器写 /dev/sda 的速率为 30 MB/s
docker run -it --device-write-bps /dev/sda:30MB ubuntu
复制代码
-
通过 dd 测试在容器中写磁盘的速度。因为容器的文件系统是在 host /dev/sda 上的,在容器中写文件相当于对 host /dev/sda 进行写操作。另外,oflag=direct 指定用 direct IO 方式写文件,这样 --device-write-bps 才能生效。
-
作为对比测试,如果不限速,结果如下:
5 总结
专注于大数据及容器云核心技术解密,可提供全栈的大数据+云原生平台咨询方案,请持续关注本套博客。如有任何学术交流,可随时联系。更多内容请关注《数据云技术社区》公众号。
有疑问加站长微信联系(非本文作者)