Docker容器与容器云学习笔记——cgroup

3 篇文章 0 订阅

cgroups(control groups)资源控制组,它不仅可以限制被namespace隔离起来的资源,还可以为资源设置权重、计算使用量、操控任务(进程或线程)启停等。一般来说,cgroup(单数形式)用于指定整个功能,当需要明确表示多个资源控制组的时候,用cgruops(复数形式)。

以下根据Docker容器与容器与描述统一使用cgroups

1. cgroups是什么

官方定义如下:内核cgroup官方文档

cgroups是Linux内核提供的一种机制,这种机制可以根据需求把一系列系统任务及其子任务整合(或分隔)到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的框架。

通俗地说,cgorups可以限制、记录任务组所使用的物理资源(包括CPU、Memory、IO等),为容器实现虚拟化提供了基本保证,是构建Docker等一系列虚拟化管理工具的基石。

对开发者来说,cgroups有以下4个特点:

  • cgroups的API以一个伪文件系统的方式实现,用户态的程序可以通过文件操作实现cgroups的组织管理
  • cgroups的组织管理操作单元可以细粒度到线程级别,另外用户可以创建和销毁cgroup,从而实现资源再分配和管理
  • 所有资源管理的功能都以子系统的方式实现,接口统一
  • 子任务创建之初与其父任务处于同一个cgroups控制组

本质上来说,cgroups是内核附加在程序上的一系列钩子(hook),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。

2. cgroups的作用

实现cgroups的主要目的是为不同用户层面的资源管理,提供一个统一化的接口。从单个任务的资源控制到操作系统层面的虚拟化,cgroups提供了以下四大功能。

  • 资源限制:cgroups可以对任务使用的资源总额进行限制。如设定应用运行时使用内存的上限,一旦超过这个配额就发出OOM(Out of Memory)提示
  • 优先级分配:通过分配的CPU时间片数量及磁盘IO带宽大小,实际上就相当于控制了任务运行的优先级
  • 资源统计:cgroups可以统计系统的资源使用量,如CPU使用时长、内存用量等,这个功能非常适用于计费(如通过设置net_classid对相应的进程组添加iptables规则统计流量)
  • 任务控制:cgroups可以对任务执行挂起、恢复等操作。

3. cgroups术语表

  • task(任务):在cgroups的术语中,任务表示系统的一个进程或线程
  • cgroup(控制组):cgroups中的资源控制都以cgroup为单位实现。cgroup表示按某种资源控制标准划分而成的任务组,包含一个或多个子系统。一个任务可以加入某个cgroup,也可以从某个cgroup迁移到另外一个cgroup
  • subsystem(子系统):cgroups中的子系统就是一个资源调度控制器。比如CPU子系统可以控制CPU时间分配,内存子系统可以限制cgroup内存使用量
  • hierarchy(层级):层级由一系列cgroup以一个树状结构排列而成,每个层级通过绑定对应的子系统进行资源控制。层级中的cgroup节点可以包含零或多个子节点,子节点继承父节点挂载的子系统。整个操作系统可以有多个层级

4. 组织结构与基本规则

传统的Unix任务管理,实际上是先启动init任务作为根节点,再由init节点创建子任务作为子节点,而每个子节点又可以创建新的子节点,如此往复,形成一个树状结构。而系统中的多个cgroup也构成类似的树状结构,子节点从父节点继承属性。

它们最大的不同在于,系统中的多个cgroup构成的层级并非单根结构,可以允许存在多个。如果任务模型是由init作为根节点构成的一棵树,那么系统中的多个cgroup则是由多个层级构成的森林。这样做的目的很好理解,如果只有一个层级,那么所有的任务都将被迫绑定其上的所有子系统,这会给某些任务造成不必要的限制。在Docker中,每个子系统独自构成一个层级,这样做非常易于管理。

上面介绍的是cgroups的组织结构,再来看看cgroup、任务、子系统、层级四者间的关系及其基本规则。

  • 规则一:同一个层级可以附加一个或多个子系统。如下图所示,CPU和Memory的子系统附加到了一个层级。在这里插入图片描述
  • 规则二:一个子系统可以附加到多个层级,当且仅当目标层级只有唯一一个子系统时。下图小圈中的数字表示子系统附件的时间顺序,CPU子系统附件到层级A的同时不能再附加到层级B,因为层级B已经附加了内存子系统。如果层级B没有附加过内存子系统,那么CPU子系统同时附加到两个层级是允许的。在这里插入图片描述
  • 规则三:系统每次新建一个层级时,该系统上的所有任务默认加入这个新建层级的初始化cgroup,这个cgroup也被称为root cgroup。对于创建的每个层级,任务只能存在于其中一个cgorup中,即一个任务不能存在于同一个层级的不同cgroup中,但一个任务可以存在于不同层级中的多个cgroup中。如果操作时把一个任务添加到同一个层级中的另一个cgorup中欧冠,则会将它从第一个cgroup中移除。在下图可以看到,httpd任务已经加入到层级A中的/cg1(①),而不能加入同一个层级中的/cg2(②),但是可以加入层级B中的/cg3(③)。如果需要第二步成功,则httpd任务就会从/cg1的task中移除。在这里插入图片描述
  • 规则四:任务在fork/clone自身时创建的子任务默认与原任务在同一个cgroup中,但是子任务允许被移动到不同的cgroup中。即fork/clone完成后,父子任务间在cgroup方面是互不影响的。下图中小圈中的数字表示任务出现的时间顺序,当httpd刚fork出另一个httpd时,两者在同一个层级中的同一个cgroup中。但是随后如果ID为4840的httpd需要移动到其他cgroup也是可以的,因为父子间任务已经独立。总结起来就是:初始化时子任务与父任务在同一个cgroup,但是这种关系随后可以改变。在这里插入图片描述

5.子系统简介

子系统实际上就是cgroups的资源控制系统,每种子系统独立地控制一种资源,目前Docker使用如下9种子系统,其中,net_cls子系统在内核中已经广泛实现,但是Docker尚未使用。

  • blkio:可以为块设备设定输入/输出限制,比如物理驱动设备(包括磁盘、固态硬盘、USB等)
  • cpu:使用调度程序控制任务对CPU的使用
  • cpuacct:自动生成cgroup中任务对CPU资源使用情况的报告
  • cpuset:可以为cgroup中的任务分配独立的CPU(此处针对多处理器系统)和内存。
  • devices:可以开启或关闭cgroup中任务对设备的访问。
  • freezer:可以挂起或恢复cgroup中的任务
  • memory:可以设定cgroup中任务对内存使用量的限定,并且自动生成这些任务对内存资源使用情况的报告。
  • perf_event:使用后使cgroup中的任务可以进行统一的性能测试(perf是Linux的CPU性能探测器)
  • net_cls:Docker没有直接使用它,它通过使用等级标识符(classid)标记网络数据包,从而允许Linux流量控制程序(traffic Controller,TC)识别从具体cgroup中生成的数据包。

Docker本身并没有对cgroup本身做增强,容器用户一般也不需要直接操作cgroup

Linux中cgroup的实现形式表现为一个文件系统,因此需要mount这个文件系统才能够使用(/sys/fs/cgroup),挂在成功后,就能看到各类子系统。

[root@wxtest062vm6 ~]# mount -t cgroup
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)

以cpu子系统为例,先看一下挂载了这个子系统的控制组下的文件,其中system.slice是systemd创建的cgroup组。具体参考深入理解 Linux Cgroup 系列(二):玩转 CPU

[root@wxtest062vm6 cpu]# ls /sys/fs/cgroup/cpu
cgroup.clone_children  cpuacct.stat          cpu.cfs_quota_us   cpu.stat           release_agent
cgroup.event_control   cpuacct.usage         cpu.rt_period_us   docker             system.slice
cgroup.procs           cpuacct.usage_percpu  cpu.rt_runtime_us  machine.slice      tasks
cgroup.sane_behavior   cpu.cfs_period_us     cpu.shares         notify_on_release  user.slice

/sys/fs/cgroup 的cpu子目录下创建控制组cgroup,控制组目录创建成功后,它下面就会有很多类似的文件了。可以看到/sys/fs/cgroup/cpu/sys/fs/cgroup/cpu/cg1下的文件基本相同,少了release_agentcgroup.sane_behavior两个文件,其中release_agent里面存放着cgroup退出时将会执行的命令

[root@wxtest062vm6 cpu]# mkdir cg1
[root@wxtest062vm6 cpu]# ls cg1
cgroup.clone_children  cgroup.procs  cpuacct.usage         cpu.cfs_period_us  cpu.rt_period_us   cpu.shares  notify_on_release
cgroup.event_control   cpuacct.stat  cpuacct.usage_percpu  cpu.cfs_quota_us   cpu.rt_runtime_us  cpu.stat    tasks

下面举一个例子来展示如何限制PID为18828的进程的cpu使用配额:

# 限制18828进程
$ echo 18828 >> /sys/fs/cgroup/cpu/cg1/tasks
# 将cpu限制为最高使用20%
$ echo 20000 > /sys/fs/cgroup/cpu/cg1/cpu.cfs_quota_us

在Docker的实现中,Docker daemon会在每个子系统控制组目录(比如/sys/fs/cgroup/cpu)下创建一个名为docker的控制组,然后在docker控制组里面,再为每一个容器创建一个以容器ID为名称的容器控制组,这个容器里的所有进程的PID都会写到该控制组的tasks目录中,并且在控制文件(比如cpu.cfs_quota_us)中写入预设的限制参数值。综上,docker控制组的层级结构如下。

[root@wxtest062vm6 cpu]# tree /sys/fs/cgroup/cpu/docker
/sys/fs/cgroup/cpu/docker
├── 5d086ec0d09a7dffa7c127ed3a345fa0351e2bdcab8eff598100239f0556e826
│   ├── cgroup.clone_children
│   ├── cgroup.event_control
│   ├── cgroup.procs
│   ├── cpuacct.stat
│   ├── cpuacct.usage
│   ├── cpuacct.usage_percpu
│   ├── cpu.cfs_period_us
│   ├── cpu.cfs_quota_us
│   ├── cpu.rt_period_us
│   ├── cpu.rt_runtime_us
│   ├── cpu.shares
│   ├── cpu.stat
│   ├── notify_on_release
│   └── tasks
├── cgroup.clone_children
├── cgroup.event_control
├── cgroup.procs
├── cpuacct.stat
├── cpuacct.usage
├── cpuacct.usage_percpu
├── cpu.cfs_period_us
├── cpu.cfs_quota_us
├── cpu.rt_period_us
├── cpu.rt_runtime_us
├── cpu.shares
├── cpu.stat
├── notify_on_release
└── tasks

6. cgroups实现方式及工作原理简介

cgroups的实现本质上是给任务挂上钩子(hook),当任务运行的过程中涉及某种资源时,就会触发钩子上所附带的子系统进行检测,根据资源类别的不同,使用对应的技术进行资源限制和优先级分配

6.1 cgroups如何判断资源超限及超出限额之后的措施

对于不同的系统资源,cgroups提供了统一个接口对资源进行控制和设计,但限制的具体方式则不尽相同。比如memory子系统,会在描述内存状态的"mm_struct"结构体中记录它所属的cgroup,当进程需要申请更多内存时,就会触发cgroup用量检测,用量超过cgroup限定的限额,则拒绝用户的内存申请,否则就给予相应内存并在cgroup的统计信息中记录。实际实现要比上述描述地要复杂得多,不仅需要考虑内存的分配与回收,还需要考虑不同类似的内存如cache(缓存)和swap(交换区与内存拓展)等。

进程所需的内存超过它所属的cgroup的最大限额以后,如果设置了OOM Control(Out of Memory Control),那么进程就会收到OOM信号并结束;否则进程就会被挂起,进入睡眠状态,直到cgroup中其他进程释放了足够的内存资源为止。Docker中默认是开启OOM Control的。其他子系统的实现与此类似,cgroups提供了多种资源限制的策略供用户选择。

6.2 cgroup与任务之间的关联关系

实现上,cgroup与任务之间是多对多的关系,所以它们并不直接关联,而是通过一个中间结构把双向的关联信息记录起来。每个任务结构体task_struct中都包含了一个指针,可以查询到对应cgroup的情况,同时也可以查询到各个子系统的状态,这些子系统状态中也包含了找到任务的指针,不同类型的子系统按需定义本身的控制信息结构体,最终在自定义的结构体中把子系统状态指针包含进去,然后内核通过container_of(这个宏可以通过一个结构体的成员找到结构体自身)等宏定义来获取对应的结构体,关联到任务,以此达到资源限制的目的。

同时,为了让cgroups便于用户理解和使用,也为了用精简的内核代码为cgroup提供熟悉的权限和命名空间管理,内核按照Linux虚拟文件系统转换器(Virtual FileSystem Switsh,VFS)接口实现了一套名为cgroup的文件系统,非常巧妙地用来表示cgroups的层级概念,把各个子系统的实现都封装到文件系统的各项操作中。

6.3 Docker在使用cgroup时的注意事项

在实际的使用过程中,Docker需要通过挂载cgroup文件系统新建一个层级结构,挂载时指定要绑定的子系统。把cgroup文件系统挂载上以后,就可以像操作文件一样对cgroups的层级进行浏览和操作管理(包括权限管理、子文件管理等)。除了cgroup文件系统以外,内核没有为cgroups的访问和操作添加任何系统调用(即用户只能够通过操作cgroup文件系统来访问cgroups)。

如果新建的层级结构要绑定的子系统与目前已经存在的层级结构完全相同,那么新的挂载会重用原来已经存在的那一套(指向相同的css_set)。否则,如果要绑定的子系统已经被别的层级绑定,就会返回挂载失败的错误。如果一切顺利,挂载完成后层级就被激活并与相应子系统关联起来,可以开始使用了。

目前无法将一个新的子系统绑定到激活的层级上,或者从一个激活的层级中解除某个子系统的绑定。

当一个顶层的cgroup文件系统被卸载(umount)时,如果其中创建过深层次的后代cgroup目录,那么就算上层的cgroup被卸载了,层级也是激活状态,其后代cgroup中的配置依旧有效。只有递归式地卸载层级中的所有cgruop,那个层级才会被真正地删除。

在创建的层级中创建文件夹,就类似于fork了一个后代cgroup,后代cgroup中默认继承原有cgroup中的配置属性,但是可以根据需求对配置参数进行调整。这样就把一个大的cgroup系统分割成一个个嵌套的、可动态变化的“软分区”。

6.4 /sys/fs/cgroup/cpu/docker/下文件的作用

有些文件是cpu子系统特有的,有些文件是每个子系统都有的。

以资源开头(比如cpu.shares)的文件都是用来限制这个cgroup下任务的可用的配置文件。

一个cgroup创建完成,不管绑定了何种子系统,其目录下都会生成以下几个文件,用来描述cgroup的相应信息。同样,把相应信息写入这些配置文件就可以生效,内容如下:

  • tasks:这个文件中罗列了所有在该cgroup中任务的TID,即所有进程或线程的ID。该文件并不保证任务的TID有序,把一个任务的TID写到这个文件中就意味着把这个任务加入这个cgroup中,如果这个任务所在的任务组与其不在同一个cgroup,那么会在cgroup.procs文件里记录一个该任务所在任务组的TGID值,但是该任务组的其他任务并不受影响
  • cgroup.procs:这个文件罗列所有在该cgroup中的TGID(线程组ID),及线程组中第一个进程的PID。该文件并不保证TGID有序和无重复。写一个TGID到这个文件就意味着把与其相关的线程都加入到这个cgroup中。
  • notify_on_release:该文件的内容为1时,当cgroup退出时(不再包含任何进程和子cgroup),将调用release_agent里面配置的命令。新cgroup被创建时将默认继承父cgroup的这项配置。默认情况时0,表示不运行
  • release_agent:里面包含了cgroup退出时会执行的命令,系统调用该命令时会将相应cgroup的相对路径当做参数传进去。注意:这个文件只会存在于root cgroup下面,其他cgroup里面不会有这个文件。这个脚本通常用于自动化卸载无用的cgroup。
  • cgroup.clone_children:这个文件只对cpuset子系统有影响,当该文件的内容为1时,新创建的cgrouop将会继承父cgroup的配置,即从父cgroup里面拷贝配置文件来初始化新cgroup
  • cgroup.sane_behavior:不详(书里没有对这个文件有准确的说明,查了资料有没有很明确的说明),只有root cgroup会有这个目录
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值