cgroups资源限制

背景

资源隔离技术namespace通过系统调用构建了一个相对隔离的shell环境,在该环境下资源的使用不会影响宿主机,这已经有“容器”的雏形了。而如何将隔离出来的资源合理的使用,这就需要另一个强大的内核工具——cgroups。它不仅可以限制被namespace隔离的资源,还可以为资源设置权重、计算使用量、操控任务(进程或线程)启停等,Docker通过cgroups进行运行时资源的限制。

cgroups简介

cgroups最初名为process container,但由于container的多重含义,最后改名为control groups,顾名思义就是把任务放到一个组里,统一进行控制。因此,cgroups控制对象是任务组,一旦一个任务加入了一个组中,就会受到相关联的cgroups的资源限制,这里的资源指的是物理资源(cpu、Memory)。

cgroups的4个特征:

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

cgroups的核心功能和术语

cgroups为不同用户层面的资源管理提供了一个统一化的接口,实现了资源的虚拟化,主要提供了四大功能:

  • 资源限制:cgroups可以对任务组使用的资源总额进行限制,如内存上限
  • 优先级:通过分配CPU时间片数量及磁盘IO带宽大小,变相控制任务优先级
  • 资源统计:统计资源使用量,如CPU使用时长,适用于计费
  • 任务控制:cgroups可以对任务执行挂起、恢复等操作。

cgroups的几个术语

  • task(任务):表示系统的一个进程或线程
  • cgroup(控制组):cgroups资源控制的最小单位,表示按某种资源控制标准划分的任务组,包含一个或多个子系统。一个任务可以加入某个cgroup,也可以从某个cgroup迁移到另一个cgroup
  • subsystem(子系统):资源调度器,如CPU子系统、内存子系统
  • hierarchy(层级):由一系列cgroup以树状结构排列而成,每个层级绑定子系统进行资源控制。层级中cgroup节点可以有0或多个子节点,子节点继承父节点的子系统。整个操作系统有多个层级。

cgroups的组织结构和基本规则

前面说到cgroup是一个树状结构,但系统中多个cgroup构成的并非一个单根结构,而是可以存在多个,最终构成了一个cgroup森林。这样做的目的是,如果只有一个层级,则所有的任务都将被迫绑定其上的所有子系统,这会给某些人物带来不必要的限制。在Docker中,每个子系统独自构成一个层级,这样做非常易于管理。组织结构图如下:

image

上面这个图从整体结构上描述了进程与 cgroups 之间的关系。最下面的P代表一个进程。每一个进程的描述符中有一个指针指向了一个辅助数据结构css_set(cgroups subsystem set)。 指向某一个css_set的进程会被加入到当前css_set的进程链表中。一个进程只能隶属于一个css_set,一个css_set可以包含多个进程,隶属于同一css_set的进程受到同一个css_set所关联的资源限制。

上图中的”M×N Linkage”说明的是css_set通过辅助数据结构可以与 cgroups 节点进行多对多的关联。但是 cgroups 的实现不允许css_set同时关联同一个cgroups层级结构下多个节点。 这是因为 cgroups 对同一种资源不允许有多个限制配置。

一个css_set关联多个 cgroups 层级结构的节点时,表明需要对当前css_set下的进程进行多种资源的控制。而一个 cgroups 节点关联多个css_set时,表明多个css_set下的进程列表受到同一份资源的相同限制。

cgroups、任务、子系统、层级四者间的关系及其基本规则如下:

  • 同一个层级可以附加一个或多个子系统
  • 一个已经附加在某个层级上的子系统不能附加到其他含有别的子系统的层级上。
  • 一个任务不能存在于同一个层级的不同cgroup中,但可以存在不同层级的多个cgroup中。
  • 刚fork/clone出的子任务在初始状态与其父任务处于同一个cgroup,但子任务允许被移动到不同的cgroup中。

子系统简介

子系统就是cgroups的资源控制系统,每种子系统独立地控制一种资源,目前Docker使用如下9种子系统:

  • blkio:块设备输入/输出限制,如物理驱动设备(包括磁盘、固态、USB等)。Linux中分为块设备和字符设备两种,块设备以块存储数据,可以进行数据的寻址;字符设备提供连续的数据流,按字节/字符读取,如键盘、串口等。
  • cpu: 使用调度程序控制任务对cpu的使用
  • cpuacct:自动生成cgroup中任务对CPU资源使用情况的报告
  • cpuset: 可以为cgroup中任务数分配独立的cpu(针对多处理器)
  • devices: 可以开启或关闭对设备的访问
  • freezer: 挂起或恢复cgroup中的任务
  • memory: 可以设定cgroup中任务对内存使用量的限定,并自动生成内存使用情况报告
  • perf_event: 对cgroup中的任务可以进行统一的性能测试
  • net_cls: 使用等级识别符标记网络数据包,从而允许Linux流量控制程序识别从具体cgroup中生成的数据包

cgroups实现方式

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

cgroup与任务是多对多的关系,它们不直接关联,而是通过一个中间结构把双向的关联信息记录起来,类似于数据库中的中间表。任务结构体task_struct中包含了一个指针,可以查询相应的cgroup情况,同时可以查询子系统的状态,子系统状态中也包含了找到任务的指针。

一个cgroup创建完成,不管绑定了何种子系统,其目录下都会生成以下几个文件,用来描述cgroup相应信息。

  • tasks: 这个文件罗列所有在该cgroup中任务的TID,即所有进程或线程的ID,并不保证有序。
  • cgroup.procs: 罗列所有在该cgroup中的TGID(线程组ID),即线程组中的一个进程的PID。不保证有序和无重复,写一个TGID到这个文件就代表把与其相关的线程加到这个cgroup中。
  • notify_on_release: 填0或1,表示cgroup中最后一个任务退出时通知运行release_agent,默认不运行(0)
  • release_agent:指定release_agent执行脚本的文件路径,用于自动化卸载无用的cgroup

总结

cgroups通过与cgroup关联的子系统,形成了对进程组的资源限制。它一个伪文件系统的方式实现,并可以通过文件操作实现组织管理,最终提供了资源限制、优先级分配、资源统计、任务控制四大功能。