Cgroup和Namespace在测试中的使用(上)
很多时候需要测试程序在资源受限情况下的表现,普通的做法可能是不断对系统加压使能够分配给目标程序的资源变少,换另一个思路思考,可以尝试限制分配给目标程序的资源总数,使得机器状态健康的情况下让程序资源使用达到饱和。
作为一个正在做着容器项目的人,知道容器技术是依靠Cgroup和Namespace来实现的。在容器中,cpu和内存资源是使用Cgroup来控制,PID、IPC、网络等资源是通过Namespace来划分。在程序没有部署在容器的情况下,我们仍可以利用Cgoup和Namespace来构造场景完成一些异常测试,如利用Cgroup的资源控制功能做资源满载的测试;利用Namespace的资源隔离特性做一些网络异常测试而不影响其他程序的运行。
Cgroup介绍
Cgroup是进行分组化管理的Linux内核功能,具体的资源管理是通过子系统来完成的。可以理解为子系统就是资源控制器,每种子系统就是一个资源的分配器,比如cpu子系统是控制cpu时间分配的,使用方式如下
安装(ubuntu)
#apt-get install cgroup-bin
基本命令
cgclassify -- cgclassify命令是用来将运行的任务移动到一个或者多个cgroup。
cgclear -- cgclear 命令是用来删除层级中的所有cgroup。
cgconfig.conf -- 在cgconfig.conf文件中定义cgroup。
cgconfigparser -- cgconfigparser命令解析cgconfig.conf文件和并挂载层级。
cgcreate -- cgcreate在层级中创建新cgroup。
cgdelete -- cgdelete命令删除指定的cgroup。
cgexec -- cgexec命令在指定的cgroup中运行任务。
cgget -- cgget命令显示cgroup参数。
cgred.conf -- cgred.conf是cgred服务的配置文件。
cgrules.conf -- cgrules.conf 包含用来决定何时任务术语某些 cgroup的规则。
cgrulesengd -- cgrulesengd 在 cgroup 中发布任务。
cgset -- cgset 命令为 cgroup 设定参数。
lscgroup -- lscgroup 命令列出层级中的 cgroup。
lssubsys -- lssubsys 命令列出包含指定子系统的层级
子系统说明
可以使用lssubsys -a来列出系统支持多少种子系统,和:比如cpu是控制cpu时间片的,memory是控制内存使用的
#lssubsys -a
cpuset
cpu,cpuacct
memory
devices
freezer
net_cls,net_prio
blkio
perf_event
hugetlb
主要的几种子系统说明如下:
blkio 这个子系统设置限制每个块设备的输入输出控制。例如:磁盘,光盘以及usb等等。
cpu 这个子系统使用调度程序为cgroup任务提供cpu的访问。
cpuacct 产生cgroup任务的cpu资源报告。
cpuset 如果是多核心的cpu,这个子系统会为cgroup任务分配单独的cpu和内存。
devices 允许或拒绝cgroup任务对设备的访问。
freezer 暂停和恢复cgroup任务。
memory 设置每个cgroup的内存限制以及产生内存资源报告。
net_cls 标记每个网络包以供cgroup方便使用。
ns 名称空间子系统
perf_event: 增加了对每group的监测跟踪的能力,即可以监测属于某个特定的group的所有线程以及运行在特定CPU上的线程
要为Cgroup分配限制的资源,首先要挂载子系统,然后才有控制组,比如想要对目标程序进行内存限制,那就需要挂载memory子系统
使用lssubsys -am来显示已经挂载的子系统
#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
可以手动挂载或者卸载子系统,如执行umount /sys/fs/cgroup/memory
,memory子系统就被卸载了,这时候手动执行# mount -t cgroup -o memory memory /sys/fs/cgroup/memory
就又挂载上了。
要确保需要的子系统都挂上了,不然创建控制组的时候会报错 is not mounted
#cgcreate -g memory,cpu:/hzmali_test
cgcreate: can‘t create cgroup /hzmali_test: Cgroup one of the needed subsystems is not mounted
如何创建control group(即需要资源管理的组)呢, 这里用cgcreate命令,当然也有其他方法, 如cgconfig.conf等
#cgcreate -g memory,cpu:/hzmali_test
这里有个重要特性:一个组可以同时做多个资源的限制,如这里我同时限制了memory和cpu,然后memory和cpu子系统目录下会自动生成这个组的目录和些文件,如memory
#/sys/fs/cgroup/memory/hzmali_test$ ls -lrt
-rw-r--r-- 1 root root 0 Jul 26 20:56 tasks
-rw-r--r-- 1 root root 0 Jul 26 20:56 notify_on_release
-rw-r--r-- 1 root root 0 Jul 26 20:56 memory.use_hierarchy
-r--r--r-- 1 root root 0 Jul 26 20:56 memory.usage_in_bytes
-rw-r--r-- 1 root root 0 Jul 26 20:56 memory.swappiness
-r--r--r-- 1 root root 0 Jul 26 20:56 memory.stat
-rw-r--r-- 1 root root 0 Jul 26 20:56 memory.soft_limit_in_bytes
---------- 1 root root 0 Jul 26 20:56 memory.pressure_level
-rw-r--r-- 1 root root 0 Jul 26 20:56 memory.oom_control
-r--r--r-- 1 root root 0 Jul 26 20:56 memory.numa_stat
-rw-r--r-- 1 root root 0 Jul 26 20:56 memory.move_charge_at_immigrate
-rw-r--r-- 1 root root 0 Jul 26 20:56 memory.max_usage_in_bytes
-rw-r--r-- 1 root root 0 Jul 26 20:56 memory.limit_in_bytes
-r--r--r-- 1 root root 0 Jul 26 20:56 memory.kmem.usage_in_bytes
-r--r--r-- 1 root root 0 Jul 26 20:56 memory.kmem.tcp.usage_in_bytes
-rw-r--r-- 1 root root 0 Jul 26 20:56 memory.kmem.tcp.max_usage_in_bytes
-rw-r--r-- 1 root root 0 Jul 26 20:56 memory.kmem.tcp.limit_in_bytes
-rw-r--r-- 1 root root 0 Jul 26 20:56 memory.kmem.tcp.failcnt
-r--r--r-- 1 root root 0 Jul 26 20:56 memory.kmem.slabinfo
-rw-r--r-- 1 root root 0 Jul 26 20:56 memory.kmem.max_usage_in_bytes
-rw-r--r-- 1 root root 0 Jul 26 20:56 memory.kmem.limit_in_bytes
-rw-r--r-- 1 root root 0 Jul 26 20:56 memory.kmem.failcnt
--w------- 1 root root 0 Jul 26 20:56 memory.force_empty
-rw-r--r-- 1 root root 0 Jul 26 20:56 memory.failcnt
-rw-r--r-- 1 root root 0 Jul 26 20:56 cgroup.procs
--w--w--w- 1 root root 0 Jul 26 20:56 cgroup.event_control
-rw-r--r-- 1 root root 0 Jul 26 20:56 cgroup.clone_children
文件很多,选几个重要的讲下:
- tasks 可以将想要限制资源的进程都加到这个文件中
- memory.max_usage_in_bytes内存的最大使用量,用来限制资源
-memory.soft_limit_in_bytes 和 memory.limit_in_bytes 的差异是,这个限制并不会阻止进程使用超过限额的内存,只是在系统内存不足时,会优先回收超过限额的进程占用的内存,使之向限定值靠拢。 - memory.oom_control
包含一个标志(0或1)来开启或者关闭cgroup的OOM killer。如果开启(1),任务如果尝试申请内存超过允许,就会被系统OOM killer终止。OOM killer在每个使用cgroup内存子系统中都是默认开启的。如果需要关闭,则可以向memory.oom_control文件写入1:
# echo 1 > /sys/fs/cgroup/memory.oom_control
如果OOM killer关闭,那么进程尝试申请的内存超过允许,那么它就会被暂停,直到额外的内存被释放
- memory.mem.usage_in_bytes 当前进程内存用量,因为现在还没有进程加到组里,就是0了
- memory.mem.failcnt显示内存达到限制值的次数
Cgroup文档
Cgroup的使用细节,子系统和参数设置都可以可以在https://www.kernel.org/doc/Documentation/cgroups/中找到,继承等特性由于篇幅所限,可以看下文档
Cgroup实战
内存限制测试
用控制组限制目标程序内存使用为1000000 byte,当然,需要root执行
echo "1000000" >memory.limit_in_bytes
一般更推荐用cgset来设置数值
cgset -r memory.limit_in_bytes=1000000 hzmali_test
然后构造一个吃内存的程序,每运行一次内存使用就大幅增加
#vim memtest.sh
x="hahaha"
while [ True ];do
x=$x$x$x$x$x$x$x$x$x$x
sleep 1
done;
然后运行程序,并将进程pid写入mem下面控制组的tasks中
#./memtest.sh &
[1] 17638
# echo 17638 > /sys/fs/cgroup/memory/hzmali_test/tasks
使用cgclassify 可以将运行中的进程加到task中,如果控制组有多个资源的控制,使用命令会比echo方便很多
cgclassify -g mem:hzmali_test 17638
然后这货就在不断占内存,由于没有设置disable oom killing,所以最后会oom被kill掉
# cat /sys/fs/cgroup/memory/hzmali_test/memory.usage_in_bytes
966656
# cat /sys/fs/cgroup/memory/hzmali_test/memory.usage_in_bytes
978944
# cat /sys/fs/cgroup/memory/hzmali_test/memory.usage_in_bytes
995328
#
[1]+ Killed ./memtest.sh
CPU限制测试
我的机器上有2个核
%Cpu0 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
写个死循环脚本cpu_test.sh跑一下
x=a
while [ True ];do
x=$x
done;
如果我不想把机器跑死,这里想要限制组里的进程的CPU使用,有2种做法
1.在cpu子系统中控制cpu调度的配额
先看下当前cpu分配情况
cat /sys/fs/cgroup/cpu/hzmali_test/cpu.cfs_quota_us
-1
cat /sys/fs/cgroup/cpu/hzmali_test/cpu.cfs_period_us
100000
-1表示无限制,这里改为50000,即相对于cpu.cfs_period_us 来说为50000/100000约占1个核50%的cpu时间
#./cpu_test.sh &
[1] 17709
# echo 17709 >/sys/fs/cgroup/cpu/hzmali_test/tasks
或者直接使用命令cgexec执行
cgexec -g cpu:hzmali_test ./cpu_test.sh
top了下基本上就是在50%的cpu占用
%Cpu0 : 50.5 us, 0.0 sy, 0.0 ni, 49.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 17709 root 20 0 25368 2020 1764 R 50.2 0.1 1:14.74 bash
2.在cpuset控制物理cpu的分配
当前使用了上面的方法后,我们发现进程的CPU使用都在Cpu0上,这次希望只用Cpu1来跑这个小程序
所以把控制组也加到cpuset
# cgcreate -g cpuset:/hzmali_test
看一下现在使用的cpu的设置
# cat /sys/fs/cgroup/cpuset/hzmali_test/cpuset.cpus
0-1
改为只用Cpu1,输入以下命令
# echo 1 > /sys/fs/cgroup/cpuset/hzmali_test/cpuset.cpus
# echo 17709 > /sys/fs/cgroup/cpuset/hzmali_test/tasks
或用命令
# cgset -r cpuset.cpus=‘1‘ hzmali_test
# cgclassify -g cpu,cpuset:hzmali_test 17709
top一下,内存的使用从CPU0到CPU1了
%Cpu0 : 0.0 us, 0.0 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.3 si, 0.0 st
%Cpu1 : 50.3 us, 0.0 sy, 0.0 ni, 49.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
17709 root 20 0 25368 2108 2076 R 50.1 0.1 8:56.78 bash
IO限制测试
用dd对硬盘进行写操作
# dd if=/dev/sda of=/dev/null &
打开iotop看下IO速度
Total DISK READ : 100.37 M/s | Total DISK WRITE : 0.00 B/s
Actual DISK READ: 100.37 M/s | Actual DISK WRITE: 0.00 B/s
TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
18081 be/4 root 100.37 M/s 0.00 B/s 0.00 % 1.34 % dd if=/dev/sda of=/dev/null
为了控制IO速度,在blkio上创建控制组
# cgcreate -g blkio:/hzmali_test
查看下硬盘号
# ls -l /dev/sda
brw-rw---- 1 root disk 8, 0 Jul 25 22:46 /dev/sda
设置硬盘号和对应的读取速度限制,然后执行同样的命令
# cgset -r blkio.throttle.read_bps_device="8:0 1000000" hzmali_test
# cgexec -g blkio:hzmali_test "dd if=/dev/sda of=/dev/null"
用iotop查看下,速度果然就降到1M以下
Total DISK READ : 996.55 K/s | Total DISK WRITE : 0.00 B/s
Actual DISK READ: 996.55 K/s | Actual DISK WRITE: 0.00 B/s
TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
18188 be/4 root 996.55 K/s 0.00 B/s 0.00 % 99.99 % dd if=/dev/sda of=/dev/null