Docker Cgroups——Docker 资源限制背后的技术原理

Posted iRayCheung

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Docker Cgroups——Docker 资源限制背后的技术原理相关的知识,希望对你有一定的参考价值。

Docker Cgroups——Docker 资源限制背后的技术原理

虽然在容器内部进程只能看到“掩饰”过的视图,但是在宿主机上,它就是一个普通的进程,与其他所有进程之间是平等竞争的关系。这就意味着虽然表面上被隔离了,但它实际上在与其他进程共享资源。

Cgroups(控制组) 是 Linux 内核的另一个特性,全称叫 Linux Control Group,用来限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等资源。Cgroups 还可以对进程进行优先级设置、审计。

在 Linux 中,Cgroups 以文件和目录的方式组织在 /sys/fs/cgroup 路径下。

$ ll /sys/fs/cgroup/
total 0
drwxr-xr-x 15 root root 380 May  3 14:50 ./
drwxr-xr-x  9 root root   0 May  3 20:29 ../
dr-xr-xr-x  5 root root   0 May  3 14:50 blkio/
lrwxrwxrwx  1 root root  11 May  3 14:50 cpu -> cpu,cpuacct/
dr-xr-xr-x  5 root root   0 May  3 14:50 cpu,cpuacct/
lrwxrwxrwx  1 root root  11 May  3 14:50 cpuacct -> cpu,cpuacct/
dr-xr-xr-x  3 root root   0 May  3 14:50 cpuset/
dr-xr-xr-x  5 root root   0 May  3 14:50 devices/
dr-xr-xr-x  3 root root   0 May  3 14:50 freezer/
dr-xr-xr-x  3 root root   0 May  3 14:50 hugetlb/
dr-xr-xr-x  5 root root   0 May  3 14:50 memory/
lrwxrwxrwx  1 root root  16 May  3 14:50 net_cls -> net_cls,net_prio/
dr-xr-xr-x  3 root root   0 May  3 14:50 net_cls,net_prio/
lrwxrwxrwx  1 root root  16 May  3 14:50 net_prio -> net_cls,net_prio/
dr-xr-xr-x  3 root root   0 May  3 14:50 perf_event/
dr-xr-xr-x  5 root root   0 May  3 14:50 pids/
dr-xr-xr-x  2 root root   0 May  3 14:50 rdma/
dr-xr-xr-x  6 root root   0 May  3 14:50 systemd/
dr-xr-xr-x  5 root root   0 May  3 14:50 unified/

这个路径下的子目录,都是这台机器可以被 Cgroups 限制的资源种类,也叫子系统。

$ ll /sys/fs/cgroup/cpu,cpuacct/
total 0
dr-xr-xr-x  5 root root   0 May  3 20:30 ./
drwxr-xr-x 15 root root 380 May  3 14:50 ../
-rw-r--r--  1 root root   0 May  3 20:38 cgroup.clone_children
-rw-r--r--  1 root root   0 May  3 20:38 cgroup.procs
-r--r--r--  1 root root   0 May  3 20:38 cgroup.sane_behavior
-rw-r--r--  1 root root   0 May  3 15:04 cpu.cfs_period_us
-rw-r--r--  1 root root   0 May  3 15:04 cpu.cfs_quota_us
-rw-r--r--  1 root root   0 May  3 15:04 cpu.shares
-r--r--r--  1 root root   0 May  3 20:38 cpu.stat
-r--r--r--  1 root root   0 May  3 20:38 cpuacct.stat
-rw-r--r--  1 root root   0 May  3 20:38 cpuacct.usage
-r--r--r--  1 root root   0 May  3 20:38 cpuacct.usage_all
-r--r--r--  1 root root   0 May  3 20:38 cpuacct.usage_percpu
-r--r--r--  1 root root   0 May  3 20:38 cpuacct.usage_percpu_sys
-r--r--r--  1 root root   0 May  3 20:38 cpuacct.usage_percpu_user
-r--r--r--  1 root root   0 May  3 20:38 cpuacct.usage_sys
-r--r--r--  1 root root   0 May  3 20:38 cpuacct.usage_user
drwxr-xr-x  2 root root   0 May  3 15:04 docker/
-rw-r--r--  1 root root   0 May  3 20:38 notify_on_release
-rw-r--r--  1 root root   0 May  3 20:38 release_agent
drwxr-xr-x 88 root root   0 May  3 15:03 system.slice/
-rw-r--r--  1 root root   0 May  3 20:38 tasks
drwxr-xr-x  4 root root   0 May  3 20:38 user.slice/

大多数都是文件,里面保存着一些配置参数。

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

我们首先来构建一个跑 Python 死循环脚本的容器镜像:

Dockerfile 如下:

FROM python:3.6.8-alpine3.9
RUN echo -e "while True:\\n    pass" > app.py
CMD ["python3", "app.py"]

这个脚本为了吃满 CPU。

接下来直接构建一个 Docker 镜像:

$ docker build -t cgroups_test:latest .
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM python:3.6.8-alpine3.9
3.6.8-alpine3.9: Pulling from library/python
bdf0201b3a05: Pull complete
59c926705abf: Pull complete
dd4853571cc7: Pull complete
3a45eb710779: Pull complete
f4700a9d59e5: Pull complete
Digest: sha256:54b604a4f1937b370d2daf2344594dbd76305048b705ca786cd467125f36759d
Status: Downloaded newer image for python:3.6.8-alpine3.9
 ---> ed8897654bd1
Step 2/3 : RUN echo -e "while True:\\n    pass" > app.py
 ---> Running in fbd12ca97a57
Removing intermediate container fbd12ca97a57
 ---> d1e64aa6624f
Step 3/3 : CMD ["python3", "app.py"]
 ---> Running in b5834c5ef957
Removing intermediate container b5834c5ef957
 ---> a76fa98dd418
Successfully built a76fa98dd418
Successfully tagged cgroups_test:latest
docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
cgroups_test        latest              a76fa98dd418        3 minutes ago       79.1MB
python              3.6.8-alpine3.9     ed8897654bd1        8 days ago          79.1MB

直接启动容器:

$ docker run -d cgroups_test
$ top
top - 22:20:15 up  5:35,  2 users,  load average: 0.72, 0.24, 0.09
Tasks: 172 total,   2 running, 132 sleeping,   0 stopped,   0 zombie
%Cpu(s): 50.2 us,  0.3 sy,  0.0 ni, 49.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  4034008 total,  2943452 free,   439008 used,   651548 buff/cache
KiB Swap:  2097148 total,  2095100 free,     2048 used.  3355620 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
29223 root      20   0    7916   7184   2876 R  99.7  0.2   1:12.09 python3

python3(容器进程) 对 CPU 的占用率已经接近百分百了。。。

docker ps看一下容器的 ID:

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
9226c3249105        cgroups_test        "python3 app.py"    8 minutes ago       Up 3 minutes                            reverent_euclid
$ docker inspect --format=".Id" reverent_euclid
9226c3249105f42d04b433baa134af9bb10891de2df72fe2edfc6f2bb0a41fb2

得到完整的 container ID:9226c3249105f42d04b433baa134af9bb10891de2df72fe2edfc6f2bb0a41fb2

然后查看 /sys/fs/cgroup/cpu,cpuacct/docker/ 路径

ll /sys/fs/cgroup/cpu,cpuacct/docker/
total 0
drwxr-xr-x 3 root root 0 May  3 22:24 ./
dr-xr-xr-x 5 root root 0 May  3 20:30 ../
drwxr-xr-x 2 root root 0 May  3 22:24 9226c3249105f42d04b433baa134af9bb10891de2df72fe2edfc6f2bb0a41fb2/
-rw-r--r-- 1 root root 0 May  3 22:25 cgroup.clone_children
-rw-r--r-- 1 root root 0 May  3 22:25 cgroup.procs
-rw-r--r-- 1 root root 0 May  3 22:25 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 May  3 22:25 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 May  3 22:25 cpu.shares
-r--r--r-- 1 root root 0 May  3 22:25 cpu.stat
-r--r--r-- 1 root root 0 May  3 22:25 cpuacct.stat
-rw-r--r-- 1 root root 0 May  3 22:25 cpuacct.usage
-r--r--r-- 1 root root 0 May  3 22:25 cpuacct.usage_all
-r--r--r-- 1 root root 0 May  3 22:25 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 May  3 22:25 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 May  3 22:25 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 May  3 22:25 cpuacct.usage_sys
-r--r--r-- 1 root root 0 May  3 22:25 cpuacct.usage_user
-rw-r--r-- 1 root root 0 May  3 22:25 notify_on_release
-rw-r--r-- 1 root root 0 May  3 22:25 tasks

看到 docker 路径下多了一个 9226c3249105f42d04b433baa134af9bb10891de2df72fe2edfc6f2bb0a41fb2 子目录,就是正在运行的容器的 ID。

$ ll /sys/fs/cgroup/cpu,cpuacct/docker/9226c3249105f42d04b433baa134af9bb10891de2df72fe2edfc6f2bb0a41fb2/
total 0
drwxr-xr-x 2 root root 0 May  3 22:27 ./
drwxr-xr-x 3 root root 0 May  3 22:24 ../
-rw-r--r-- 1 root root 0 May  3 22:27 cgroup.clone_children
-rw-r--r-- 1 root root 0 May  3 22:24 cgroup.procs
-rw-r--r-- 1 root root 0 May  3 22:27 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 May  3 22:27 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 May  3 22:27 cpu.shares
-r--r--r-- 1 root root 0 May  3 22:27 cpu.stat
-r--r--r-- 1 root root 0 May  3 22:27 cpuacct.stat
-rw-r--r-- 1 root root 0 May  3 22:27 cpuacct.usage
-r--r--r-- 1 root root 0 May  3 22:27 cpuacct.usage_all
-r--r--r-- 1 root root 0 May  3 22:27 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 May  3 22:27 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 May  3 22:27 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 May  3 22:27 cpuacct.usage_sys
-r--r--r-- 1 root root 0 May  3 22:27 cpuacct.usage_user
-rw-r--r-- 1 root root 0 May  3 22:27 notify_on_release
-rw-r--r-- 1 root root 0 May  3 22:27 tasks

这里都是9226c3249105f42d04b433baa134af9bb10891de2df72fe2edfc6f2bb0a41fb2 控制组 CPU 资源的配置。

$ cat /sys/fs/cgroup/cpu,cpuacct/docker/9226c3249105f42d04b433baa134af9bb10891de2df72fe2edfc6f2bb0a41fb2/cpu.cfs_period_us
100000
$ cat /sys/fs/cgroup/cpu,cpuacct/docker/9226c3249105f42d04b433baa134af9bb10891de2df72fe2edfc6f2bb0a41fb2/cpu.cfs_quota_us
-1

可以看到 9226c3249105f42d04b433baa134af9bb10891de2df72fe2edfc6f2bb0a41fb2 控制组 CPU 周期是默认的 100ms,而 CPU 配额没有任何限制,所以 python3 进程直接就吃满了整个 CPU。

接下来我们这样修改文件内容:

$ echo 20000 > /sys/fs/cgroup/cpu,cpuacct/docker/9226c3249105f42d04b433baa134af9bb10891de2df72fe2edfc6f2bb0a41fb2/cpu.cfs_quota_us

代表在每 100ms 的时间里,被 9226c3249105f42d04b433baa134af9bb10891de2df72fe2edfc6f2bb0a41fb2 控制组限制的进程只能使用 20ms 的 CPU 时间,也就是这个进程最多只能使用 20% 的 CPU 带宽。

我们来看一下 tasks 文件:

cat /sys/fs/cgroup/cpu,cpuacct/docker/9226c3249105f42d04b433baa134af9bb10891de2df72fe2edfc6f2bb0a41fb2/tasks
29223

如果对数字敏感的话,马上就可以发现这个数字就是容器进程 python3 的 PID!

现在我们 top 看一下进程对 CPU 资源的占用:

$ top
top - 22:58:05 up  6:13,  2 users,  load average: 0.41, 0.84, 0.90
Tasks: 173 total,   2 running, 132 sleeping,   0 stopped,   0 zombie
%Cpu(s): 10.6 us,  0.3 sy,  0.0 ni, 89.1 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  4034008 total,  2968868 free,   412624 used,   652516 buff/cache
KiB Swap:  2097148 total,  2095100 free,     2048 used.  3381924 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
  29223 root      20   0    6180   5528   2816 R  20.3  0.1   9:33.65 python3

python3 进程的 CPU 使用率已经降到了 20% 左右!

下面停掉这个容器,重新创建一个限制 CPU 使用的容器:

$ docker container stop 9226c3249105
9226c3249105
$ docker run --cpu-period=100000 --cpu-quota=20000 -d cgroups_test
5218de26ad09db66a92b5c6f8880e99e48012d3cb2148eeff0ce481775b27f02
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
5218de26ad09        cgroups_test        "python3 app.py"    1 second ago        Up 1 second                             flamboyant_taussig
$ top
top - 23:04:04 up  6:19,  2 users,  load average: 0.11, 0.37, 0.67
Tasks: 177 total,   2 running, 135 sleeping,   0 stopped,   0 zombie
%Cpu(s): 10.5 us,  0.3 sy,  0.0 ni, 89.2 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  4034008 total,  2959716 free,   420932 used,   653360 buff/cache
KiB Swap:  2097148 total,  2095100 free,     2048 used.  3373504 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 3162 root      20   0    7916   7168   2868 R  19.6  0.2   0:13.86 python3

PID 为3162的 python3 进程在启动后的 CPU 占用率就在20%左右。

接下来确认容器对应控制组中的参数配置:

$ ll /sys/fs/cgroup/cpu,cpuacct/docker/5218de26ad09db66a92b5c6f8880e99e48012d3cb2148eeff0ce481775b27f02/
total 0
drwxr-xr-x 2 root root 0 May  3 23:06 ./
drwxr-xr-x 3 root root 0 May  3 23:02 ../
-rw-r--r-- 1 root root 0 May  3 23:06 cgroup.clone_children
-rw-r--r-- 1 root root 0 May  3 23:02 cgroup.procs
-rw-r--r-- 1 root root 0 May  3 23:02 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 May  3 23:02 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 May  3 23:06 cpu.shares
-r--r--r-- 1 root root 0 May  3 23:06 cpu.stat
-r--r--r-- 1 root root 0 May  3 23:06 cpuacct.stat
-rw-r--r-- 1 root root 0 May  3 23:06 cpuacct.usage
-r--r--r-- 1 root root 0 May  3 23:06 cpuacct.usage_all
-r--r--r-- 1 root root 0 May  3 23:06 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 May  3 23:06 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 May  3 23:06 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 May  3 23:06 cpuacct.usage_sys
-r--r--r-- 1 root root 0 May  3 23:06 cpuacct.usage_user
-rw-r--r-- 1 root root 0 May  3 23:06 notify_on_release
-rw-r--r-- 1 root root 0 May  3 23:06 tasks
$ cat /sys/fs/cgroup/cpu,cpuacct/docker/5218de26ad09db66a92b5c6f8880e99e48012d3cb2148eeff0ce481775b27f02/cpu.cfs_period_us
100000
$ cat /sys/fs/cgroup/cpu,cpuacct/docker/5218de26ad09db66a92b5c6f8880e99e48012d3cb2148eeff0ce481775b27f02/cpu.cfs_quota_us
20000
$ cat /sys/fs/cgroup/cpu,cpuacct/docker/5218de26ad09db66a92b5c6f8880e99e48012d3cb2148eeff0ce481775b27f02/tasks
3162

Docker 在启动容器的时候就把 cpu-period 和 cpu-quota 参数写到了对应的控制组中。

总结一下,Linux Cgroups 就是一个子系统目录加上一组资源配置文件的组合。Docker 在启动容器时在每个子系统下为容器创建一个控制组(目录名为 container ID),修改相应的配置参数,把容器进程的 PID 填到 tasks 文件中就完事了。

容器的本质只是一个加了限定参数的进程。

但是 Linux Cgroups 对资源的限制最大的问题还是限制不彻底,Linux 下的 /proc 路径下存储着当前内核运行状态的一系列特殊文件,用户可以通过访问这些文件来查看系统信息,它们也是 top 查看 CPU 占用的数据来源。但在容器里执行 top,显示的信息居然是宿主机的数据。。。

这就要避免容器挂载宿主机的 /proc/stats 目录。lxcfs是一种不错的纠正方案。容器中进程读取相应文件内容时,LXCFS 的 FUSE 文件系统实现会从容器对应的控制组中读取正确的限制,从而使得应用获得正确的资源限制设定。

理解Docker:Docker 容器使用 cgroups 限制资源使用

    上一篇文章将到 Docker 容器使用 linux namespace 来隔离其运行环境,使得容器中的进程看起来就像爱一个独立环境中运行一样。但是,光有运行环境隔离还不够,因为这些进程还是可以不受限制地使用系统资源,比如网络、磁盘、CPU以及内存 等。为了让容器中的进程更加可控,Docker 使用 Linux cgroups 来限制容器中的进程允许使用的系统资源。 

1. 基础知识:Linux control groups 

1.1 概念

  Linux Cgroup 可???让???您???为???系???统???中???所???运???行???任???务???(进???程???)的???用???户???定???义???组???群???分???配???资???源??? — 比???如??? CPU 时???间???、???系???统???内???存???、???网???络???带???宽???或???者???这???些???资???源???的???组???合???。???您???可???以???监???控???您???配???置???的??? cgroup,拒???绝??? cgroup 访???问???某???些???资???源???,甚???至???在???运???行???的???系???统???中???动???态???配???置???您???的??? cgroup。所以,可以将 controll groups 理解为 controller (system resource) (for) (process)groups,也就是是说它以一组进程为目标进行系统资源分配和控制。

  它主要提供了如下功能: 

  • Resource limitation: 限制资源使用,比如内存使用上限以及文件系统的缓存限制。
  • Prioritization: 优先级控制,比如:CPU利用和磁盘IO吞吐。
  • Accounting: 一些审计或一些统计,主要目的是为了计费。
  • Control: 挂起进程,恢复执行进程。

使???用??? cgroup,系???统???管???理???员???可???更???具???体???地???控???制???对???系???统???资???源???的???分???配???、???优???先???顺???序???、???拒???绝???、???管???理???和???监???控???。???可???更???好???地???根???据???任???务???和???用???户???分???配???硬???件???资???源???,提???高???总???体???效???率???。

在实践中,系统管理员一般会利用CGroup做下面这些事(有点像为某个虚拟机分配资源似的):

  • 隔离一个进程集合(比如:nginx的所有进程),并限制他们所消费的资源,比如绑定CPU的核。
  • 为这组进程分配其足够使用的内存
  • 为这组进程分配相应的网络带宽和磁盘存储限制
  • 限制访问某些设备(通过设置设备的白名单)

Linux 系统中,一切皆文件。Linux 也将 cgroups 实现成了文件系统,方便用户使用。在我的 Ubuntu 14.04 测试环境中:

技术分享
[email protected]:/home/sammy# mount -t cgroup
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu type cgroup (rw,relatime,cpu)
systemd on /sys/fs/cgroup/systemd type cgroup (rw,noexec,nosuid,nodev,none,name=systemd)
[email protected]:/home/sammy# lssubsys -m cpuset /sys/fs/cgroup/cpuset cpu /sys/fs/cgroup/cpu cpuacct /sys/fs/cgroup/cpuacct memory /sys/fs/cgroup/memory devices /sys/fs/cgroup/devices freezer /sys/fs/cgroup/freezer blkio /sys/fs/cgroup/blkio perf_event /sys/fs/cgroup/perf_event hugetlb /sys/fs/cgroup/hugetlb
[email protected]:/home/sammy# ls /sys/fs/cgroup/ -l total 0 drwxr-xr-x 3 root root 0 Sep 18 21:46 blkio drwxr-xr-x 3 root root 0 Sep 18 21:46 cpu drwxr-xr-x 3 root root 0 Sep 18 21:46 cpuacct drwxr-xr-x 3 root root 0 Sep 18 21:46 cpuset drwxr-xr-x 3 root root 0 Sep 18 21:46 devices drwxr-xr-x 3 root root 0 Sep 18 21:46 freezer drwxr-xr-x 3 root root 0 Sep 18 21:46 hugetlb drwxr-xr-x 3 root root 0 Sep 18 21:46 memory drwxr-xr-x 3 root root 0 Sep 18 21:46 perf_event drwxr-xr-x 3 root root 0 Sep 18 21:46 systemd
技术分享

我们看到 /sys/fs/cgroup 目录中有若干个子目录,我们可以认为这些都是受 cgroups 控制的资源以及这些资源的信息。

  • blkio — 这???个???子???系???统???为???块???设???备???设???定???输???入???/输???出???限???制???,比???如???物???理???设???备???(磁???盘???,固???态???硬???盘???,USB 等???等???)。
  • cpu — 这???个???子???系???统???使???用???调???度???程???序???提???供???对??? CPU 的??? cgroup 任???务???访???问???。???
  • cpuacct — 这???个???子???系???统???自???动???生???成??? cgroup 中???任???务???所???使???用???的??? CPU 报???告???。???
  • cpuset — 这???个???子???系???统???为??? cgroup 中???的???任???务???分???配???独???立??? CPU(在???多???核???系???统???)和???内???存???节???点???。???
  • devices — 这???个???子???系???统???可???允???许???或???者???拒???绝??? cgroup 中???的???任???务???访???问???设???备???。???
  • freezer — 这???个???子???系???统???挂???起???或???者???恢???复??? cgroup 中???的???任???务???。???
  • memory — 这???个???子???系???统???设???定??? cgroup 中???任???务???使???用???的???内???存???限???制???,并???自???动???生???成?????内???存???资???源使用???报???告???。???
  • net_cls — 这???个???子???系???统???使???用???等???级???识???别???符???(classid)标???记???网???络???数???据???包???,可???允???许??? Linux 流???量???控???制???程???序???(tc)识???别???从???具???体??? cgroup 中???生???成???的???数???据???包???。???
  • net_prio — 这个子系统用来设计网络流量的优先级
  • hugetlb — 这个子系统主要针对于HugeTLB系统进行限制,这是一个大页文件系统。

默认的话,在 Ubuntu 系统中,你可能看不到 net_cls 和 net_prio 目录,它们需要你手工做 mount:

技术分享
[email protected]:/sys/fs/cgroup# modprobe cls_cgroup
[email protected]:/sys/fs/cgroup# mkdir net_cls
[email protected]:/sys/fs/cgroup# mount -t cgroup -o net_cls none net_cls

[email protected]:/sys/fs/cgroup# modprobe netprio_cgroup
[email protected]:/sys/fs/cgroup# mkdir net_prio
[email protected]:/sys/fs/cgroup# mount -t cgroup -o net_prio none net_prio

[email protected]:/sys/fs/cgroup# ls net_prio/cgroup.clone_children  cgroup.procs          net_prio.ifpriomap  notify_on_release  tasks
cgroup.event_control   cgroup.sane_behavior  net_prio.prioidx    release_agent
[email protected]:/sys/fs/cgroup# ls net_cls/
cgroup.clone_children  cgroup.event_control  cgroup.procs  cgroup.sane_behavior  net_cls.classid  notify_on_release  release_agent  tasks
技术分享

1.2 实验

1.2.1 通过 cgroups 限制进程的 CPU

写一段最简单的 C 程序:

int main(void)
{
    int i = 0;
    for(;;) i++;
    return 0;
}

编译,运行,发现它占用的 CPU 几乎到了 100%:

top - 22:43:02 up  1:14,  3 users,  load average: 0.24, 0.06, 0.06  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 2304 root      20   0    4188    356    276 R 99.6  0.0   0:11.77 hello

接下来我们做如下操作:

技术分享
[email protected]:/home/sammy/c# mkdir /sys/fs/cgroup/cpu/hello
[email protected]:/home/sammy/c# cd /sys/fs/cgroup/cpu/hello
[email protected]:/sys/fs/cgroup/cpu/hello# ls
cgroup.clone_children  cgroup.procs       cpu.cfs_quota_us  cpu.stat           tasks
cgroup.event_control   cpu.cfs_period_us  cpu.shares        notify_on_release
[email protected]:/sys/fs/cgroup/cpu/hello# cat cpu.cfs_quota_us
-1
[email protected]:/sys/fs/cgroup/cpu/hello# echo 20000 > cpu.cfs_quota_us
[email protected]:/sys/fs/cgroup/cpu/hello# cat cpu.cfs_quota_us
20000
[email protected]:/sys/fs/cgroup/cpu/hello# echo 2428 > tasks
技术分享

然后再来看看这个进程的 CPU 占用情况:

 PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 2428 root      20   0    4188    356    276 R 19.9  0.0   0:46.03 hello

它占用的 CPU 几乎就是 20%,也就是我们预设的阈值。这说明我们通过上面的步骤,成功地将这个进程运行所占用的 CPU 资源限制在某个阈值之内了。

如果此时再启动另一个 hello 进程并将其 id 加入 tasks 文件,则两个进程会共享设定的 CPU 限制:

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 2428 root      20   0    4188    356    276 R 10.0  0.0 285:39.54 hello
12526 root      20   0    4188    356    276 R 10.0  0.0   0:25.09 hello

1.2.2 通过 cgroups 限制进程的 Memory

同样地,我们针对它占用的内存做如下操作:

技术分享
[email protected]:/sys/fs/cgroup/memory# mkdir hello
[email protected]:/sys/fs/cgroup/memory# cd hello/
[email protected]:/sys/fs/cgroup/memory/hello# cat memory.limit_in_bytes
18446744073709551615
[email protected]:/sys/fs/cgroup/memory/hello# echo 64k > memory.limit_in_bytes
[email protected]:/sys/fs/cgroup/memory/hello# echo 2428 > tasks
[email protected]:/sys/fs/cgroup/memory/hello#
技术分享

上面的步骤会把进程 2428 说占用的内存阈值设置为 64K。超过的话,它会被杀掉。

1.2.3 限制进程的 I/O

运行命令:

sudo dd if=/dev/sda1 of=/dev/null

通过 iotop 命令看 IO (此时磁盘在快速转动),此时其写速度为 242M/s:

 TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
 2555 be/4 root      242.60 M/s    0.00 B/s  0.00 % 61.66 % dd if=/dev/sda1 of=/dev/null

接着做下面的操作:

技术分享
[email protected]:/home/sammy# mkdir /sys/fs/cgroup/blkio/io
[email protected]:/home/sammy# cd /sys/fs/cgroup/blkio/io
[email protected]:/sys/fs/cgroup/blkio/io# ls -l /dev/sda1
brw-rw---- 1 root disk 8, 1 Sep 18 21:46 /dev/sda1
[email protected]:/sys/fs/cgroup/blkio/io# echo ‘8:0 1048576‘  > /sys/fs/cgroup/blkio/io/blkio.throttle.read_bps_device
[email protected]:/sys/fs/cgroup/blkio/io# echo 2725 > /sys/fs/cgroup/blkio/io/tasks
技术分享

结果,这个进程的IO 速度就被限制在 1Mb/s 之内了:

 TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
 2555 be/4 root      990.44 K/s    0.00 B/s  0.00 % 96.29 % dd if=/dev/sda1 of=/dev/null

1.3 术语

cgroups 的术语包括:

  • 任务(Tasks):就是系统的一个进程。
  • 控制组(Control Group):一组按照某种标准划分的进程,比如官方文档中的Professor和Student,或是WWW和System之类的,其表示了某进程组。Cgroups中的资源控制都是以控制组为单位实现。一个进程可以加入到某个控制组。而资源的限制是定义在这个组上,就像上面示例中我用的 hello 一样。简单点说,cgroup的呈现就是一个目录带一系列的可配置文件。
  • 层级(Hierarchy):控制组可以组织成hierarchical的形式,既一颗控制组的树(目录结构)。控制组树上的子节点继承父结点的属性。简单点说,hierarchy就是在一个或多个子系统上的cgroups目录树。
  • 子系统(Subsystem):一个子系统就是一个资源控制器,比如CPU子系统就是控制CPU时间分配的一个控制器。子系统必须附加到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制族群都受到这个子系统的控制。Cgroup的子系统可以有很多,也在不断增加中。

2. Docker 对 cgroups 的使用

2.1 默认情况

默认情况下,Docker 启动一个容器后,会在 /sys/fs/cgroup 目录下的各个资源目录下生成以容器 ID 为名字的目录(group),比如:

/sys/fs/cgroup/cpu/docker/03dd196f415276375f754d51ce29b418b170bd92d88c5e420d6901c32f93dc14

此时 cpu.cfs_quota_us 的内容为 -1,表示默认情况下并没有限制容器的 CPU 使用。在容器被 stopped 后,该目录被删除。

运行命令 docker run -d --name web41 --cpu-quota 25000 --cpu-period 100 --cpu-shares 30 training/webapp python app.py 启动一个新的容器,结果:

技术分享
[email protected]:/sys/fs/cgroup/cpu/docker/06bd180cd340f8288c18e8f0e01ade66d066058dd053ef46161eb682ab69ec24# cat cpu.cfs_quota_us
25000
[email protected]:/sys/fs/cgroup/cpu/docker/06bd180cd340f8288c18e8f0e01ade66d066058dd053ef46161eb682ab69ec24# cat tasks
3704
[email protected]:/sys/fs/cgroup/cpu/docker/06bd180cd340f8288c18e8f0e01ade66d066058dd053ef46161eb682ab69ec24# cat cpu.cfs_period_us
2000
技术分享

Docker 会将容器中的进程的 ID 加入到各个资源对应的 tasks 文件中。表示 Docker 也是以上面的机制来使用 cgroups 对容器的 CPU 使用进行限制。

相似地,可以通过 docker run 中 mem 相关的参数对容器的内存使用进行限制:

技术分享
      --cpuset-mems string          MEMs in which to allow execution (0-3, 0,1)
      --kernel-memory string        Kernel memory limit
  -m, --memory string               Memory limit
      --memory-reservation string   Memory soft limit
      --memory-swap string          Swap limit equal to memory plus swap: ‘-1‘ to enable unlimited swap
      --memory-swappiness int       Tune container memory swappiness (0 to 100) (default -1)
技术分享

比如  docker run -d --name web42 --blkio-weight 100 --memory 10M --cpu-quota 25000 --cpu-period 2000 --cpu-shares 30 training/webapp python app.py:

技术分享
[email protected]:/sys/fs/cgroup/memory/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410# cat memory.limit_in_bytes
10485760

  [email protected]:/sys/fs/cgroup/blkio/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410# cat blkio.weight
  100

技术分享

目前 docker 已经几乎支持了所有的 cgroups 资源,可以限制容器对包括 network,device,cpu 和 memory 在内的资源的使用,比如:

技术分享
[email protected]:/sys/fs/cgroup# find -iname ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./net_prio/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./net_cls/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./systemd/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./hugetlb/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./perf_event/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./blkio/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./freezer/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./devices/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./memory/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./cpuacct/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./cpu/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./cpuset/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
技术分享

 2.2 net_cls 

   net_cls 和 tc 一起使用可用于限制进程发出的网络包所使用的网络带宽。当使用 cgroups network controll net_cls 后,指定进程发出的所有网络包都会被加一个 tag,然后就可以使用其他工具比如 iptables 或者 traffic controller (TC)来根据网络包上的 tag 进行流量控制。关于 TC 的文档,网上很多,这里不再赘述,只是用一个简单的例子来加以说明。

  关于 classid,它的格式是 0xAAAABBBB,其中,AAAA 是十六进制的主ID(major number),BBBB 是十六进制的次ID(minor number)。因此,0X10001 表示 10:1,而 0x00010001 表示 1:!。

  (1)首先在host 的网卡 eth0 上做如下设置:

技术分享

tc qdisc del dev eth0 root                                        #删除已有的规则
tc qdisc add dev eth0 root handle 10: htb default 12              
tc class add dev eth0 parent 10: classid 10:1 htb rate 1500kbit ceil 1500kbit burst 10k         #限速
tc filter add dev eth0 protocol ip parent 10:0 prio 1 u32 match ip protocol 1 0xff flowid 10:1  #只处理 ping 参数的网络包

技术分享

其结果是:

  • 在网卡 eth0 上创建了一个 HTB root 队列,hangle 10: 表示队列句柄也就是major number 为 10
  • 创建一个分类 10:1,限制它的出发网络带宽为 80 kbit (千比特每秒)
  • 创建一个分类器,将 eth0 上 IP IMCP 协议 的 major ID 为 10 的 prio 为 1 的网络流量都分类到 10:1 类别

(2)启动容器

容器启动后,其 init 进程在host 上的 PID 就被加入到 tasks 文件中了:

[email protected]:/sys/fs/cgroup/net_cls/docker/ff8d9715b7e11a5a69446ff1e3fde3770078e32a7d8f7c1cb35d51c75768fe33# ps -ef | grep 10047
231072   10047 10013  1 07:08 ?        00:00:00 python app.py

设置 net_cls classid:

echo 0x100001 > net_cls.classid

再在容器启动一个 ping 进程,其 ID 也被加入到 tasks 文件中了。

(3)查看tc 情况: tc -s -d class show dev eth0

技术分享

Every 2.0s: tc -s class ls dev eth0 Wed Sep 21 04:07:56 2016

class htb 10:1 root prio 0 rate 1500Kbit ceil 1500Kbit burst 10Kb cburst 1599b
Sent 17836 bytes 182 pkt (dropped 0, overlimits 0 requeues 0)
rate 0bit 0pps backlog 0b 0p requeues 0
lended: 182 borrowed: 0 giants: 0
tokens: 845161 ctokens: 125161

技术分享

我们可以看到 tc 已经在处理 ping 进程产生的数据包了。再来看一下 net_cls 和 ts 合作的限速效果:

10488 bytes from 192.168.1.1: icmp_seq=35 ttl=63 time=12.7 ms
10488 bytes from 192.168.1.1: icmp_seq=36 ttl=63 time=15.2 ms
10488 bytes from 192.168.1.1: icmp_seq=37 ttl=63 time=4805 ms
10488 bytes from 192.168.1.1: icmp_seq=38 ttl=63 time=9543 ms

其中:

  • 后两条说使用的 tc class 规则是 tc class add dev eth0 parent 10: classid 10:1 htb rate 1500kbit ceil 15kbit burst 10k
  • 前两条所使用的 tc class 规则是 tc class add dev eth0 parent 10: classid 10:1 htb rate 1500kbit ceil 10Mbit burst 10k 

3. Docker run 命令中 cgroups 相关命令 

技术分享
block IO:
      --blkio-weight value          Block IO (relative weight), between 10 and 1000
      --blkio-weight-device value   Block IO weight (relative device weight) (default [])
      --cgroup-parent string        Optional parent cgroup for the container
CPU:
      --cpu-percent int             CPU percent (Windows only)
      --cpu-period int              Limit CPU CFS (Completely Fair Scheduler) period
      --cpu-quota int               Limit CPU CFS (Completely Fair Scheduler) quota
  -c, --cpu-shares int              CPU shares (relative weight)
      --cpuset-cpus string          CPUs in which to allow execution (0-3, 0,1)
      --cpuset-mems string          MEMs in which to allow execution (0-3, 0,1)
Device:    
      --device value                Add a host device to the container (default [])
      --device-read-bps value       Limit read rate (bytes per second) from a device (default [])
      --device-read-iops value      Limit read rate (IO per second) from a device (default [])
      --device-write-bps value      Limit write rate (bytes per second) to a device (default [])
      --device-write-iops value     Limit write rate (IO per second) to a device (default [])
Memory:      
      --kernel-memory string        Kernel memory limit
  -m, --memory string               Memory limit
      --memory-reservation string   Memory soft limit
      --memory-swap string          Swap limit equal to memory plus swap: ‘-1‘ to enable unlimited swap
      --memory-swappiness int       Tune container memory swappiness (0 to 100) (default -1)
技术分享

  

参考链接:

 
 

    上一篇文章将到 Docker 容器使用 linux namespace 来隔离其运行环境,使得容器中的进程看起来就像爱一个独立环境中运行一样。但是,光有运行环境隔离还不够,因为这些进程还是可以不受限制地使用系统资源,比如网络、磁盘、CPU以及内存 等。为了让容器中的进程更加可控,Docker 使用 Linux cgroups 来限制容器中的进程允许使用的系统资源。 

1. 基础知识:Linux control groups 

1.1 概念

  Linux Cgroup 可???让???您???为???系???统???中???所???运???行???任???务???(进???程???)的???用???户???定???义???组???群???分???配???资???源??? — 比???如??? CPU 时???间???、???系???统???内???存???、???网???络???带???宽???或???者???这???些???资???源???的???组???合???。???您???可???以???监???控???您???配???置???的??? cgroup,拒???绝??? cgroup 访???问???某???些???资???源???,甚???至???在???运???行???的???系???统???中???动???态???配???置???您???的??? cgroup。所以,可以将 controll groups 理解为 controller (system resource) (for) (process)groups,也就是是说它以一组进程为目标进行系统资源分配和控制。

  它主要提供了如下功能: 

  • Resource limitation: 限制资源使用,比如内存使用上限以及文件系统的缓存限制。
  • Prioritization: 优先级控制,比如:CPU利用和磁盘IO吞吐。
  • Accounting: 一些审计或一些统计,主要目的是为了计费。
  • Control: 挂起进程,恢复执行进程。

使???用??? cgroup,系???统???管???理???员???可???更???具???体???地???控???制???对???系???统???资???源???的???分???配???、???优???先???顺???序???、???拒???绝???、???管???理???和???监???控???。???可???更???好???地???根???据???任???务???和???用???户???分???配???硬???件???资???源???,提???高???总???体???效???率???。

在实践中,系统管理员一般会利用CGroup做下面这些事(有点像为某个虚拟机分配资源似的):

  • 隔离一个进程集合(比如:nginx的所有进程),并限制他们所消费的资源,比如绑定CPU的核。
  • 为这组进程分配其足够使用的内存
  • 为这组进程分配相应的网络带宽和磁盘存储限制
  • 限制访问某些设备(通过设置设备的白名单)

Linux 系统中,一切皆文件。Linux 也将 cgroups 实现成了文件系统,方便用户使用。在我的 Ubuntu 14.04 测试环境中:

技术分享
[email protected]:/home/sammy# mount -t cgroup
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu type cgroup (rw,relatime,cpu)
systemd on /sys/fs/cgroup/systemd type cgroup (rw,noexec,nosuid,nodev,none,name=systemd)
[email protected]:/home/sammy# lssubsys -m cpuset /sys/fs/cgroup/cpuset cpu /sys/fs/cgroup/cpu cpuacct /sys/fs/cgroup/cpuacct memory /sys/fs/cgroup/memory devices /sys/fs/cgroup/devices freezer /sys/fs/cgroup/freezer blkio /sys/fs/cgroup/blkio perf_event /sys/fs/cgroup/perf_event hugetlb /sys/fs/cgroup/hugetlb
[email protected]:/home/sammy# ls /sys/fs/cgroup/ -l total 0 drwxr-xr-x 3 root root 0 Sep 18 21:46 blkio drwxr-xr-x 3 root root 0 Sep 18 21:46 cpu drwxr-xr-x 3 root root 0 Sep 18 21:46 cpuacct drwxr-xr-x 3 root root 0 Sep 18 21:46 cpuset drwxr-xr-x 3 root root 0 Sep 18 21:46 devices drwxr-xr-x 3 root root 0 Sep 18 21:46 freezer drwxr-xr-x 3 root root 0 Sep 18 21:46 hugetlb drwxr-xr-x 3 root root 0 Sep 18 21:46 memory drwxr-xr-x 3 root root 0 Sep 18 21:46 perf_event drwxr-xr-x 3 root root 0 Sep 18 21:46 systemd
技术分享

我们看到 /sys/fs/cgroup 目录中有若干个子目录,我们可以认为这些都是受 cgroups 控制的资源以及这些资源的信息。

  • blkio — 这???个???子???系???统???为???块???设???备???设???定???输???入???/输???出???限???制???,比???如???物???理???设???备???(磁???盘???,固???态???硬???盘???,USB 等???等???)。
  • cpu — 这???个???子???系???统???使???用???调???度???程???序???提???供???对??? CPU 的??? cgroup 任???务???访???问???。???
  • cpuacct — 这???个???子???系???统???自???动???生???成??? cgroup 中???任???务???所???使???用???的??? CPU 报???告???。???
  • cpuset — 这???个???子???系???统???为??? cgroup 中???的???任???务???分???配???独???立??? CPU(在???多???核???系???统???)和???内???存???节???点???。???
  • devices — 这???个???子???系???统???可???允???许???或???者???拒???绝??? cgroup 中???的???任???务???访???问???设???备???。???
  • freezer — 这???个???子???系???统???挂???起???或???者???恢???复??? cgroup 中???的???任???务???。???
  • memory — 这???个???子???系???统???设???定??? cgroup 中???任???务???使???用???的???内???存???限???制???,并???自???动???生???成?????内???存???资???源使用???报???告???。???
  • net_cls — 这???个???子???系???统???使???用???等???级???识???别???符???(classid)标???记???网???络???数???据???包???,可???允???许??? Linux 流???量???控???制???程???序???(tc)识???别???从???具???体??? cgroup 中???生???成???的???数???据???包???。???
  • net_prio — 这个子系统用来设计网络流量的优先级
  • hugetlb — 这个子系统主要针对于HugeTLB系统进行限制,这是一个大页文件系统。

默认的话,在 Ubuntu 系统中,你可能看不到 net_cls 和 net_prio 目录,它们需要你手工做 mount:

技术分享
[email protected]:/sys/fs/cgroup# modprobe cls_cgroup
[email protected]:/sys/fs/cgroup# mkdir net_cls
[email protected]:/sys/fs/cgroup# mount -t cgroup -o net_cls none net_cls

[email protected]:/sys/fs/cgroup# modprobe netprio_cgroup
[email protected]:/sys/fs/cgroup# mkdir net_prio
[email protected]:/sys/fs/cgroup# mount -t cgroup -o net_prio none net_prio

[email protected]:/sys/fs/cgroup# ls net_prio/cgroup.clone_children  cgroup.procs          net_prio.ifpriomap  notify_on_release  tasks
cgroup.event_control   cgroup.sane_behavior  net_prio.prioidx    release_agent
[email protected]:/sys/fs/cgroup# ls net_cls/
cgroup.clone_children  cgroup.event_control  cgroup.procs  cgroup.sane_behavior  net_cls.classid  notify_on_release  release_agent  tasks
技术分享

1.2 实验

1.2.1 通过 cgroups 限制进程的 CPU

写一段最简单的 C 程序:

int main(void)
{
    int i = 0;
    for(;;) i++;
    return 0;
}

编译,运行,发现它占用的 CPU 几乎到了 100%:

top - 22:43:02 up  1:14,  3 users,  load average: 0.24, 0.06, 0.06  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 2304 root      20   0    4188    356    276 R 99.6  0.0   0:11.77 hello

接下来我们做如下操作:

技术分享
[email protected]:/home/sammy/c# mkdir /sys/fs/cgroup/cpu/hello
[email protected]:/home/sammy/c# cd /sys/fs/cgroup/cpu/hello
[email protected]:/sys/fs/cgroup/cpu/hello# ls
cgroup.clone_children  cgroup.procs       cpu.cfs_quota_us  cpu.stat           tasks
cgroup.event_control   cpu.cfs_period_us  cpu.shares        notify_on_release
[email protected]:/sys/fs/cgroup/cpu/hello# cat cpu.cfs_quota_us
-1
[email protected]:/sys/fs/cgroup/cpu/hello# echo 20000 > cpu.cfs_quota_us
[email protected]:/sys/fs/cgroup/cpu/hello# cat cpu.cfs_quota_us
20000
[email protected]:/sys/fs/cgroup/cpu/hello# echo 2428 > tasks
技术分享

然后再来看看这个进程的 CPU 占用情况:

 PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 2428 root      20   0    4188    356    276 R 19.9  0.0   0:46.03 hello

它占用的 CPU 几乎就是 20%,也就是我们预设的阈值。这说明我们通过上面的步骤,成功地将这个进程运行所占用的 CPU 资源限制在某个阈值之内了。

如果此时再启动另一个 hello 进程并将其 id 加入 tasks 文件,则两个进程会共享设定的 CPU 限制:

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 2428 root      20   0    4188    356    276 R 10.0  0.0 285:39.54 hello
12526 root      20   0    4188    356    276 R 10.0  0.0   0:25.09 hello

1.2.2 通过 cgroups 限制进程的 Memory

同样地,我们针对它占用的内存做如下操作:

技术分享
[email protected]:/sys/fs/cgroup/memory# mkdir hello
[email protected]:/sys/fs/cgroup/memory# cd hello/
[email protected]:/sys/fs/cgroup/memory/hello# cat memory.limit_in_bytes
18446744073709551615
[email protected]:/sys/fs/cgroup/memory/hello# echo 64k > memory.limit_in_bytes
[email protected]:/sys/fs/cgroup/memory/hello# echo 2428 > tasks
[email protected]:/sys/fs/cgroup/memory/hello#
技术分享

上面的步骤会把进程 2428 说占用的内存阈值设置为 64K。超过的话,它会被杀掉。

1.2.3 限制进程的 I/O

运行命令:

sudo dd if=/dev/sda1 of=/dev/null

通过 iotop 命令看 IO (此时磁盘在快速转动),此时其写速度为 242M/s:

 TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
 2555 be/4 root      242.60 M/s    0.00 B/s  0.00 % 61.66 % dd if=/dev/sda1 of=/dev/null

接着做下面的操作:

技术分享
[email protected]:/home/sammy# mkdir /sys/fs/cgroup/blkio/io
[email protected]:/home/sammy# cd /sys/fs/cgroup/blkio/io
[email protected]:/sys/fs/cgroup/blkio/io# ls -l /dev/sda1
brw-rw---- 1 root disk 8, 1 Sep 18 21:46 /dev/sda1
[email protected]:/sys/fs/cgroup/blkio/io# echo ‘8:0 1048576‘  > /sys/fs/cgroup/blkio/io/blkio.throttle.read_bps_device
[email protected]:/sys/fs/cgroup/blkio/io# echo 2725 > /sys/fs/cgroup/blkio/io/tasks
技术分享

结果,这个进程的IO 速度就被限制在 1Mb/s 之内了:

 TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
 2555 be/4 root      990.44 K/s    0.00 B/s  0.00 % 96.29 % dd if=/dev/sda1 of=/dev/null

1.3 术语

cgroups 的术语包括:

  • 任务(Tasks):就是系统的一个进程。
  • 控制组(Control Group):一组按照某种标准划分的进程,比如官方文档中的Professor和Student,或是WWW和System之类的,其表示了某进程组。Cgroups中的资源控制都是以控制组为单位实现。一个进程可以加入到某个控制组。而资源的限制是定义在这个组上,就像上面示例中我用的 hello 一样。简单点说,cgroup的呈现就是一个目录带一系列的可配置文件。
  • 层级(Hierarchy):控制组可以组织成hierarchical的形式,既一颗控制组的树(目录结构)。控制组树上的子节点继承父结点的属性。简单点说,hierarchy就是在一个或多个子系统上的cgroups目录树。
  • 子系统(Subsystem):一个子系统就是一个资源控制器,比如CPU子系统就是控制CPU时间分配的一个控制器。子系统必须附加到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制族群都受到这个子系统的控制。Cgroup的子系统可以有很多,也在不断增加中。

2. Docker 对 cgroups 的使用

2.1 默认情况

默认情况下,Docker 启动一个容器后,会在 /sys/fs/cgroup 目录下的各个资源目录下生成以容器 ID 为名字的目录(group),比如:

/sys/fs/cgroup/cpu/docker/03dd196f415276375f754d51ce29b418b170bd92d88c5e420d6901c32f93dc14

此时 cpu.cfs_quota_us 的内容为 -1,表示默认情况下并没有限制容器的 CPU 使用。在容器被 stopped 后,该目录被删除。

运行命令 docker run -d --name web41 --cpu-quota 25000 --cpu-period 100 --cpu-shares 30 training/webapp python app.py 启动一个新的容器,结果:

技术分享
[email protected]:/sys/fs/cgroup/cpu/docker/06bd180cd340f8288c18e8f0e01ade66d066058dd053ef46161eb682ab69ec24# cat cpu.cfs_quota_us
25000
[email protected]:/sys/fs/cgroup/cpu/docker/06bd180cd340f8288c18e8f0e01ade66d066058dd053ef46161eb682ab69ec24# cat tasks
3704
[email protected]:/sys/fs/cgroup/cpu/docker/06bd180cd340f8288c18e8f0e01ade66d066058dd053ef46161eb682ab69ec24# cat cpu.cfs_period_us
2000
技术分享

Docker 会将容器中的进程的 ID 加入到各个资源对应的 tasks 文件中。表示 Docker 也是以上面的机制来使用 cgroups 对容器的 CPU 使用进行限制。

相似地,可以通过 docker run 中 mem 相关的参数对容器的内存使用进行限制:

技术分享
      --cpuset-mems string          MEMs in which to allow execution (0-3, 0,1)
      --kernel-memory string        Kernel memory limit
  -m, --memory string               Memory limit
      --memory-reservation string   Memory soft limit
      --memory-swap string          Swap limit equal to memory plus swap: ‘-1‘ to enable unlimited swap
      --memory-swappiness int       Tune container memory swappiness (0 to 100) (default -1)
技术分享

比如  docker run -d --name web42 --blkio-weight 100 --memory 10M --cpu-quota 25000 --cpu-period 2000 --cpu-shares 30 training/webapp python app.py:

技术分享
[email protected]:/sys/fs/cgroup/memory/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410# cat memory.limit_in_bytes
10485760

  [email protected]:/sys/fs/cgroup/blkio/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410# cat blkio.weight
  100

技术分享

目前 docker 已经几乎支持了所有的 cgroups 资源,可以限制容器对包括 network,device,cpu 和 memory 在内的资源的使用,比如:

技术分享
[email protected]:/sys/fs/cgroup# find -iname ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./net_prio/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./net_cls/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./systemd/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./hugetlb/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./perf_event/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./blkio/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./freezer/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./devices/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./memory/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./cpuacct/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./cpu/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
./cpuset/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
技术分享

 2.2 net_cls 

   net_cls 和 tc 一起使用可用于限制进程发出的网络包所使用的网络带宽。当使用 cgroups network controll net_cls 后,指定进程发出的所有网络包都会被加一个 tag,然后就可以使用其他工具比如 iptables 或者 traffic controller (TC)来根据网络包上的 tag 进行流量控制。关于 TC 的文档,网上很多,这里不再赘述,只是用一个简单的例子来加以说明。

  关于 classid,它的格式是 0xAAAABBBB,其中,AAAA 是十六进制的主ID(major number),BBBB 是十六进制的次ID(minor number)。因此,0X10001 表示 10:1,而 0x00010001 表示 1:!。

  (1)首先在host 的网卡 eth0 上做如下设置:

技术分享

tc qdisc del dev eth0 root                                        #删除已有的规则
tc qdisc add dev eth0 root handle 10: htb default 12              
tc class add dev eth0 parent 10: classid 10:1 htb rate 1500kbit ceil 1500kbit burst 10k         #限速
tc filter add dev eth0 protocol ip parent 10:0 prio 1 u32 match ip protocol 1 0xff flowid 10:1  #只处理 ping 参数的网络包

技术分享

其结果是:

  • 在网卡 eth0 上创建了一个 HTB root 队列,hangle 10: 表示队列句柄也就是major number 为 10
  • 创建一个分类 10:1,限制它的出发网络带宽为 80 kbit (千比特每秒)
  • 创建一个分类器,将 eth0 上 IP IMCP 协议 的 major ID 为 10 的 prio 为 1 的网络流量都分类到 10:1 类别

(2)启动容器

容器启动后,其 init 进程在host 上的 PID 就被加入到 tasks 文件中了:

[email protected]:/sys/fs/cgroup/net_cls/docker/ff8d9715b7e11a5a69446ff1e3fde3770078e32a7d8f7c1cb35d51c75768fe33# ps -ef | grep 10047
231072   10047 10013  1 07:08 ?        00:00:00 python app.py

设置 net_cls classid:

echo 0x100001 > net_cls.classid

再在容器启动一个 ping 进程,其 ID 也被加入到 tasks 文件中了。

(3)查看tc 情况: tc -s -d class show dev eth0

技术分享

Every 2.0s: tc -s class ls dev eth0 Wed Sep 21 04:07:56 2016

class htb 10:1 root prio 0 rate 1500Kbit ceil 1500Kbit burst 10Kb cburst 1599b
Sent 17836 bytes 182 pkt (dropped 0, overlimits 0 requeues 0)
rate 0bit 0pps backlog 0b 0p requeues 0
lended: 182 borrowed: 0 giants: 0
tokens: 845161 ctokens: 125161

技术分享

我们可以看到 tc 已经在处理 ping 进程产生的数据包了。再来看一下 net_cls 和 ts 合作的限速效果:

10488 bytes from 192.168.1.1: icmp_seq=35 ttl=63 time=12.7 ms
10488 bytes from 192.168.1.1: icmp_seq=36 ttl=63 time=15.2 ms
10488 bytes from 192.168.1.1: icmp_seq=37 ttl=63 time=4805 ms
10488 bytes from 192.168.1.1: icmp_seq=38 ttl=63 time=9543 ms

其中:

  • 后两条说使用的 tc class 规则是 tc class add dev eth0 parent 10: classid 10:1 htb rate 1500kbit ceil 15kbit burst 10k
  • 前两条所使用的 tc class 规则是 tc class add dev eth0 parent 10: classid 10:1 htb rate 1500kbit ceil 10Mbit burst 10k 

3. Docker run 命令中 cgroups 相关命令 

技术分享
block IO:
      --blkio-weight value          Block IO (relative weight), between 10 and 1000
      --blkio-weight-device value   Block IO weight (relative device weight) (default [])
      --cgroup-parent string        Optional parent cgroup for the container
CPU:
      --cpu-percent int             CPU percent (Windows only)
      --cpu-period int              Limit CPU CFS (Completely Fair Scheduler) period
      --cpu-quota int               Limit CPU CFS (Completely Fair Scheduler) quota
  -c, --cpu-shares int              CPU shares (relative weight)
      --cpuset-cpus string          CPUs in which to allow execution (0-3, 0,1)
      --cpuset-mems string          MEMs in which to allow execution (0-3, 0,1)
Device:    
      --device value                Add a host device to the container (default [])
      --device-read-bps value       Limit read rate (bytes per second) from a device (default [])
      --device-read-iops value      Limit read rate (IO per second) from a device (default [])
      --device-write-bps value      Limit write rate (bytes per second) to a device (default [])
      --device-write-iops value     Limit write rate (IO per second) to a device (default [])
Memory:      
      --kernel-memory string        Kernel memory limit
  -m, --memory string               Memory limit
      --memory-reservation string   Memory soft limit
      --memory-swap string          Swap limit equal to memory plus swap: ‘-1‘ to enable unlimited swap
      --memory-swappiness int       Tune container memory swappiness (0 to 100) (default -1)
技术分享

  

参考链接:

 
 

以上是关于Docker Cgroups——Docker 资源限制背后的技术原理的主要内容,如果未能解决你的问题,请参考以下文章

docker——docker资源控制管理

Docker之Linux Cgroups

yarn 容器资源隔离和docker容器资源隔离实现原理

Docker背后的内核知识

Docker背后的内核知识

docker背后的内核知识