歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> Docker基礎技術:Linux CGroup

Docker基礎技術:Linux CGroup

日期:2017/2/27 15:53:14   编辑:Linux教程
前面,我們介紹了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。

主要提供了如下功能:

  • Resource limitation: 限制資源使用,比如內存使用上限以及文件系統的緩存限制。
  • Prioritization: 優先級控制,比如:CPU利用和磁盤IO吞吐。
  • Accounting: 一些審計或一些統計,主要目的是為了計費。
  • Control: 掛起進程,恢復執行進程。

使​​​用​​​ cgroup,系​​​統​​​管​​​理​​​員​​​可​​​更​​​具​​​體​​​地​​​控​​​制​​​對​​​系​​​統​​​資​​​源 ​​​的​​​分​​​配​​​、​​​優​​​先​​​順​​​序​​​、​​​拒​​​絕​​​、​​​管​​​理​​​和​​​監​​​控​​​。 ​​​可​​​更​​​好​​​地​​​根​​​據​​​任​​​務​​​和​​​用​​​戶​​​分​​​配​​​硬​​​件​​​資​​​源​​​, 提​​​高​​​總​​​體​​​效​​​率​​​。

在實踐中,系統管理員一般會利用CGroup做下面這些事(有點像為某個虛擬機分配資源似的):

  • 隔離一個進程集合(比如:nginx的所有進程),並限制他們所消費的資源,比如綁定CPU的核。
  • 為這組進程 分配其足夠使用的內存
  • 為這組進程分配相應的網絡帶寬和磁盤存儲限制
  • 限制訪問某些設備(通過設置設備的白名單)

那麼CGroup是怎麼干的呢?我們先來點感性認識吧。

首先,Linux把CGroup這個事實現成了一個file system,你可以mount。在我的Ubuntu 14.04下,你輸入以下命令你就可以看到cgroup已為你mount好了。

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 限制

假設,我們有一個非常吃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掉了。

磁盤I/O限制

我們先看一下我們的硬盤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...

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一下:

$ 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一下看看:

$ 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只有上線控制下級,無法傳遞到下下級。所以,C和D中沒有memory的限制,E中沒有blkio和memory的限制。而本層的cgroup.controllers文件是個只讀的,其中的內容就看上級的subtree_control裡有什麼了。
  • 任何被配置過subtree_control的目錄都不能綁定進程,根結點除外。所以,A,C,D,E可以綁上進程,但是B不行。

我們可以看到,這種方式干淨的區分開了兩個事,一個是進程的分組,一個是對分組的資源控制(以前這兩個事完全混在一起),在目錄繼承上增加了些限制,這樣可以避免一些模稜兩可的情況。

當然,這個事還在演化中,cgroup的這些問題這個事目前由cgroup的吐槽人Tejun Heo和華為的Li Zefan同學負責解決中。總之,這是一個系統管理上的問題,而且改變會影響很多東西,但一旦方案確定,老的cgroup方式將一去不復返。

原文:http://coolshell.cn/articles/17049.html
Copyright © Linux教程網 All Rights Reserved