容器基础之 cgroups 学习

容器基础之 cgroups 学习

cgroups 简单介绍

老规矩,按照国际惯例,先百度、谷歌下这到底是个啥?到底有啥用?

Linux cgroups 的全称是 Linux Control Groups,它是 Linux 内核的特性,主要作用是限制、记录和隔离进程组(process groups)使用的物理资源(cpu、memory、IO 等)。2006 的时候,Google 的一些工程师(主要是 Paul Menage 和 Rohit Seth)启动了这个项目,最初的名字叫 process containers。因为 container 在内核中名字有歧义,2007 的时候改名为 control groups,并合并到 2008 年发布的 2.6.24 内核版本。最初 cgroups 的版本被称为 v1,这个版本的 cgroups 设计并不友好,理解起来非常困难。后续的开发工作由 Tejun Heo 接管,他重新设计并重写了 cgroups,新版本被称为 v2,并首次出现在 kernel 4.5 版本。

目前 cgroups 已经成为了很多技术的基础,比如:LXC、docker、systemd

有了 cgroups 这个技术,就可以很方便的限制 CPU、内存等资源,所以了解、掌握 cgroups 技术对我们学习容器是非常有必要的。

概念及原理

cgroups 主要作用

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

  • 资源限制:cgroups 可以对任务资源总额进行限制。比如设定任务运行时使用的内存上限,一旦超出就发 OOM。
  • 优先级分配:通过分配的 CPU 时间片数量和磁盘 IO 带宽,实际上就等同于控制了任务运行的优先级。
  • 资源统计:cgoups 可以统计系统的资源使用量,比如 CPU 使用时长、内存用量等。这个功能非常适合当前云端产品按使用量计费的方式。
  • 任务控制:cgroups 可以对任务执行挂起、恢复等操作。

它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。

cgroups 子系统

cgroups 的全称是 control groups,cgroups 为每种可以控制的资源定义了一个子系统。典型的子系统介绍如下:

  1. cpu 子系统,主要限制进程的 cpu 使用率。
  2. cpuacct 子系统,可以统计 cgroups 中的进程的 cpu 使用报告。
  3. cpuset 子系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点。
  4. memory 子系统,可以限制进程的 memory 使用量。
  5. blkio 子系统,可以限制进程的块设备 io。
  6. devices 子系统,可以控制进程能够访问某些设备。
  7. net_cls 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制。
  8. freezer 子系统,可以挂起或者恢复 cgroups 中的进程。
  9. ns 子系统,可以使不同 cgroups 下面的进程使用不同的 namespace。

cgroups 层级结构(Hierarchy)

内核使用 cgroup 结构体来表示一个 control group 对某一个或者某几个 cgroups 子系统的资源限制。cgroup 结构体可以组织成一颗树的形式,每一棵 cgroup 结构体组成的树称之为一个 cgroups 层级结构。cgroups 层级结构可以 attach 一个或者几个 cgroups 子系统,当前层级结构可以对其 attach 的 cgroups 子系统进行资源的限制。每一个 cgroups 子系统只能被 attach 到一个 cpu 层级结构中。

比如上图表示两个 cgroups 层级结构,每一个层级结构中是一颗树形结构,树的每一个节点是一个 cgroup 结构体(比如 cpu_cgrp, memory_cgrp)。第一个 cgroups 层级结构 attach 了 cpu 子系统和 cpuacct 子系统, 当前 cgroups 层级结构中的 cgroup 结构体就可以对 cpu 的资源进行限制,并且对进程的 cpu 使用情况进行统计。 第二个 cgroups 层级结构 attach 了 memory 子系统,当前 cgroups 层级结构中的 cgroup 结构体就可以对 memory 的资源进行限制。

在每一个 cgroups 层级结构中,每一个节点(cgroup 结构体)可以设置对资源不同的限制权重。比如上图中 cgrp1 组中的进程可以使用 60%的 cpu 时间片,而 cgrp2 组中的进程可以使用 20%的 cpu 时间片。

cgroups 文件系统接口

在 Linux 中,Cgroups 给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下,下面以腾讯云主机 ubuntu16.04 为例

$ mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)

它的输出结果,是一系列文件系统目录,我们单独看下关于 cpu 的子系统是如何进行限制的

$ ls  /sys/fs/cgroup/cpu
cgroup.clone_children  cpuacct.usage              cpuacct.usage_sys   cpu.stat           user.slice
cgroup.procs           cpuacct.usage_all          cpuacct.usage_user  notify_on_release
cgroup.sane_behavior   cpuacct.usage_percpu       cpu.cfs_period_us   release_agent
cpuacct.usage_percpu_sys   cpu.cfs_quota_us    system.slice    cpuacct.stat
cpuacct.usage_percpu_user  cpu.shares          tasks

需要特别注意下面这两个关键文件:``cfs_periodcfs_quota`

这两个参数需要组合使用,可以用来限制进程在长度为 cfs_period 的一段时间内,只能被分配到总量为 cfs_quota 的 CPU 时间。

现在,我们需要在这个 cpu 子系统里新建一个目录,例如:container

$ cd /sys/fs/cgroup/cpu
$ sudo mkdir container

这个目录就称为一个“控制组”。你会发现,操作系统会在你新创建的 container 目录下,自动生成该子系统对应的资源限制文件

$ ls container/
cgroup.clone_children  cpuacct.usage_all          cpuacct.usage_sys   cpu.shares
cgroup.procs           cpuacct.usage_percpu       cpuacct.usage_user  cpu.stat
cpuacct.stat           cpuacct.usage_percpu_sys   cpu.cfs_period_us   notify_on_release
cpuacct.usage          cpuacct.usage_percpu_user  cpu.cfs_quota_us    tasks

执行一个脚本,用来测试 cpu 限制情况

$ while : ; do : ; done &
[1] 12155

重新打开一个新的终端,使用top命令你会看到有一个bash进程,已经把服务器的 cpu 占到了 100%,进程号正是 12155,即刚我们执行的一个死循环的脚本

此时,我们可以通过查看 container 目录下的文件,看到 container 控制组里的 CPU quota 还没有任何限制(即:-1),CPU period 则是默认的 100 ms(100000 us)

$ cat /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us
-1
$ cat /sys/fs/cgroup/cpu/container/cpu.cfs_period_us
100000

接下来,我们可以通过修改这些文件的内容来设置限制。比如,向 container 组里的 cfs_quota 文件写入 20 ms(20000 us)

$ echo 20000 > /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us

这条命令的含义是在每 100 ms 的时间里,被该控制组限制的进程只能使用 20 ms 的 CPU 时间,也就是说这个进程只能使用到 20% 的 CPU 带宽。

接下来,我们把被限制的进程的 PID 写入 container 组里的 tasks 文件,上面的设置就会对该进程生效了:

$ echo 12155 > /sys/fs/cgroup/cpu/container/tasks

再次使用 top 命令,你会非常明显的看到,cpu 很快的被限制到了 20%

Linux Cgroups 的设计还是比较易用的,它就是一个子系统目录加上一组资源限制文件的组合。而对于 Docker 等 Linux 容器项目来说,它们只需要在每个子系统下面,为每个容器创建一个控制组(即创建一个新目录),然后在启动容器进程之后,把这个进程的 PID 填写到对应控制组的 tasks 文件中就可以了

好的,下面看看在 docker 里是怎么进行限制的。

运行一个容器

$ sudo docker run -it -d --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash

查看该容器的 container id

$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
547e871365af        ubuntu              "/bin/bash"         2 minutes ago       Up 2 minutes                            sad_noyce

进到这个新创建的控制组中

$ cd /sys/fs/cgroup/cpu/docker/547e871365af6c4f4e835b8c3cfe0355c865eb2a692faab0f8ec82dd468e987b

可以很明显的看到系统自动生成了一堆文件

$ ls -l
total 0
-rw-r--r-- 1 root root 0 Jul 27 23:01 cgroup.clone_children
-rw-r--r-- 1 root root 0 Jul 27 22:59 cgroup.procs
-r--r--r-- 1 root root 0 Jul 27 23:01 cpuacct.stat
-rw-r--r-- 1 root root 0 Jul 27 23:01 cpuacct.usage
-r--r--r-- 1 root root 0 Jul 27 23:01 cpuacct.usage_all
-r--r--r-- 1 root root 0 Jul 27 23:01 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 Jul 27 23:01 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 Jul 27 23:01 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 Jul 27 23:01 cpuacct.usage_sys
-r--r--r-- 1 root root 0 Jul 27 23:01 cpuacct.usage_user
-rw-r--r-- 1 root root 0 Jul 27 22:59 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Jul 27 22:59 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Jul 27 23:01 cpu.shares
-r--r--r-- 1 root root 0 Jul 27 23:01 cpu.stat
-rw-r--r-- 1 root root 0 Jul 27 23:01 notify_on_release
-rw-r--r-- 1 root root 0 Jul 27 23:01 tasks

分别查看 cpu-period 和 cpu-quota 的值

$ cat cpu.cfs_period_us
100000

$ cat cpu.cfs_quota_us
20000

到这里,这个容器只能使用 20%的 cpu

可以关注下我的微信公众号哦,一起学习进步

posted @ 2020-10-20 22:58  dogfei  阅读(453)  评论(0)    收藏  举报