聊一聊linux中的cgroup

最近由于工作原因接触了docker,也就研究一下它的原理,主要看了阿里出的<<自己动手写docker>>,感觉蛮有意思,姑且记录一下。docker可以算是当前非常火热的技术了。我们知道docker基于Namespce和Cgroups,其中

  • Namespace主要用于隔离资源
  • Cgroups用来提供对一组进程以及将来子进程的资源限制

Cgrougps的三个组件

cgrpups包含三个组件

  • 控制组 一个cgroups包含一组进程,并可以在这个cgroups上增加Linux subsystem的各种参数配置,将一组进程和一组subsystem关联起来。

  • subsystem 子系统 是一组资源控制模块,可以通过lssubsys -a命令查看当前内核支持哪些subsystem。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    cpuset
    cpu,cpuacct
    blkio
    memory
    devices
    freezer
    net_cls,net_prio
    perf_event
    hugetlb
    pids
    rdma

    subsystem作用于hierarchy的cgroup节点,并控制节点中进程的资源占用。

  • hierarchy 层级树 主要功能是把cgroups串成一个树型结构,使cgruops可以做到继承。也就是说将cgroup通过树状结构串起来,通过虚拟文件系统的方式暴露给用户。

三个组件之前的关系

cgroup中的组件是相互关联的

  • 系统创建新的hierarchy之后,系统中所有的进程都会加入这个hierarchy的cgroup的根节点,这个cgroup根节点是hierarchy默认创建的,在这个hierarchy中创建的所有cgroup都是这个cgroup根节点的子节点。

  • 一个subsystem只能附加到一个hierarchy上

  • 一个hierarchy可以附加多个subsystem

  • 一个进程可以作为多个cgroup的成员,但是这些cgroup必须在不同的hierarchy下

  • 一个进程fork出子进程时,子进程和父进程是在同一个cgroup中的,根据需要也可以移动到其他的cgroup中

使用Cgroup

创建挂载点

我们知道kernel是通过一个虚拟树状文件系统来配置Cgroups的。我们首先需要创建并挂载一个hierarchy(cgroup树)。即先mkdir后mount。

1
2
3
4
5
6
readlnh@readlnh-Inspiron-3542:~$ sudo mount -t cgroup -o none,name=cgroup-test cgroup-test cgroup-test/
readlnh@readlnh-Inspiron-3542:~$ ls ./c
cgroup-test/ clone_test.c
readlnh@readlnh-Inspiron-3542:~$ ls ./cgroup-test/
cgroup.clone_children cgroup.sane_behavior release_agent
cgroup.procs notify_on_release tasks

可以看到挂载后系统在目录下生成了一系列文件,这些文件其实就是根节点文件的配置项。

创建子cgroup

创建cgroup-1,我们可以看到在cgroup文件夹下再创建文件夹,系统会将这个文件夹也标记为cgroup,并且它是上一个cgroup的子cgroup,它会继承父cgroup的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
readlnh@readlnh-Inspiron-3542:~/cgroup-test$ sudo mkdir cgroup-1
readlnh@readlnh-Inspiron-3542:~/cgroup-test$ tree
.
├── cgroup-1
│   ├── cgroup.clone_children
│   ├── cgroup.procs
│   ├── notify_on_release
│   └── tasks
├── cgroup.clone_children
├── cgroup.procs
├── cgroup.sane_behavior
├── notify_on_release
├── release_agent
└── tasks

1 directory, 10 files

移动进程

将终端进程移动到cgroup-1(只要将进程ID写到相应的cgroup的tasks文件)

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
readlnh@readlnh-Inspiron-3542:~/cgroup-test/cgroup-1$ echo $$
10644
readlnh@readlnh-Inspiron-3542:~/cgroup-test/cgroup-1$ cat /proc/10644/cgroup
13:name=cgroup-test:/
12:perf_event:/
11:memory:/user.slice
10:hugetlb:/
9:cpuset:/
8:pids:/user.slice/user-1000.slice/user@1000.service
7:rdma:/
6:devices:/user.slice
5:freezer:/
4:cpu,cpuacct:/user.slice
3:net_cls,net_prio:/
2:blkio:/user.slice
1:name=systemd:/user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service
0::/user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service
readlnh@readlnh-Inspiron-3542:~/cgroup-test/cgroup-1$ sudo sh -c "echo $$ >> tasks"
readlnh@readlnh-Inspiron-3542:~/cgroup-test/cgroup-1$ cat /proc/10644/cgroup
13:name=cgroup-test:/cgroup-1
12:perf_event:/
11:memory:/user.slice
10:hugetlb:/
9:cpuset:/
8:pids:/user.slice/user-1000.slice/user@1000.service
7:rdma:/
6:devices:/user.slice
5:freezer:/
4:cpu,cpuacct:/user.slice
3:net_cls,net_prio:/
2:blkio:/user.slice
1:name=systemd:/user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service
0::/user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service

可以看到现在终端进程已经在cgroup-1了

通过subsystem限制cgroups中的进程资源

之前我们创建的cgroup是没有和任何subsystem相关联的,所以没法通过hierarchy中的cgroup节点限制资源。实际系统中已经默认为每个subsystem创建了一个默认的hierarchy,我们这里就直接在这个hierarchy下创建cgroup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
readlnh@readlnh-Inspiron-3542:~/cgroup-test/test-limit-memory$ mount | grep memory
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
readlnh@readlnh-Inspiron-3542:~/cgroup-test/test-limit-memory$ cd /sys/fs/cgroup/memory/
readlnh@readlnh-Inspiron-3542:/sys/fs/cgroup/memory$ sudo mkdir test-limit-memory
readlnh@readlnh-Inspiron-3542:/sys/fs/cgroup/memory$ cd test-limit-memory/
readlnh@readlnh-Inspiron-3542:/sys/fs/cgroup/memory/test-limit-memory$ ls
cgroup.clone_children memory.limit_in_bytes
cgroup.event_control memory.max_usage_in_bytes
cgroup.procs memory.move_charge_at_immigrate
memory.failcnt memory.numa_stat
memory.force_empty memory.oom_control
memory.kmem.failcnt memory.pressure_level
memory.kmem.limit_in_bytes memory.soft_limit_in_bytes
memory.kmem.max_usage_in_bytes memory.stat
memory.kmem.slabinfo memory.swappiness
memory.kmem.tcp.failcnt memory.usage_in_bytes
memory.kmem.tcp.limit_in_bytes memory.use_hierarchy
memory.kmem.tcp.max_usage_in_bytes notify_on_release
memory.kmem.tcp.usage_in_bytes tasks
memory.kmem.usage_in_bytes
readlnh@readlnh-Inspiron-3542:/sys/fs/cgroup/memory/test-limit-memory$ stress --vm-bytes 200m --vm-keep -m 1
stress: info: [15357] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd

用top查看,发现内存为100m,实际上这里限制内存就是一个向memory_limit_in_bytes文件中写入100m这个操作,可以说相简单了

1
15358 readlnh   20   0  213044 100916    212 D  39.5  1.3   7:54.10 stress

从这里我们就可以猜测,docker实际上就是通过go语言挂载,创建cgroups再向相应文件写入相应条件来限制容器资源的。cgroup确实是一个很有意思也很方便的功能。