Skip to main content

cgroup 观察和实践篇

《5分钟Docker容器原理 | 开篇》 中,我们提到,为了限制 Docker容器 运行时能够使用的最大资源,我们用到了 Linux cgroup 技术。这篇我们继续来了解下这个技术。

Linux cgroup 介绍

在一个容器中,假如不对其进行资源限制,允许其使用无限大的内存和CPU资源,有时候因为一些内存泄漏,代码逻辑问题,把整个宿主机的内存资源和CPU资源全部占满,影响宿主机上其他进程的允许。为了避免这样的情况发生,我们就必须对容器进行资源限制。
Linux cgroup, 全称 Linux crontrol group, 最初是由Google的工程师提出,后被整合进Linux内核中。Linux cgroup 是一种可以限制、记录、隔离进程组(process groups)所使用的物理资源(如:内存、CPU、IO等)的机制。
Linux cgroup 的功能如下:

功能描述
资源限制(Resource limitation)cgroup 可以对进程组使用的资源总额进行限制。比如我们可以设置应用运行时内存上限时,一旦超过整个配额(quota)就会发出OOM(Out of Memory)
优先级分配(Prioritization)cgroup 能够控制进程运行的优先级,实际上这个控制是通过限制分配的CPU时间片数量以及硬盘IO、带宽大小来实现的
资源统计(Accounting)cgroup 可以统计系统的资源总使用量,比如内存用量、CPU使用时长的。
进程控制(Process Control)cgroup 可以对进程组进行挂起、恢复等操作

Linux cgroup的API是以一个伪文件系统的方式实现,所有资源管理的功能都以子系统(subsystem)的方式管理。比如 CPU 的 cgroup 就一般会放在 /sys/fs/cgroup/cpu里面,内存的 cgroup 就一般会放在 /sys/fs/cgroup/memory里面。

观察一个Docker容器

我们首先通过busybox镜像run一个容器,指定最大CPU配额为0.2,也就是容器能够使用的最大CPU个数为0.2个。

$ docker run -it --rm --cpus=0.2 --name busybox busybox sh
/ #
/ # while true; do a=1; done;

我们在里面运行了一个死循环,我们另开一个terminal,用docker stats指令观察容器的CPU利用率

$ docker stats busybox
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
565d245db66c busybox 19.97% 148KiB / 62.65GiB 0.00% 0B / 0B 0B / 0B 1

能够看到CPU利用率一直在 20% 附近波动,证明参数是生效的。接下来我们到 /sys/fs/cgroup/cpu下面,找到docker分组下面对应容器ID的目录

$ cd /sys/fs/cgroup/cpu
$ ls
cgroup.clone_children cpuacct.stat cpu.cfs_quota_us cpu.stat notify_on_release tasks
cgroup.event_control cpuacct.usage cpu.rt_period_us docker release_agent
cgroup.procs cpuacct.usage_percpu cpu.rt_runtime_us kubepods scs
cgroup.sane_behavior cpu.cfs_period_us cpu.shares machine.slice system.slice
$ cd docker/
$ ls
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 cgroup.clone_children
565d245db66c2e3fba3d6dec90a49ee03b2c90b9e8ca5ab40fd2d20bc2059562
cgroup.event_control cgroup.procs cpuacct.stat notify_on_release
tasks
$ cd 565d245db66c2e3fba3d6dec90a49ee03b2c90b9e8ca5ab40fd2d20bc2059562/
$ ls
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

能够看到这里就是cgroup对我们刚刚那个容器进程的CPU限制目录。我们关注2个文件,一个是tasks,里面配置了资源受限的进程ID,另一个是cpu.cfs_quota_us,里面配置了最大CPU额度(quota)。

$ cat cpu.cfs_quota_us
20000
$ cat tasks
146058

在CPU的cgroup中,一个CPU核心被切割成了10万份,这里配置了2万,也就是0.2个CPU

CPU cgroup的实践

接下来我们模拟下Docker容器,创建一个CPU受限的进程。
首先在/sys/fs/cgroup/cpu目录下面创建一个目录ob01

# sudo su
# cd /sys/fs/cgroup/cpu
# mkdir ob01
# cd ob01/
# ls -l
total 0
-rw-r--r--. 1 root root 0 Dec 29 08:42 cgroup.clone_children
--w--w--w-. 1 root root 0 Dec 29 08:42 cgroup.event_control
-rw-r--r--. 1 root root 0 Dec 29 08:42 cgroup.procs
-r--r--r--. 1 root root 0 Dec 29 08:42 cpuacct.stat
-rw-r--r--. 1 root root 0 Dec 29 08:42 cpuacct.usage
-r--r--r--. 1 root root 0 Dec 29 08:42 cpuacct.usage_percpu
-rw-r--r--. 1 root root 0 Dec 29 08:42 cpu.cfs_period_us
-rw-r--r--. 1 root root 0 Dec 29 08:42 cpu.cfs_quota_us
-rw-r--r--. 1 root root 0 Dec 29 08:42 cpu.rt_period_us
-rw-r--r--. 1 root root 0 Dec 29 08:42 cpu.rt_runtime_us
-rw-r--r--. 1 root root 0 Dec 29 08:42 cpu.shares
-r--r--r--. 1 root root 0 Dec 29 08:42 cpu.stat
-rw-r--r--. 1 root root 0 Dec 29 08:42 notify_on_release
-rw-r--r--. 1 root root 0 Dec 29 08:42 tasks

可以看到系统已经帮我们创建了一系列的配置文件了,接下来我们将把CPU配额cpu.cfs_quota_us修改成5万,然后把当前进程ID加入到tasks文件中

# echo "50000" > cpu.cfs_quota_us
# echo $$
152314
# echo $$ > tasks
# while true; do a=1; done;

然后打开另外一个terminal,用top -p 指令查看CPU利用率

$ top -p 152314
top - 21:50:40 up 86 days, 1:36, 2 users, load average: 0.90, 0.61, 0.60
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
%Cpu(s): 1.8 us, 0.2 sy, 0.0 ni, 97.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 65689704 total, 4661644 free, 20283872 used, 40744188 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 41196384 avail Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
152314 root 20 0 116796 3624 1800 R 49.8 0.0 0:12.89 bash
......

可以看到CPU利用率也是稳定在 50%, 和我们的限额是保持一致的。

The End

目前我们已经观察和试验过了Docker容器底层的2个重要技术,Linux namespace的隔离,还有Linux cgroup的限额(如果Linux namespace隔离部分还没看的话可以跳转《5分钟Docker容器原理 | namespace 观察和实践篇》)虽然Docker容器听起来是一个很玄的概念,但是经过我们分析,其实也是一个特殊的进程,并且在我们了解了隔离和限额2个技术以后,它神秘的面纱也被慢慢解开,原理一了解,回过头来看,其实也就这么回事。
接下来我还会继续更新这个系列的其他文章,希望对大家理解Docker原理带来帮助。
我是老骨头,下次再见。