简介
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做的资源限制:
-
首先使用docker启动一个nginx容器
docker run -id -m 512m nginx(这里的资源限制是最多允许512MiB的内存),获得容器ID 6cb4b2abec -
然后查询该容器实际对应的Host级别的pid
ps -ef | grep 6cb4b2abec,获得pid 3771505 -
这里的pid实际是docket进程的,需要进一步获得其内部nginx的进程pid。可执行
ps -ef | grep 3771505这里的pid 3771524才是容器内的nginx对应的host pid
-
根据pid查询其cgroup配置
cat /proc/3771524/cgroup可以看到cgroup mem配置位于
/docker/6cb4b2abec3079eaff7a2dcdfceb7df56a5ff7e051543192e92031eb336c2dd3,前面需要加上memory的subsystem的挂载点 -
进入该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(主机名),未来可以做一期详细讲解下,各位同学们注意区分。