大话linux的计划经济cgroups

874 阅读6分钟

简介

cgroups(controller groups)是Linux内核提供的特性,用于对程序/进程进行资源限制和观测,这里的资源包括cpu(可用cpu core和cpu时间片)、memory(内存使用限制)、device(硬件设备可见性)、blkio(块设备IO速率限制)等;目前有v1和v2两个版本。

以v1为例,先了解下几个cgroup的概念:

  • subsystem/controller:子系统/控制器,都是指cgroups提供的关于不同资源的控制系统,v1支持cpu、cpuacct、cpuset、memory、devices、freezer、net_cls、blkio、perf_event、net_prio、hugetlb、pids、rdma有兴趣的可以去man手册查看(man7.org/linux/man-p… 搜索Cgroups version 1 controllers)
  • cgroup:指具体的一个cg控制组
  • tasks:cgroup中控制/关联的进程或线程

cgroup是内核的特性,它通过Linux的VFS以一个文件系统的形式暴露给应用程序,即应用程序仅需操作此文件系统(对文件或目录的增删改查)即可调用cgroup实现功能。通常情况下Linux系统会以tmpfs类型将cgroups系统挂载到/sys/fs/cgroup目录

在cg v1中,各个subsystem和cgroup是以树形组织(hierarchical)的,这和tmpfs文件系统的层级结构对应,所以我们在/sys/fs/cgroup看到的目录一般就是一个subsystem或者一个cgroup

挂载子系统

在应用程序使用cg之前,我们先得明确使用哪些子系统,并将其挂载到cgroup的tmpfs文件系统中,下面演示下如何挂载cpu和memory子系统

# 挂载CPU子系统
mount -t cgroup -o cpu none /sys/fs/cgroup/cpu

# 挂载Memory子系统
mount -t cgroup -o memory none /sys/fs/cgroup/memory

下图是一个已挂载所有子系统的cgroup系统,其中一个目录对应一个子系统:

也可以从mount视角查看了具体的挂载情况:

移除子系统

移除子系统就是挂载的逆操作 执行umount即可 需要注意的是umount要注意改子系统中没有cgroup或者绑定的tasks

umount /sys/fs/cgroup/cpu
umount /sys/fs/cgroup/memory

新建cgroup

cgroup即是一组资源限制的配置集合,比如某个cg A的资源限制是 允许程序最多使用CPU 1c和内存1GB,这里面涉及了两种子系统,cpu和memory,我们需要分别在cpu和memory subsystem下面建立A cg,对应的文件系统操作即为:

mkdir -p /sys/fs/cgroup/cpu/A
mkdir -p /sys/fs/cgroup/memory/A

执行完成后,可以发现/sys/fs/cgroup/cpu/A目录多了很多文件:

目录/sys/fs/cgroup/memory/A也是如此,只不过文件名称略有差异:

/sys/fs/cgroup/cpu/A/sys/fs/cgroup/memory/A就是cgroup,我们可以通过上述的文件设置cgroup参数以及将进程绑定到此cgroup。

假设有个进程P(pid=1000),想让P拥有cg A的资源限制(最多使用CPU 1c和内存1GB),可以进行如下操作:

# 设置CPU限制1core, 100000 = 1 * 100000
echo 100000 > /sys/fs/cgroup/cpu/A/cpu.cfs_quota_us

# 设置MEM限制1GB, 1000000000 = 1000 * 1000 * 1000 bytes
echo 1000000000 > /sys/fs/cgroup/memory/A/memory.limit_in_bytes

# 将进程P绑定到cgroup A
echo 1000 >  /sys/fs/cgroup/cpu/A/cgroup.procs
echo 1000 >  /sys/fs/cgroup/memory/A/cgroup.procs

刚提到层级结构(hierarchical)也就很容易理解了,可以在cgroup A建立AChild cgroup,则AChild继承A的资源属性,它最多可以使用CPU 1c和内存1GB,也可以主动设置更小的参数。

这里不同的子系统的文件含义都不一样,同学们可以自行去查询手册了解 access.redhat.com/documentati…

删除cgroup

删除前先确保没有子group和绑定的进程,然后执行rmdir删除目录即可,cgroups会自动删除对应的cgroup系统

rmdir /sys/fs/cgroup/cpu/A
rmdir /sys/fs/cgroup/memory/A

cgroupv2

cgroupv2和v1功能上差距不大,v2去掉了子系统的层级结构设置,即上述挂载/移除子系统的操作,所以它的结构长这样:

如上图,将cgroup的subsystem简化为cpu.xxx、mem.xxx等文件,且全部放置在一层中,所以就没有子系统层级一说,每个cgroup都包含全部的子系统(v1中可能是对应部分子系统);

另外一点差别是,v2移除了tasks文件,且除了顶层cgroup外,其他cgroup如果有子目录,则其cgroup.procs的进程会自动移到其子cgroup,相当于所有的进程都绑定早cgroup树状结构的叶子结点,主要是为了便于管理

应用场景

cgroups在容器场景中应用非常广泛,docker就是采用cgroup实现的资源隔离。下面从一个实际的docker容器出发,探究docker是如何用cgroup做的资源限制:

  1. 首先使用docker启动一个nginx容器 docker run -id -m 512m nginx(这里的资源限制是最多允许512MiB的内存),获得容器ID 6cb4b2abec

  2. 然后查询该容器实际对应的Host级别的pid ps -ef | grep 6cb4b2abec,获得pid 3771505

  3. 这里的pid实际是docket进程的,需要进一步获得其内部nginx的进程pid。可执行ps -ef | grep 3771505 这里的pid 3771524才是容器内的nginx对应的host pid

  4. 根据pid查询其cgroup配置 cat /proc/3771524/cgroup 可以看到cgroup mem配置位于/docker/6cb4b2abec3079eaff7a2dcdfceb7df56a5ff7e051543192e92031eb336c2dd3,前面需要加上memory的subsystem的挂载点

  5. 进入该cgroup,查看内存限制 内存限额为536870912 = 512 * 1024 * 1024,刚好和512m对应

从上面可以看到,docker使用cg的原理也非常简单,就是用docker启动一个容器,然后根据容器id新建一个cg,设置好资源限额,然后将容器内的进程加入到cg的cgroup.procs中,从而实现限制。

Namespace

在容器场景中,常常和cgroup配合的是Linux的namespace技术,它和cg的区别在于ns是用于控制程序间资源的可见性,主要用作隔离;cg是用于做资源限制和分配。Linux的namespace技术包括Cgroup(cgroup的根目录)、IPC(进程通信)、Network(网络)、Mount(挂载点)、PID(pid系统)、Time(时间)、User(用户)、UTS(主机名),未来可以做一期详细讲解下,各位同学们注意区分。