1 Cgroups简介
1.1 What are cgroups ?
Cgroups(控制组)是Linux内核的一个功能,用来限制、统计和分离一个进程组的资源(CPU、内存、磁盘输入输出等)。换句话说就是,如果一个进程加入了某一个控制组,该控制组对Linux的系统资源都有严格的限制,进程在使用这些资源时,不能超过其最大的限制数,例如:memory资源,如果加入控制组的进程使用的memory大于其限制,可能会出现OOM错误(关于OOM错误可参看Linux内核OOM机制分析)。cgroup本身提供将进程进行分组化管理的功能和接口的基础结构
控制组提供一种机制,将一组任务和它们的子任务聚合或划分到有特定功能的层级结构(hierarchical)中。在Cgroups中涉及下面的几个概念:
(1)任务(task):在 cgroups 中,任务就是系统的一个进程。
(2)控制族群(control group):控制族群就是一组按照某种资源限制划分的进程。Cgroups 中的资源控制都是以控制族群为单位实现。一个进程可以加入到多个控制族群。假设这里有个进程A,我们要限制其使用的系统内存总量的10%,我们就可以创建一个memory占用10%的cgroups。然后,将进程A添加到cgroups,那么进程A最多占用内存总量的10%,当然,一个cgroups一般情况下不止一个进程。
(3)层级(hierarchy):控制族群可以组织成 hierarchical 的形式,既一颗控制族群树。子控制组自动继承父节点的特定属性,当然子控制组还可以有自己特定的属性。下面是Linux内核关于Cgroups的一种图示:可以看出他的层级表示:
.CPU : "Top cpuset"
/ \
CPUSet1 CPUSet2
| |
(Professors) (Students)
In addition (system tasks) are attached to topcpuset (so
that they can run anywhere) with a limit of 20%
Memory : Professors (50%), Students (30%), system (20%)
Disk : Professors (50%), Students (30%), system (20%)
Network : WWW browsing (20%), Network File System (60%), others (20%)
/ \
Professors (15%) students (5%)
Browsers like Firefox/Lynx go into the WWW network class, while (k)nfsd goes
into the NFS network class.(参考Linux内核的cgroups说明)
(4)Subsystem:子系统是任务组中的一个模块,例如前面的memory就是一个子系统,该子系统是一个内存控制器。在cgroups的框架下,为cgroups提供多种资源限制。(each subsystem has system-specific state attached to each cgroup in the hierarchy. Each hierarchy has an instance of the cgroup virtual filesystem associated with it)。
1.2 Cgroups子系统介绍
(1)blkio --为块设备设定输入/输出限制,比如物理设备(磁盘,固态硬盘,USB 等)。
(2)cpu -- 使用调度程序提供对CPU的cgroup任务访问。
(3)cpuacct --自动生成cgroup中任务所使用的CPU资源报告。
(4)cpuset --为cgroup中的任务分配独立CPU(在多核系统)和内存节点。
(5)devices --可允许或者拒绝中的任务对设备的访问。
(6)freezer --挂起或者恢复cgroup中的任务。
(7)memory --设定 cgroup 中任务使用的内存限制,并自动生成任务使用的内存资源报告。
(8)net_cls--使用等级识别符(classid)标记网络数据包,可允许 Linux 流量控制程序(tc)识别从具体 cgroup 中生成的数据包。
(9)net_prio--允许管理员动态的通过各种应用程序设置网络传输的优先级,类似于socket 选项的SO_PRIORITY,但它有它自身的优势。
(10)HugeTLB--HugeTLB页的资源控制功能
上面的几种子系统特性可以参考Linux的文档cgoups子特性文档。
1.3 Cgroups在Linux中的使用和安装
Cgroups是从Linux内核版本2.6.24开始添加进去的,Cgroups最初是由Google的工程师在2006年开发的,起初的名字叫“process containers”,在2007年改名为“Control Groups”。首先,确定使用的Linux发行版本是否支持该特性,我这里使用的Linux发行版本是ubuntu,可以使用下面的方式查看Ubuntu的版本。
root@ubuntu:~# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu Trusty Tahr (development branch)
Release: 14.04
Codename: trusty
使用下面的命令可以查看Ubuntu使用的Linux内核版本。
root@ubuntu:~# uname -r
3.13.0-12-generic
在我的Ubuntu系统中执行lssubsys �Call查看系统中默认挂载的子系统。检查Linux内核是否支持,可以使用下面的命令:
grep CGROUP /boot/config-`uname -r`
在cgroup-bin_0.38-1ubuntu2_i386版本中的etc目录下已经没有了cgconfig.conf文件,但是我们可以自己创建该配置文件,这样但cggroup启动时,就会读取该配置文件。这个配置文件包括mount , group and default 三个段,这三个段的顺序是任意的,并且都是可选参数,该文件中用#表示的注释。
mount section has this form:
mount {
= ;
...
}
Controller:
表示内核支持的子系统的名字。通过/proc/cgroups文件查看系统中支持子系统。
Path:
子系统的挂载点。
如果没有mount段,则controllers不会被挂载。
group section has this form:
group {
[permissions]
{
= ;
...
}
...
}
Name:
control group的名字,只能包括目录名字允许的字符。
permissions:
可选项,指定cgroup对应的挂载点文件系统的权限
Controller:
内核中支持的子系统的名字。
param name,param value:参数的名字和参数的值。
如果没有group,则就没有group被创建。
Default段如下面的形式:
default {
perm {
task {
uid = ;
gid = ;
fperm =
}
admin {
uid = ;
gid = ;
dperm =
fperm =
}
}
}
下面举两个例子:
mount {#定义需要创建的cgroup子系统及其挂载点,这里创建cpu与cpuacct(统计)两个cgroup子系统
cpu = /mnt/cgroups/cpu;
cpuacct = /mnt/cgroups/cpu;
}
和下面的shell脚本是等价的:
mkdir /mnt/cgroups/cpu
mount -t cgroup -o cpu,cpuacct cpu /mnt/cgroups/c
group daemons/www {
perm {#定义组的权限
task {
uid = root;
gid = webmaster;
fperm = 770;
}
admin {
uid = root;
gid = root;
dperm = 775;
fperm = 744;
}
}
cpu {
cpu.shares = "1000";
}
}
group daemons/ftp {
perm {
task {
uid = root;
gid = ftpmaster;
fperm = 774;
}
admin {
uid = root;
gid = root;
dperm = 755;
fperm = 700;
}
}
cpu {
cpu.shares = "500";
}
}
相当于使用下面的脚本:
mkdir /mnt/cgroups/cpu
mount -t cgroup -o cpu,cpuacct cpu /mnt/cgroups/cpu
mkdir /mnt/cgroups/cpu/daemons
mkdir /mnt/cgroups/cpu/daemons/www
chown root:root /mnt/cgroups/cpu/daemons/www/*
chown root:webmaster /mnt/cgroups/cpu/daemons/www/tasks
echo 1000 > /mnt/cgroups/cpu/daemons/www/cpu.shares
上面的具体内容可以参考,cfconfig.conf文件说明
1.4 用户空间挂载Cgroup
上面我们分析了一下,cgroup的配置文件,这里我们具体分析一下使用命令如何进行配置。我们知道cgroup的实现有一个文件系统的支持,用户空间的配置可以使用cgroup文件系统。首先,我们可以创建一个层级:在创建层级之前我们需要创建挂载的目录,这里使用的目录是/home/ubuntu /test_cgroup
root@ubuntu:~# mount -t cgroup -o cpu cpu_group /home/ubuntu/test_cgroup/
这个命令就是创建了一个test_cgroup的层级,这个层级有一个子系统cpu,并把层级挂载到了/home/ubuntu /test_cgroup目录下。进入该目录可以看到一些和CPU相关的参数。
root@ubuntu:~# cd test_cgroup/
root@ubuntu:~/test_cgroup# ls
cgroup.clone_children cpu.cfs_period_us cpu.shares tasks
cgroup.event_control cpu.cfs_quota_us cpu.stat cgroup.procs cpu.rt_period_us notify_on_release
cgroup.sane_behavior cpu.rt_runtime_us release_agent
然后创建一个cgroup,名字为foo
root@ubuntu:~/test_cgroup# mkdir foo
root@ubuntu:~/test_cgroup# cd foo/
root@ubuntu:~/test_cgroup/foo# ls
cgroup.clone_children cpu.cfs_period_us cpu.rt_runtime_us notify_on_release
cgroup.event_control cpu.cfs_quota_us cpu.shares tasks
cgroup.procs cpu.rt_period_us cpu.stat
这样就在cpu_group层级上创建了一个foo的cgroup,在Foo目录下有一些文件,这些都是CPU相关的属性。其中cat ~/test_cgroup/task可以看到系统中的所有进程ID。这是因为每创建一个层级,系统会把系统中所以的进程ID添加到task中,而在foo/task文件却是空,这个文件用户可以添加。
在/proc/下面有一个cgroups的文件,记录了所有cgroups子系统的状态信息。
root@ubuntu:~/test_cgroup/foo# cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 3 1 1
cpu 4 3 1
cpuacct 5 1 1
memory 6 2 1
devices 7 1 1
freezer 8 1 1
blkio 9 1 1
perf_event 10 1 1
hugetlb 11 1 1
从左到右的条目分别是子系统name,hierarchy ID,子系统的cgroup控制组数目,子系统是否可用(1可用,0不可用)
/proc/mounts文件显示了系统中挂载的文件系统信息,前面几个目录表示文件系统name,挂载点绝对路径,文件系统类型
root@ubuntu:~/test_cgroup/foo# cat /proc/mounts
rootfs / rootfs rw 0 0
sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
udev /dev devtmpfs rw,relatime,size=503368k,nr_inodes=125842,mode=755 0 0
devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0
tmpfs /run tmpfs rw,nosuid,noexec,relatime,size=102556k,mode=755 0 0
/dev/disk/by-uuid/612dacd9-3cf7-471b-92a4-f5c7c6e4e88a / ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
none /sys/fs/cgroup tmpfs rw,relatime,size=4k,mode=755 0 0
none /sys/fs/fuse/connections fusectl rw,relatime 0 0
none /sys/kernel/debug debugfs rw,relatime 0 0
none /sys/kernel/security securityfs rw,relatime 0 0
none /run/lock tmpfs rw,nosuid,nodev,noexec,relatime,size=5120k 0 0
none /run/shm tmpfs rw,nosuid,nodev,relatime 0 0
none /run/user tmpfs rw,nosuid,nodev,noexec,relatime,size=102400k,mode=755 0 0
none /sys/fs/pstore pstore rw,relatime 0 0
systemd /sys/fs/cgroup/systemd cgroup rw,nosuid,nodev,noexec,relatime,name=systemd 0 0
vmware-vmblock /run/vmblock-fuse fuse.vmware-vmblock rw,nosuid,nodev,relatime,user_id=0,group_id=0,default_permissions,allow_other 0 0
gvfsd-fuse /run/user/1000/gvfs fuse.gvfsd-fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
cgroup /sys/fs/cgroup/cpuset cgroup rw,relatime,cpuset 0 0
cgroup /sys/fs/cgroup/cpu cgroup rw,relatime,cpu 0 0
cgroup /sys/fs/cgroup/cpuacct cgroup rw,relatime,cpuacct 0 0
cgroup /sys/fs/cgroup/memory cgroup rw,relatime,memory 0 0
cgroup /sys/fs/cgroup/devices cgroup rw,relatime,devices 0 0
cgroup /sys/fs/cgroup/freezer cgroup rw,relatime,freezer 0 0
cgroup /sys/fs/cgroup/blkio cgroup rw,relatime,blkio 0 0
cgroup /sys/fs/cgroup/perf_event cgroup rw,relatime,perf_event 0 0
cgroup /sys/fs/cgroup/hugetlb cgroup rw,relatime,hugetlb 0 0
gvfsd-fuse /root/.gvfs fuse.gvfsd-fuse rw,nosuid,nodev,relatime,user_id=0,group_id=0 0 0
test_group /home/zfz/cgroup cgroup rw,relatime,cpu 0 0
cpu_group /home/zfz/test_cgroup cgroup rw,relatime,cpu 0 0
1.5 Cgroup使用实例
用lssubsys -al来列出系统支持多少种子系统,和使用ls /sys/fs/cgroup/ (ubuntu)来显示已经挂载的子系统。
如果提示下面的错误信息,则表示系统中还没有安装cgroup.
lssubsys -all
The program 'lssubsys' is currently not installed. You can install it by typing:
需要使用下面的方法进行安装:
root@ubuntu:~# apt-get install cgroup-bin
Reading package lists... Done
再次使用lssubsys列出系统中支持的子系统,根据不同的内核版本,支持的子系统也不相同。
root@ubuntu:~# lssubsys --all
cpuset
cpu
cpuacct
memory
devices
freezer
blkio
perf_event
hugetlb
还有一种情况需要进行确认,确认相应的内核部分是否已经编译。
grep CGROUP /boot/config-`uname -r`
CONFIG_CGROUPS=y
# CONFIG_CGROUP_DEBUG is not set
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_HUGETLB=y
CONFIG_CGROUP_PERF=y
CONFIG_CGROUP_SCHED=y
CONFIG_BLK_CGROUP=y
# CONFIG_DEBUG_BLK_CGROUP is not set
CONFIG_NET_CLS_CGROUP=m
CONFIG_NETPRIO_CGROUP=m
我们可以创建一个cgroup的cpu子系统,创建完成之后就在该目录下有下面的属性文件可以进行设置。
root@ubuntu:/sys/fs/cgroup/cpu/test_cpu# ls
cgroup.clone_children cpu.cfs_period_us cpu.rt_runtime_us notify_on_release
cgroup.event_control cpu.cfs_quota_us cpu.shares tasks
cgroup.procs cpu.rt_period_us cpu.stat
使用cat命令查看其中的默认值。
root@ubuntu:/sys/fs/cgroup/cpu/test_cpu# cat cpu.cfs_quota_us
-1
root@ubuntu:/sys/fs/cgroup/cpu/test_cpu# cat cpu.cfs_period_us
100000
默认挂载在/sys/fs/cgroup/memory,可以通过cfconfig.conf文件查看默认的挂载点。这里有两个小的例子主要是为了测试cgroup对进程使用CPU和memeory的控制,这里使用两个Python脚本来模拟其中的效果。
例子1:设置该进程对CPU的占用率不能超过50%。
root@ubuntu:/sys/fs/cgroup/cpu/test_cpu# echo 50000 > cpu.cfs_quota_us
root@ubuntu:/sys/fs/cgroup/cpu/test_cpu# cat cpu.cfs_quota_us
50000
root@ubuntu:/sys/fs/cgroup/cpu/test_cpu# cat tasks
root@ubuntu:/sys/fs/cgroup/cpu/test_cpu# echo 9477 > tasks
使用的Python程序如下:
root@ubuntu:~# cat python_test_cpu.py
#!/usr/bin/env python
import os
pid_num = os.getpid()
print pid_num
var = 1
count = 1
while var == 1:
count = count + 1
count = 0
在没有把python的进程号添加到tasks中,该进程的CPU占用率接近95%。而把该进程ID添加到tasks中经过持续的观察,该进程的CPU使用率不超出50%,和在cgroup中设置的一致。
例子2:限制内存的使用率
Python代码如下:
root@ubuntu:~# cat python_test_memory.py
#!/usr/bin/env python
import os
import time
pid_num = os.getpid()
print pid_num
var = 1
a = "abcdefgh"
b = "abcdefgh"
c = ""
time.sleep(10)
#while var == 1:
for i in range(1,30000000):
c = "####".join([a,b]) + c
在没有使用cgroups机制的情况下,通过top命令查看该进程占用的memory情况如下:
持续观察一段时间基本维持在40%左右。在使用cgoup机制,限制在10M的情况下,其内存的占有率发生了变化。
root@ubuntu:/sys/fs/cgroup/memory/memory_test# echo 10M > memory.limit_in_bytes
root@ubuntu:/sys/fs/cgroup/memory/memory_test# cat memory.limit_in_bytes
10485760
过一小段时间会出现进程退出的情况,如下:进程被杀掉了。
root@ubuntu:~# ./python_test_memory.py
10608
[8]+ Killed ./python_test_memory.py
Killed
在这里也可以配置内核的OOM机制,默认值是开着呢。
root@ubuntu:/sys/fs/cgroup/memory/memory_test# cat memory.oom_control
oom_kill_disable 0
under_oom 0
在上面我们设置的是memory.limit_in_bytes 值,但在目录下还有一个memory.soft_limit_in_bytes 属性,,该值和 memory.limit_in_bytes 的差异是,memory.soft_limit_in_bytes这个限制并不会阻止进程使用超过限额的内存,只是在系统内存不足时,会优先回收超过限额的进程占用的内存,使之向限定值靠拢
通过上面的分析可以看出来,简单的说Cgroup就是限制了各个不同进程之间使用各种资源的能力。上面两个例子是最常用的CPU和内存,每一个内核子系统都有很多的配置选项信息,其中配置选项的含义可以参考Linux内核文档,Linux的Cgroup子系统解释。Cgroup做到对资源的限制访问,这对于Linux系统上的虚拟化和云计算提供了一种资源配置方式。