Docker cgroup资源限制Linux内核机制深入剖析-Docker商业环境实战

数据云技术社区 · · 632 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

专注于大数据及容器云核心技术解密,可提供全栈的大数据+云原生平台咨询方案,请持续关注本套博客。如有任何学术交流,可随时联系。更多内容请关注《数据云技术社区》公众号。

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 总结

专注于大数据及容器云核心技术解密,可提供全栈的大数据+云原生平台咨询方案,请持续关注本套博客。如有任何学术交流,可随时联系。更多内容请关注《数据云技术社区》公众号。


有疑问加站长微信联系(非本文作者)

本文来自:掘金

感谢作者:数据云技术社区

查看原文:Docker cgroup资源限制Linux内核机制深入剖析-Docker商业环境实战

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

632 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传