原文:?http://coolshell.cn/articles/17049.html
前面,我們介紹了Linux Namespace,但是Namespace解決的問題主要是環境隔離的問題,這只是虛擬化中最最基礎的一步,我們還需要解決對計算機資源使用上的隔離。也就是說,雖然你通過Namespace把我Jail到一個特定的環境中去了,但是我在其中的進程使用用CPU、內存、磁盤等這些計算資源其實還是可以隨心所欲的。所以,我們希望對進程進行資源利用上的限制或控制。這就是Linux CGroup出來了的原因。
Linux CGroup全稱Linux Control Group, 是Linux內核的一個功能,用來限制,控制與分離一個進程組群的資源(如CPU、內存、磁盤輸入輸出等)。這個項目最早是由Google的工程師在2006年發起(主要是Paul Menage和Rohit Seth),最早的名稱為進程容器(process containers)。在2007年時,因為在Linux內核中,容器(container)這個名詞太過廣泛,為避免混亂,被重命名為cgroup,并且被合并到2.6.24版的內核中去。然后,其它開始了他的發展。
Linux CGroupCgroup 可???讓???您???為???系???統???中???所???運???行???任???務???(進???程???)的???用???戶???定???義???組???群???分???配???資???源??? — 比???如??? CPU 時???間???、???系???統???內???存???、???網???絡???帶???寬???或???者???這???些???資???源???的???組???合???。???您???可???以???監???控???您???配???置???的??? cgroup,拒???絕??? cgroup 訪???問???某???些???資???源???,甚???至???在???運???行???的???系???統???中???動???態???配???置???您???的??? cgroup。
主要提供了如下功能:
linux udev。?
- Resource limitation: 限制資源使用,比如內存使用上限以及文件系統的緩存限制。
- Prioritization: 優先級控制,比如:CPU利用和磁盤IO吞吐。
- Accounting: 一些審計或一些統計,主要目的是為了計費。
- Control: 掛起進程,恢復執行進程。
使???用??? cgroup,系???統???管???理???員???可???更???具???體???地???控???制???對???系???統???資???源???的???分???配???、???優???先???順???序???、???拒???絕???、???管???理???和???監???控???。???可???更???好???地???根???據???任???務???和???用???戶???分???配???硬???件???資???源???,提???高???總???體???效???率???。
在實踐中,系統管理員一般會利用CGroup做下面這些事(有點像為某個虛擬機分配資源似的):
- 隔離一個進程集合(比如:nginx的所有進程),并限制他們所消費的資源,比如綁定CPU的核。
- 為這組進程 分配其足夠使用的內存
- 為這組進程分配相應的網絡帶寬和磁盤存儲限制
- 限制訪問某些設備(通過設置設備的白名單)
那么CGroup是怎么干的呢?我們先來點感性認識吧。
首先,Linux把CGroup這個事實現成了一個file system,你可以mount。在我的Ubuntu 14.04下,你輸入以下命令你就可以看到cgroup已為你mount好了。
1 cgroup查看,2 3 4 5 6 7 國內linux?8 9 10 11 12 | hchen@ubuntu:~$ mount -t cgroup shell開發。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) linux容器技術、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命令:
ubuntu source。1 2 3 4 5 6 LINUX教程?7 8 9 10 11 12 | 深度linux,$ 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 linux是什么。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 |
docker cgroup、我們可以看到,在/sys/fs下有一個cgroup的目錄,這個目錄下還有很多子目錄,比如: cpu,cpuset,memory,blkio……這些,這些都是cgroup的子系統。分別用于干不同的事的。
如果你沒有看到上述的目錄,你可以自己mount,下面給了一個示例:
1 2 3 4 cgroup的功能是什么?5 6 7 8 | mkdir cgroup mount -t tmpfs cgroup_root . /cgroup cgroup namespace。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/ |
cgroup詳解。一旦mount成功,你就會看到這些目錄下就有好文件了,比如,如下所示的cpu和cpuset的子系統:
1 2 3 4 5 linux面試題及答案100。6 7 8 9 10 11 linux和windows的區別、12 13 14 | 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 LINUX系統?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 深入理解linux網絡技術?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,你會發現,一旦你創建了一個子目錄,這個子目錄里又有很多文件了。
1 2 3 4 5 | 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 限制
假設,我們有一個非常吃CPU的程序,叫deadloop,其源碼如下:
DEADLOOP.C 1 2 3 4 5 6 | int main( void ) { ???? int i = 0; ???? for (;;) i++; ???? return 0; } |
用sudo執行起來后,毫無疑問,CPU被干到了100%(下面是top命令的輸出)
1 2 | 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利用的限制:
1 2 3 | 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中:
1 | # echo 3529 >> /sys/fs/cgroup/cpu/haoel/tasks |
然后,就會在top中看到CPU的利用立馬下降成20%了。(前面我們設置的20000就是20%的意思)
1 2 | 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 |
下面的代碼是一個線程的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | #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個字節,每次休息一秒):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #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; } |
然后,在我們另外一邊:
1 2 3 4 5 6 | # 創建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掉了。
磁盤I/O限制
我們先看一下我們的硬盤IO,我們的模擬命令如下:(從/dev/sda1上讀入數據,輸出到/dev/null上)
1 | sudo dd if = /dev/sda1 of= /dev/null |
我們通過iotop命令我們可以看到相關的IO速度是55MB/s(虛擬機內):
1 2 | 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
1 | mkdir /sys/fs/cgroup/blkio/haoel |
并把讀IO限制到1MB/s,并把前面那個dd命令的pid放進去(注:8:0 是設備號,你可以通過ls -l /dev/sda1獲得):
1 2 | 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左右。
1 2 | 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 ... |
CGroup的子系統
好了,有了以上的感性認識我們來,我們來看看control group有哪些子系統:
???
- blkio — 這???個???子???系???統???為???塊???設???備???設???定???輸???入???/輸???出???限???制???,比???如???物???理???設???備???(磁???盤???,固???態???硬???盤???,USB 等???等???)。
- cpu — 這???個???子???系???統???使???用???調???度???程???序???提???供???對??? CPU 的??? cgroup 任???務???訪???問???。???
- cpuacct — 這???個???子???系???統???自???動???生???成??? cgroup 中???任???務???所???使???用???的??? CPU 報???告???。???
- cpuset — 這???個???子???系???統???為??? cgroup 中???的???任???務???分???配???獨???立??? CPU(在???多???核???系???統???)和???內???存???節???點???。???
- devices — 這???個???子???系???統???可???允???許???或???者???拒???絕??? cgroup 中???的???任???務???訪???問???設???備???。???
- freezer — 這???個???子???系???統???掛???起???或???者???恢???復??? cgroup 中???的???任???務???。???
- memory — 這???個???子???系???統???設???定??? cgroup 中???任???務???使???用???的???內???存???限???制???,并???自???動???生???成?????內???存???資???源使用???報???告???。???
- net_cls — 這???個???子???系???統???使???用???等???級???識???別???符???(classid)標???記???網???絡???數???據???包???,可???允???許??? Linux 流???量???控???制???程???序???(tc)識???別???從???具???體??? cgroup 中???生???成???的???數???據???包???。???
- net_prio — 這個子系統用來設計網絡流量的優先級
- hugetlb — 這個子系統主要針對于HugeTLB系統進行限制,這是一個大頁文件系統。
注意,你可能在Ubuntu 14.04下看不到net_cls和net_prio這兩個cgroup,你需要手動mount一下:
1 2 3 4 5 6 7 | $ 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的文檔,你可以看看下面的文檔:
- Linux Kernel的官方文檔
- Redhat的官方文檔
CGroup的術語
CGroup有下述術語:
- 任務(Tasks):就是系統的一個進程。
- 控制組(Control Group):一組按照某種標準劃分的進程,比如官方文檔中的Professor和Student,或是WWW和System之類的,其表示了某進程組。Cgroups中的資源控制都是以控制組為單位實現。一個進程可以加入到某個控制組。而資源的限制是定義在這個組上,就像上面示例中我用的haoel一樣。簡單點說,cgroup的呈現就是一個目錄帶一系列的可配置文件。
- 層級(Hierarchy):控制組可以組織成hierarchical的形式,既一顆控制組的樹(目錄結構)。控制組樹上的子節點繼承父結點的屬性。簡單點說,hierarchy就是在一個或多個子系統上的cgroups目錄樹。
- 子系統(Subsystem):一個子系統就是一個資源控制器,比如CPU子系統就是控制CPU時間分配的一個控制器。子系統必須附加到一個層級上才能起作用,一個子系統附加到某個層級以后,這個層級上的所有控制族群都受到這個子系統的控制。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一下看看:
1 2 3 4 5 6 7 | $ 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,包括所有的子系統()。
1 2 3 4 5 6 7 8 9 10 11 12 13 | # 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只有上線控制下級,無法傳遞到下下級。所以,C和D中沒有memory的限制,E中沒有blkio和memory的限制。而本層的cgroup.controllers文件是個只讀的,其中的內容就看上級的subtree_control里有什么了。
- 任何被配置過subtree_control的目錄都不能綁定進程,根結點除外。所以,A,C,D,E可以綁上進程,但是B不行。
我們可以看到,這種方式干凈的區分開了兩個事,一個是進程的分組,一個是對分組的資源控制(以前這兩個事完全混在一起),在目錄繼承上增加了些限制,這樣可以避免一些模棱兩可的情況。
當然,這個事還在演化中,cgroup的這些問題這個事目前由cgroup的吐槽人Tejun Heo和華為的Li Zefan同學負責解決中。總之,這是一個系統管理上的問題,而且改變會影響很多東西,但一旦方案確定,老的cgroup方式將一去不復返。
參考
- Linux Kernel Cgroup Documents
- Reahat Resource Management Guide
- Fixing control groups
- The unified control group hierarchy in 3.16
- Cgroup v2(PDF)