---
title: "【转载】Docker与Kubernetes系列(六): Docker的原理2_Linux CGroup"
id: "1532"
type: "post"
slug: "%e3%80%90%e8%bd%ac%e8%bd%bd%e3%80%91docker%e4%b8%8ekubernetes%e7%b3%bb%e5%88%97%e5%85%ad-docker%e7%9a%84%e5%8e%9f%e7%90%862_linux-cgroup"
published_at: "2017-08-09T21:20:03+00:00"
modified_at: "2017-08-09T21:20:03+00:00"
url: "https://seq.ink/all/1532.html"
markdown_url: "https://seq.ink/all/1532.html.md"
excerpt: "原文地址 这段时间工作中用到了Docker以及Kubernetes（简称K8S），现在整理下我学习Docker以及K8S过程中看的一些比较好的资料，方便自己回顾，也希望能给容器小白一些帮助。给自己定一个小目标，二月底之前完成。 这是本系列的 ..."
taxonomy_category:
  - "ALL"
---

**[原文地址](http://blog.csdn.net/u012422829/article/details/56551640)**

这段时间工作中用到了[Docker](http://lib.csdn.net/base/docker)
以及Kubernetes（简称K8S），现在**整理**下我学习[Docker](http://lib.csdn.net/base/docker)
以及K8S过程中看的一些比较好的资料，方便自己回顾，也希望能给容器小白一些帮助。**给自己定一个小目标，二月底之前完成。**

这是本系列的第六篇文章， 将介绍[Docker](http://lib.csdn.net/base/docker)
的原理之CGroup。(整理自: http://coolshell.cn/articles/17049.html)

前面，我们介绍了[Linux](http://lib.csdn.net/base/linux)
 Namespace，但是Namespace解决的问题主要是环境隔离的问题，这只是虚拟化中最最基础的一步，我们还需要解决对计算机资源使用上的隔离。也就是说，虽然你通过Namespace把我Jail到一个特定的环境中去了，但是我在其中的进程使用用CPU、内存、磁盘等这些计算资源其实还是可以随心所欲的。所以，我们希望对进程进行资源利用上的限制或控制。这就是[linux](http://lib.csdn.net/base/linux)
 CGroup出来了的原因。

Linux CGroup全称Linux Control Group， 是Linux内核的一个功能，用来限制，控制与分离一个进程组群的资源（如CPU、内存、磁盘输入输出等）。

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好了。

| 1 2 3 4 5 6 7 8 9 10 11 12 | hchen@ubuntu:~$ mount-t cgroup cgroup on /sys/fs/cgroup/cpuset typecgroup (rw,relatime,cpuset) cgroup on /sys/fs/cgroup/cpu typecgroup (rw,relatime,cpu) cgroup on /sys/fs/cgroup/cpuacct typecgroup (rw,relatime,cpuacct) cgroup on /sys/fs/cgroup/memory typecgroup (rw,relatime,memory) cgroup on /sys/fs/cgroup/devices typecgroup (rw,relatime,devices) cgroup on /sys/fs/cgroup/freezer typecgroup (rw,relatime,freezer) cgroup on /sys/fs/cgroup/blkio typecgroup (rw,relatime,blkio) cgroup on /sys/fs/cgroup/net_prio typecgroup (rw,net_prio) cgroup on /sys/fs/cgroup/net_cls typecgroup (rw,net_cls) cgroup on /sys/fs/cgroup/perf_event typecgroup (rw,relatime,perf_event) cgroup on /sys/fs/cgroup/hugetlb typecgroup (rw,relatime,hugetlb) |
| --- | --- |

或者使用lssubsys命令：

| 1 2 3 4 5 6 7 8 9 10 11 12 | $ 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，下面给了一个示例：

| 1 2 3 4 5 6 7 8 | mkdircgroup mount-t tmpfs cgroup_root ./cgroup mkdircgroup/cpuset mount-t cgroup -ocpuset cpuset ./cgroup/cpuset/ mkdircgroup/cpu mount-t cgroup -ocpu cpu ./cgroup/cpu/ mkdircgroup/memory mount-t cgroup -omemory memory ./cgroup/memory/ |
| --- | --- |

一旦mount成功，你就会看到这些目录下就有好文件了，比如，如下所示的cpu和cpuset的子系统：

| 1 2 3 4 5 6 7 8 9 10 11 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 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，你会发现，一旦你创建了一个子目录，这个子目录里又有很多文件了。

| 1 2 3 4 5 | hchen@ubuntu:/sys/fs/cgroup/cpu$ sudomkdirhaoel [sudo] password forhchen: 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，其源码如下：

| 1 2 3 4 5 6 | intmain(void) { inti = 0; for(;;) i++; return0; } |
| --- | --- |

用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> constintNUM_THREADS = 5; void*thread_main(void*threadid) { /* 把自己加入cgroup中（syscall(SYS_gettid)为得到线程的系统tid） */ charcmd[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); longtid; tid = (long)threadid; printf(“Hello World! It’s me, thread #%ld, pid #%ld!\n”, tid, syscall(SYS_gettid)); inta=0; while(1) { a++; } pthread_exit(NULL); } intmain (intargc, char*argv[]) { intnum_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); intrc; longt; 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> intmain(void) { intsize = 0; intchunk_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); } return0; } |
| --- | --- |

然后，在我们另外一边：

| 1 2 3 4 5 6 | # 创建memory cgroup $ mkdir/sys/fs/cgroup/memory/haoel $ echo64k > /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 | sudoddif=/dev/sda1of=/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 % ddif=/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 % ddif=/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 | $ sudomodprobe cls_cgroup $ sudomkdir/sys/fs/cgroup/net_cls $ sudomount-t cgroup -o net_cls none /sys/fs/cgroup/net_cls $ sudomodprobe netprio_cgroup $ sudomkdir/sys/fs/cgroup/net_prio $ sudomount-t cgroup -o net_prio none /sys/fs/cgroup/net_prio |
| --- | --- |

关于各个子系统的参数细节，以及更多的Linux CGroup的文档，你可以看看下面的文档：

- [Linux Kernel的官方文档](https://www.kernel.org/doc/Documentation/cgroups/)
- [Redhat的官方文档](https://access.redhat.com/documentation/zh-CN/Red_Hat_Enterprise_Linux/6/html-single/Resource_Management_Guide/index.html#ch-Subsystems_and_Tunable_Parameters)

#### CGroup的术语

CGroup有下述术语：

- 任务（Tasks）：就是系统的一个进程。
- 控制组（Control Group）：一组按照某种标准划分的进程，比如官方文档中的Professor和Student，或是WWW和System之类的，其表示了某进程组。Cgroups中的资源控制都是以控制组为单位实现。一个进程可以加入到某个控制组。而资源的限制是定义在这个组上，就像上面示例中我用的haoel一样。简单点说，cgroup的呈现就是一个目录带一系列的可配置文件。
- 层级（Hierarchy）：控制组可以组织成hierarchical的形式，既一颗控制组的树（目录结构）。控制组树上的子节点继承父结点的属性。简单点说，hierarchy就是在一个或多个子系统上的cgroups目录树。
- 子系统（Subsystem）：一个子系统就是一个资源控制器，比如CPU子系统就是控制CPU时间分配的一个控制器。子系统必须附加到一个层级上才能起作用，一个子系统附加到某个层级以后，这个层级上的所有控制族群都受到这个子系统的控制。Cgroup的子系统可以有很多，也在不断增加中。
