原文: http://coolshell.cn/articles/17049.htmlhtml
大名鼎鼎的左耳朵耗子的文章, 很是浅显的介绍了linux cgroup技术, 看完以后必定让你明白cgroup技术linux
前面,咱们介绍了Linux Namespace,可是Namespace解决的问题主要是环境隔离的问题,这只是虚拟化中最最基础的一步,咱们还须要解决对计算机资源使用上的隔离。也就是说,虽然你经过Namespace把我Jail到一个特定的环境中去了,可是我在其中的进程使用用CPU、内存、磁盘等这些计算资源其实仍是能够为所欲为的。因此,咱们但愿对进程进行资源利用上的限制或控制。这就是Linux CGroup出来了的缘由。nginx
Linux CGroup全称Linux Control Group, 是Linux内核的一个功能,用来限制,控制与分离一个进程组群的资源(如CPU、内存、磁盘输入输出等)。这个项目最先是由Google的工程师在2006年发起(主要是Paul Menage和Rohit Seth),最先的名称为进程容器(process containers)。在2007年时,由于在Linux内核中,容器(container)这个名词太过普遍,为避免混乱,被重命名为cgroup,而且被合并到2.6.24版的内核中去。而后,其它开始了他的发展。shell
Linux CGroupCgroup 可让您为系统中所运行任务(进程)的用户定义组群分配资源 — 比如 CPU 时间、系统内存、网络带宽或者这些资源的组合。您可以监控您配置的 cgroup,拒绝 cgroup 访问某些资源,甚至在运行的系统中动态配置您的 cgroup。ubuntu
主要提供了以下功能:缓存
使用 cgroup,系统管理员可更具体地控制对系统资源的分配、优先顺序、拒绝、管理和监控。可更好地根据任务和用户分配硬件资源,提高总体效率。bash
在实践中,系统管理员通常会利用CGroup作下面这些事(有点像为某个虚拟机分配资源似的):网络
那么CGroup是怎么干的呢?咱们先来点感性认识吧。dom
首先,Linux把CGroup这个事实现成了一个file system,你能够mount。在个人Ubuntu 14.04下,你输入如下命令你就能够看到cgroup已为你mount好了。ide
hchen@ubuntu:~$ mount -t cgroup cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,relatime,cpuset) cgroup on /sys/fs/cgroup/cpu type cgroup (rw,relatime,cpu) cgroup on /sys/fs/cgroup/cpuacct type cgroup (rw,relatime,cpuacct) cgroup on /sys/fs/cgroup/memory type cgroup (rw,relatime,memory) cgroup on /sys/fs/cgroup/devices type cgroup (rw,relatime,devices) cgroup on /sys/fs/cgroup/freezer type cgroup (rw,relatime,freezer) cgroup on /sys/fs/cgroup/blkio type cgroup (rw,relatime,blkio) cgroup on /sys/fs/cgroup/net_prio type cgroup (rw,net_prio) cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,net_cls) cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,relatime,perf_event) cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,relatime,hugetlb)
或者使用lssubsys命令:
$ lssubsys -m cpuset /sys/fs/cgroup/cpuset cpu /sys/fs/cgroup/cpu cpuacct /sys/fs/cgroup/cpuacct memory /sys/fs/cgroup/memory devices /sys/fs/cgroup/devices freezer /sys/fs/cgroup/freezer blkio /sys/fs/cgroup/blkio net_cls /sys/fs/cgroup/net_cls net_prio /sys/fs/cgroup/net_prio perf_event /sys/fs/cgroup/perf_event hugetlb /sys/fs/cgroup/hugetlb
咱们能够看到,在/sys/fs下有一个cgroup的目录,这个目录下还有不少子目录,好比: cpu,cpuset,memory,blkio……这些,这些都是cgroup的子系统。分别用于干不一样的事的。
若是你没有看到上述的目录,你能够本身mount,下面给了一个示例:
mkdir cgroup mount -t tmpfs cgroup_root ./cgroup mkdir cgroup/cpuset mount -t cgroup -ocpuset cpuset ./cgroup/cpuset/ mkdir cgroup/cpu mount -t cgroup -ocpu cpu ./cgroup/cpu/ mkdir cgroup/memory mount -t cgroup -omemory memory ./cgroup/memory/
一旦mount成功,你就会看到这些目录下就有好文件了,好比,以下所示的cpu和cpuset的子系统:
hchen@ubuntu:~$ ls /sys/fs/cgroup/cpu /sys/fs/cgroup/cpuset/ /sys/fs/cgroup/cpu: cgroup.clone_children cgroup.sane_behavior cpu.shares release_agent cgroup.event_control cpu.cfs_period_us cpu.stat tasks cgroup.procs cpu.cfs_quota_us notify_on_release user /sys/fs/cgroup/cpuset/: cgroup.clone_children cpuset.mem_hardwall cpuset.sched_load_balance cgroup.event_control cpuset.memory_migrate cpuset.sched_relax_domain_level cgroup.procs cpuset.memory_pressure notify_on_release cgroup.sane_behavior cpuset.memory_pressure_enabled release_agent cpuset.cpu_exclusive cpuset.memory_spread_page tasks cpuset.cpus cpuset.memory_spread_slab user cpuset.mem_exclusive cpuset.mems
你能够到/sys/fs/cgroup的各个子目录下去make个dir,你会发现,一旦你建立了一个子目录,这个子目录里又有不少文件了。
hchen@ubuntu:/sys/fs/cgroup/cpu$ sudo mkdir haoel [sudo] password for hchen: hchen@ubuntu:/sys/fs/cgroup/cpu$ ls ./haoel cgroup.clone_children cgroup.procs cpu.cfs_quota_us cpu.stat tasks cgroup.event_control cpu.cfs_period_us cpu.shares notify_on_release
好了,咱们来看几个示例。
假设,咱们有一个很是吃CPU的程序,叫deadloop,其源码以下:
int main(void) { int i = 0; for(;;) i++; return 0; }
用sudo执行起来后,毫无疑问,CPU被干到了100%(下面是top命令的输出)
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 3529 root 20 0 4196 736 656 R 99.6 0.1 0:23.13 deadloop
而后,咱们这前不是在/sys/fs/cgroup/cpu下建立了一个haoel的group。咱们先设置一下这个group的cpu利用的限制:
hchen@ubuntu:~# cat /sys/fs/cgroup/cpu/haoel/cpu.cfs_quota_us -1 root@ubuntu:~# echo 20000 > /sys/fs/cgroup/cpu/haoel/cpu.cfs_quota_us
咱们看到,这个进程的PID是3529,咱们把这个进程加到这个cgroup中:
# echo 3529 >> /sys/fs/cgroup/cpu/haoel/tasks
而后,就会在top中看到CPU的利用立马降低成20%了。(前面咱们设置的20000就是20%的意思)
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 3529 root 20 0 4196 736 656 R 19.9 0.1 8:06.11 deadloop
下面的代码是一个线程的示例:
#define _GNU_SOURCE /* See feature_test_macros(7) */ #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <sys/syscall.h> const int NUM_THREADS = 5; void *thread_main(void *threadid) { /* 把本身加入cgroup中(syscall(SYS_gettid)为获得线程的系统tid) */ char cmd[128]; sprintf(cmd, "echo %ld >> /sys/fs/cgroup/cpu/haoel/tasks", syscall(SYS_gettid)); system(cmd); sprintf(cmd, "echo %ld >> /sys/fs/cgroup/cpuset/haoel/tasks", syscall(SYS_gettid)); system(cmd); long tid; tid = (long)threadid; printf("Hello World! It's me, thread #%ld, pid #%ld!\n", tid, syscall(SYS_gettid)); int a=0; while(1) { a++; } pthread_exit(NULL); } int main (int argc, char *argv[]) { int num_threads; if (argc > 1){ num_threads = atoi(argv[1]); } if (num_threads<=0 || num_threads>=100){ num_threads = NUM_THREADS; } /* 设置CPU利用率为50% */ mkdir("/sys/fs/cgroup/cpu/haoel", 755); system("echo 50000 > /sys/fs/cgroup/cpu/haoel/cpu.cfs_quota_us"); mkdir("/sys/fs/cgroup/cpuset/haoel", 755); /* 限制CPU只能使用#2核和#3核 */ system("echo \"2,3\" > /sys/fs/cgroup/cpuset/haoel/cpuset.cpus"); pthread_t* threads = (pthread_t*) malloc (sizeof(pthread_t)*num_threads); int rc; long t; for(t=0; t<num_threads; t++){ printf("In main: creating thread %ld\n", t); rc = pthread_create(&threads[t], NULL, thread_main, (void *)t); if (rc){ printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1); } } /* Last thing that main() should do */ pthread_exit(NULL); free(threads); }
咱们再来看一个限制内存的例子(下面的代码是个死循环,其它不断的分配内存,每次512个字节,每次休息一秒):
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> int main(void) { int size = 0; int chunk_size = 512; void *p = NULL; while(1) { if ((p = malloc(p, chunk_size)) == NULL) { printf("out of memory!!\n"); break; } memset(p, 1, chunk_size); size += chunk_size; printf("[%d] - memory is allocated [%8d] bytes \n", getpid(), size); sleep(1); } return 0; }
而后,在咱们另一边:
# 建立memory cgroup $ mkdir /sys/fs/cgroup/memory/haoel $ echo 64k > /sys/fs/cgroup/memory/haoel/memory.limit_in_bytes # 把上面的进程的pid加入这个cgroup $ echo [pid] > /sys/fs/cgroup/memory/haoel/tasks
你会看到,一会上面的进程就会由于内存问题被kill掉了。
咱们先看一下咱们的硬盘IO,咱们的模拟命令以下:(从/dev/sda1上读入数据,输出到/dev/null上)
sudo dd if=/dev/sda1 of=/dev/null
咱们经过iotop命令咱们能够看到相关的IO速度是55MB/s(虚拟机内):
TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND 8128 be/4 root 55.74 M/s 0.00 B/s 0.00 % 85.65 % dd if=/de~=/dev/null...
而后,咱们先建立一个blkio(块设备IO)的cgroup
mkdir /sys/fs/cgroup/blkio/haoel
并把读IO限制到1MB/s,并把前面那个dd命令的pid放进去(注:8:0 是设备号,你能够经过ls -l /dev/sda1得到):
root@ubuntu:~# echo '8:0 1048576' > /sys/fs/cgroup/blkio/haoel/blkio.throttle.read_bps_device root@ubuntu:~# echo 8128 > /sys/fs/cgroup/blkio/haoel/tasks
再用iotop命令,你立刻就能看到读速度被限制到了1MB/s左右。
TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND 8128 be/4 root 973.20 K/s 0.00 B/s 0.00 % 94.41 % dd if=/de~=/dev/null...
好了,有了以上的感性认识咱们来,咱们来看看control group有哪些子系统:
注意,你可能在Ubuntu 14.04下看不到net_cls和net_prio这两个cgroup,你须要手动mount一下:
$ sudo modprobe cls_cgroup $ sudo mkdir /sys/fs/cgroup/net_cls $ sudo mount -t cgroup -o net_cls none /sys/fs/cgroup/net_cls $ sudo modprobe netprio_cgroup $ sudo mkdir /sys/fs/cgroup/net_prio $ sudo mount -t cgroup -o net_prio none /sys/fs/cgroup/net_prio
关于各个子系统的参数细节,以及更多的Linux CGroup的文档,你能够看看下面的文档:
CGroup有下述术语:
上面,咱们能够看到,CGroup的一些经常使用方法和相关的术语。通常来讲,这样的设计在通常状况下仍是没什么问题的,除了操做上的用户体验不是很好,但基本知足咱们的通常需求了。
不过,对此,有个叫Tejun Heo的同窗很是不爽,他在Linux社区里对cgroup吐了一把槽,还引起了内核组的各类讨论。
对于Tejun Heo同窗来讲,cgroup设计的至关糟糕。他给出了些例子,大意就是说,若是有多种层级关系,也就是说有多种对进程的分类方式,好比,咱们能够按用户来分,分红Professor和Student,同时,也有按应用相似来分的,好比WWW和NFS等。那么,当一个进程便是Professor的,也是WWW的,那么就会出现多层级正交的状况,从而出现对进程上管理的混乱。另外,一个case是,若是有一个层级A绑定cpu,而层级B绑定memory,还有一个层级C绑定cputset,而有一些进程有的须要AB,有的须要AC,有的须要ABC,管理起来就至关不易。
层级操做起来比较麻烦,并且若是层级变多,更不易于操做和管理,虽然那种方式很好实现,可是在使用上有不少的复杂度。你能够想像一个图书馆的图书分类问题,你能够有各类不一样的分类,分类和图书就是一种多对多的关系。
因此,在Kernel 3.16后,引入了unified hierarchy的新的设计,这个东西引入了一个叫__DEVEL__sane_behavior的特性(这个名字很明显意味目前还在开发试验阶段),它能够把全部子系统都挂载到根层级下,只有叶子节点能够存在tasks,非叶子节点只进行资源控制。
咱们mount一下看看:
$ sudo mount -t cgroup -o __DEVEL__sane_behavior cgroup ./cgroup $ ls ./cgroup cgroup.controllers cgroup.procs cgroup.sane_behavior cgroup.subtree_control $ cat ./cgroup/cgroup.controllers cpuset cpu cpuacct memory devices freezer net_cls blkio perf_event net_prio hugetlb
咱们能够看到有四个文件,而后,你在这里mkdir一个子目录,里面也会有这四个文件。上级的cgroup.subtree_control控制下级的cgroup.controllers。
举个例子:假设咱们有如下的目录结构,b表明blkio,m代码memory,其中,A是root,包括全部的子系统()。
# A(b,m) - B(b,m) - C (b) # \ - D (b) - E # 下面的命令中, +表示enable, -表示disable # 在B上的enable blkio # echo +blkio > A/cgroup.subtree_control # 在C和D上enable blkio # echo +blkio > A/B/cgroup.subtree_control # 在B上enable memory # echo +memory > A/cgroup.subtree_control
在上述的结构中,
咱们能够看到,这种方式干净的区分开了两个事,一个是进程的分组,一个是对分组的资源控制(之前这两个事彻底混在一块儿),在目录继承上增长了些限制,这样能够避免一些模棱两可的状况。
固然,这个事还在演化中,cgroup的这些问题这个事目前由cgroup的吐槽人Tejun Heo和华为的Li Zefan同窗负责解决中。总之,这是一个系统管理上的问题,并且改变会影响不少东西,但一旦方案肯定,老的cgroup方式将一去不复返。
(全文完)