原文: 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方式將一去不復返。
(全文完)