Graph + AI 中国峰会火热报名中,点击探索图分析更多可能! 了解详情
写点什么

Docker 资源管理探秘:Docker 背后的内核 Cgroups 机制

2016 年 12 月 04 日

随着 Docker 技术被越来越多的个人、企业所接受,其用途也越来越广泛。Docker 资源管理包含对 CPU、内存、IO 等资源的限制,但大部分 Docker 使用者在使用资源管理接口时往往只知其然而不知其所以然。
本文将介绍 Docker 资源管理背后的 Cgroups 机制,并且列举每一个资源管理接口对应的 Cgroups 接口,让 Docker 使用者对资源管理知其然并且知其所以然。

1.Docker 资源管理接口概览

格式

描述

-m, --memory=" < 数字 >[< 单位 >]" 内存使用限制。 数字需要使用整数,对应的单位是 b, k, m, g 中的一个。最小取值是 4M。–memory-swap="< 数字 >[< 单位 >]" 总内存使用限制 (物理内存 + 交换分区,数字需要使用整数,对应的单位是 b, k, m, g 中的一个。–memory-reservation="< 数字 >[< 单位 >]" 内存软限制。 数字需要使用正整数,对应的单位是 b, k, m, g 中的一个。–kernel-memory="< 数字 >[< 单位 >]" 内核内存限制。 数字需要使用正整数,对应的单位是 b, k, m, g 中的一个。最小取值是 4M。–oom-kill-disable=false 内存耗尽时是否杀掉容器–memory-swappiness="" 调节容器内存使用交换分区的选项,取值为 0 和 100 之间的整数 (含 0 和 100)。-c, --cpu-shares=0 CPU 份额 (相对权重)–cpu-period=0 完全公平算法中的 period 值–cpu-quota=0 完全公平算法中的 quota 值–cpuset-cpus="< 数字 >" 限制容器使用的 cpu 核 (0-3, 0,1)–cpuset-mems="" 限制容器使用的内存节点,该限制仅仅在 NUMA 系统中生效。–blkio-weight=0 块设备 IO 相对权重,取值在 10 值 1000 之间的整数(包含 10 和 1000)–blkio-weight-device=“设备名称: 权重值” 指定的块设备的 IO 相对权重–device-read-bps="< 设备路径 >:< 数字 >[< 单位 >]" 限制对某个设备的读取速率 ,数字需要使用正整数,单位是 kb, mb, or gb 中的一个。–device-write-bps="< 设备路径 >:< 数字 >[< 单位 >]" 限制对某个设备的写速率 ,数字需要使用正整数,单位是 kb, mb, or gb 中的一个。–device-read-iops="< 设备路径 >:< 数字 >" 限制对某个设备每秒 IO 的读取速率,数字需要使用正整数。–device-write-iops="< 设备路径 >:< 数字 >" 限制对某个设备每秒 IO 的写速率,数字需要使用正整数。## 2. Docker 资源管理原理——Cgroups 子系统介绍

Cgroups 是 control groups 的缩写,最初由 google 的工程师提出,后来被整合进 Linux 内核。Cgroups 是 Linux 内核提供的一种可以限制、记录、隔离进程组(process groups)所使用的物理资源(如:CPU、内存、IO 等)的机制。Cgroups 由 7 个子系统组成:分别是 cpuset、cpu、cpuacct、blkio、devices、freezer、memory。不同类型资源的分配和管理是由各个 cgroup 子系统负责完成的。

下面介绍与 docker 资源管理接口相关的 4 个子系统。

2.1 memory – 用来限制 cgroup 中的任务所能使用的内存上限。

子系统常用 cgroups 接口

描述

对应的 docker 接口

cgroup/memory/memory.
limit_in_bytes 设定内存上限,单位是字节,也可以使用 k/K、m/M 或者 g/G 表示要设置数值的单位。 -m, --memory="“cgroup/memory/memory.
memsw.limit_in_bytes 设定内存加上交换分区的使用总量。通过设置这个值,可以防止进程把交换分区用光。 --memory-swap=”“cgroup/memory/memory.
soft_limit_in_bytes 设定内存限制,但这个限制并不会阻止进程使用超过限额的内存,只是在系统内存不足时,会优先回收超过限额的进程占用的内存,使之向限定值靠拢。 --memory-reservation=”“cgroup/memory/memory.
kmem.limit_in_bytes 设定内核内存上限。 --kernel-memory=”“cgroup/memory/memory.
oom_control 如果设置为 0,那么在内存使用量超过上限时,系统不会杀死进程,而是阻塞进程直到有内存被释放可供使用时,另一方面,系统会向用户态发送事件通知,用户态的监控程序可以根据该事件来做相应的处理,例如提高内存上限等。 --oom-kill-disable=”“cgroup/memory/memory.
swappiness 控制内核使用交换分区的倾向。取值范围是 0 至 100 之间的整数(包含 0 和 100)。值越小,越倾向使用物理内存。 --memory-swappiness=”"### 2.2 cpu – 使用调度程序提供对 CPU 的 cgroup 任务访问。

子系统常用 cgroups 接口

描述

对应的 docker 接口

cgroup/cpu/cpu.
shares 负责 CPU 比重分配的接口。假设我们在 cgroupfs 的根目录下创建了两个 cgroup(C1 和 C2),并且将 cpu.shares 分别配置为 512 和 1024,那么当 C1 和 C2 争用 CPU 时,C2 将会比 C1 得到多一倍的 CPU 占用率。要注意的是,只有当它们争用 CPU 时 CPU share 才会起作用,如果 C2 是空闲的,那么 C1 可以得到全部的 CPU 资源。 -c, --cpu-shares="“cgroup/cpu/cpu.
cfs_period_us 负责 CPU 带宽限制,需要与 cpu.cfs_quota_us 搭配使用。我们可以将 period 设置为 1 秒,将 quota 设置为 0.5 秒,那么 cgroup 中的进程在 1 秒内最多只能运行 0.5 秒,然后就会被强制睡眠,直到下一个 1 秒才能继续运行。 --cpu-period=”“cgroup/cpu/cpu.
cfs_quota_us 负责 CPU 带宽限制,需要与 cpu.cfs_period_us 搭配使用。 --cpu-quota=”"### 2.3 cpuset – 为 cgroup 中的任务分配独立 CPU(在多核系统)和内存节点

子系统常用 cgroups 接口 描述 对应的 docker 接口 cgroup/cpuset/cpuset.cpus 允许进程使用的 CPU 列表(例如:0-4,9)。 --cpuset-cpus="“cgroup/cpuset/cpuset.mems 允许进程使用的内存节点列表(例如:0-1)。 --cpuset-mems=”"### 2.4 blkio – 为块设备设定输入 / 输出限制,比如物理设备(磁盘、固态硬盘、USB 等)

子系统常用 cgroups 接口

描述

对应的 docker 接口

cgroup/blkio/blkio.
weight 设置权重值,取值范围是 10 至 1000 之间的整数(包含 10 和 1000)。这跟 cpu.shares 类似,是比重分配,而不是绝对带宽的限制,因此只有当不同的 cgroup 在争用同一个块设备的带宽时,才会起作用。 --blkio-weight="“cgroup/blkio/blkio.
weight_device 对具体的设备设置权重值,这个值会覆盖上述的 blkio.weight。 --blkio-weight-device=”“cgroup/blkio/blkio.
throttle.read_bps_device 对具体的设备,设置每秒读块设备的带宽上限。 --device-read-bps=”“cgroup/blkio/blkio.
throttle.write_bps_device 设置每秒写块设备的带宽上限。同样需要指定设备。 --device-write-bps=”“cgroup/blkio/blkio.
throttle.read_iops_device 设置每秒读块设备的 IO 次数的上限。同样需要指定设备。 --device-read-iops=”“cgroup/blkio/blkio.
throttle.write_iops_device 设置每秒写块设备的 IO 次数的上限。同样需要指定设备。 --device-write-iops=”"## 3.Docker 资源管理接口详解及应用示例

以下内容针对各资源管理接口做了详尽的说明。为了加深读者理解,部分接口附有测试用例。用例中的 Docker 版本为 1.11.0。如果在你的镜像中 stress 命令不可用,你可以通过 sudo apt-get install stress 来安装 stress 工具。

3.1 memory 子系统

3.1.1 -m, --memory=""

可以限制容器使用的内存量,对应的 cgroup 文件是 cgroup/memory/memory.limit_in_bytes。
取值范围: 大于等于 4M
单位:b,k,m,g

在默认情况下,容器可以占用无限量的内存,直至主机内存资源耗尽。
运行如下命令来确认容器内存的资源管理对应的 cgroup 文件。

复制代码
$ docker run -it --memory 100M ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.limit_in_bytes"
104857600

可以看到,当内存限定为 100M 时,对应的 cgroup 文件数值为 104857600,该数值的单位为字节,即 104857600 字节等于 100M。

本机内存环境为:

复制代码
$ free
total used free shared buff/cache available
Mem: 4050284 254668 3007564 180484 788052 3560532
Swap: 0 0 0

值得注意的是本机目前没有配置交换分区 (swap)。

我们使用 stress 工具来证明内存限定已经生效。stress 是一个压力工具,如下命令将要在容器内创建一个进程,在该进程中不断的执行占用内存 (malloc) 和释放内存 (free) 的操作。在理论上如果占用的内存少于限定值,容器会工作正常。注意,如果试图使用边界值,即试图在容器中使用 stress 工具占用 100M 内存,这个操作通常会失败,因为容器中还有其他进程在运行。

复制代码
$ docker run -ti -m 100M ubuntu:14.04 stress --vm 1 --vm-bytes 50M
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd

当在限定内存为 100M 的容器中,试图占用 50M 的内存时,容器工作正常。
如下所示,当试图占用超过 100M 内存时,必然导致容器异常。

复制代码
$ docker run -ti -m 100M ubuntu:14.04 stress --vm 1 --vm-bytes 101M
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: FAIL: [1] (416) <-- worker 6 got signal 9
stress: WARN: [1] (418) now reaping child worker processes
stress: FAIL: [1] (422) kill error: No such process
stress: FAIL: [1] (452) failed run completed in 0s

注意这种情况是在系统无交换分区 (swap) 的情况下出现的,如果我们添加了交换分区,情况又会怎样?首先通过如下命令来添加交换分区 (swap)。

复制代码
$ dd if=/dev/zero of=/tmp/mem.swap bs=1M count=8192
8192+0 records in
8192+0 records out
8589934592 bytes (8.6 GB) copied, 35.2693 s, 244 MB/s
$ mkswap /tmp/mem.swap
Setting up swapspace version 1, size = 8388604 KiB
no label, UUID=55ea48e9-553d-4013-a2ae-df194f7941ed
$ sudo swapon /tmp/mem.swap
swapon: /tmp/mem.swap: insecure permissions 0664, 0600 suggested.
swapon: /tmp/mem.swap: insecure file owner 1100, 0 (root) suggested.
$ free -m
total used free shared buff/cache available
Mem: 3955 262 28 176 3665 3463
Swap: 8191 0 8191

之后再次尝试占用大于限定的内存。

复制代码
$ docker run -ti -m 100M ubuntu:14.04 stress --vm 1 --vm-bytes 101M
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd

在加入交换分区后容器工作正常,这意味着有部分存储在内存中的信息被转移到了交换分区中了。
注意,在实际容器使用场景中,如果不对容器使用内存量加以限制的话,可能导致一个容器会耗尽整个主机内存,从而导致系统不稳定。所以在使用容器时务必对容器内存加以限制。

3.1.2 --memory-swap=""

可以限制容器使用交换分区和内存的总和,对应的 cgroup 文件是 cgroup/memory/memory.memsw.limit_in_bytes。
取值范围: 大于内存限定值
单位:b,k,m,g

运行如下命令来确认容器交换分区的资源管理对应的 cgroup 文件。

复制代码
$ docker run -ti -m 300M --memory-swap 1G ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"
1073741824

可以看到,当 memory-swap 限定为 1G 时,对应的 cgroup 文件数值为 1073741824,该数值的单位为字节,即 1073741824B 等于 1G。

条件

结果

memory= 无穷大, memory-swap= 无穷大 (默认条件下) 系统不限定容器对内存和交换分区的使用量,容器能够使用主机所能提供的所有内存。memory=L< 无穷大, memory-swap= 无穷大 (设定 memory 限定值同时将 memory-swap 设置为 -1) 容器的内存使用量不能超过 L,但是交换分区的使用量不受限制 (前提是主机支持交换分区)。memory=L< 无穷大, memory-swap=2*L (设定 memory 限定值而不设置 memory-swap 值) 容器的内存使用量不能超过 L,而内存使用量和交换分区的使用量不能超过两倍的 L。memory=L< 无穷大, memory-swap=S< 无穷大, L<=S (设定了 memory 和 memory-swap 的限定值) 容器的内存使用量不能超过 L,而内存使用量和交换分区的使用量不能超过 S。例子:
以下命令没有对内存和交换分区进行限制,这意味着容器可以使用无限多的内存和交换分区。

复制代码
$ docker run -it ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.limit_in_bytes && cat /sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"
9223372036854771712
9223372036854771712

以下命令只限定了内存使用量 300M,而没有限制交换分区使用量 (-1 意味着不做限制)。

复制代码
$ docker run -it -m 300M --memory-swap -1 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.limit_in_bytes && cat /sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"
314572800
9223372036854771712

以下命令仅仅限定了内存使用量,这意味着容器能够使用 300M 的内存和 300M 的交换分区。在默认情况下,总的内存限定值 (内存 + 交换分区) 被设置为了内存限定值的两倍。

复制代码
$ docker run -it -m 300M ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.limit_in_bytes && cat /sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"
314572800
629145600

以下命令限定了内存和交换分区的使用量,容器可以使用 300M 的内存和 700M 的交换分区。

复制代码
$ docker run -it -m 300M --memory-swap 1G ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.limit_in_bytes && cat /sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"
314572800
1073741824

当 memory-swap 限定值低于 memory 限定值时,系统提示"Minimum memoryswap limit should be larger than memory limit"错误。

复制代码
$ docker run -it -m 300M --memory-swap 200M ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.limit_in_bytes && cat /sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"
docker: Error response from daemon: Minimum memoryswap limit should be larger than memory limit, see usage..
See 'docker run --help'.

如下所示,当尝试占用的内存数量超过 memory-swap 值时,容器出现异常。

复制代码
$ docker run -ti -m 100M --memory-swap 200M ubuntu:14.04 stress --vm 1 --vm-bytes 201M
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: FAIL: [1] (416) <-- worker 7 got signal 9
stress: WARN: [1] (418) now reaping child worker processes
stress: FAIL: [1] (422) kill error: No such process
stress: FAIL: [1] (452) failed run completed in 0s

如下所示,当占用内存值大于 memory 限定值但小于 memory-swap 时,容器运行正常。

复制代码
$ docker run -ti -m 100M --memory-swap 200M ubuntu:memory stress --vm 1 --vm-bytes 180M
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd

3.1.3 --memory-reservation=""

取值范围: 大于等于 0 的整数
单位:b,k,m,g
对应的 cgroup 文件是 cgroup/memory/memory.soft_limit_in_bytes。

复制代码
$ docker run -ti --memory-reservation 50M ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.soft_limit_in_bytes"
52428800

通常情况下,容器能够使用的内存量仅仅由 -m/–memory 选项限定。如果设置了–memory-reservation 选项,当内存使用量超过–memory-reservation 选项所设定的值时,系统会强制容器执行回收内存的操作,使得容器内存消耗不会长时间超过–memory-reservation 的限定值。

这个限制并不会阻止进程使用超过限额的内存,只是在系统内存不足时,会回收部分内存,使内存使用量向限定值靠拢。
在以下命令中,容器对内存的使用量不会超过 500M,这是硬性限制。当内存使用量大于 200M 而小于 500M 时,系统会尝试回收部分内存,使得内存使用量低于 200M。

复制代码
$ docker run -it -m 500M --memory-reservation 200M ubuntu:14.04 bash

在如下命令中,容器使用的内存量不受限制,但容器消耗的内存量不会长时间超过 1G,因为当容器内存使用量超过 1G 时,系统会尝试回收内存使内存使用量低于 1G。

复制代码
$ docker run -it --memory-reservation 1G ubuntu:14.04 bash

3.1.4 --kernel-memory=""

该接口限制了容器对内核内存的使用,对应的 cgroup 文件是 cgroup/memory/memory.kmem.limit_in_bytes。

复制代码
$ docker run -ti --kernel-memory 50M ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.kmem.limit_in_bytes"
52428800

如下命令可以限定容器最多可以使用 500M 的内存。在 500M 内存中,内核内存最多可以占用 50M。

复制代码
$ docker run -it -m 500M --kernel-memory 50M ubuntu:14.04 bash

如下命令可以限定容器最多可以使用 50M 的内核内存,而用户空间的内存使用量不受限制。

复制代码
$ docker run -it --kernel-memory 50M ubuntu:14.04 bash

3.1.5 --oom-kill-disable=false

当 out-of-memory (OOM) 发生时,系统会默认杀掉容器进程,如果你不想让容器进程被杀掉,可以使用该接口。接口对应的 cgroup 文件是 cgroup/memory/memory.oom_control。

当容器试图使用超过限定大小的内存值时,就会触发 OOM。此时会有两种情况,第一种情况是当接口–oom-kill-disable=false 的时候,容器会被杀掉;第二种情况是当接口–oom-kill-disable=true 的时候,容器会被挂起。

以下命令设置了容器的的内存使用限制为 20M,将–oom-kill-disable 接口的值设置为 true。查看该接口对应的 cgroup 文件,oom_kill_disable 的值为 1。

复制代码
$ docker run -m 20m --oom-kill-disable=true ubuntu:14.04 bash -c 'cat /sys/fs/cgroup/memory/memory.oom_control'
oom_kill_disable 1
under_oom 0

oom_kill_disable:取值为 0 或 1,当值为 1 的时候表示当容器试图使用超出内存限制时(即 20M),容器会挂起。
under_oom:取值为 0 或 1,当值为 1 的时候,OOM 已经出现在容器中。

通过 x=a; while true; do x=xx; done 命令来耗尽内存并强制触发 OOM,log 如下所示。

复制代码
$ docker run -m 20m --oom-kill-disable=false ubuntu:14.04 bash -c 'x=a; while true; do x=$x$x$x$x; done'
$ echo $?
137

通过上面的 log 可以看出, 当容器的内存耗尽的时候,容器退出,退出码为 137。因为容器试图使用超出限定的内存量,系统会触发 OOM,容器会被杀掉,此时 under_oom 的值为 1。我们可以通过系统中 cgroup 文件 (/sys/fs/cgroup/memory/docker/${container_id}/memory.oom_control) 查看 under_oom 的值(oom_kill_disable 1,under_oom 1)。

当–oom-kill-disable=true 的时候,容器不会被杀掉,而是被系统挂起。

复制代码
$ docker run -m 20m --oom-kill-disable=true ubuntu:14.04 bash -c 'x=a; while true; do x=$x$x$x$x; done'

3.1.6 --memory-swappiness=""

该接口可以设定容器使用交换分区的趋势,取值范围为 0 至 100 的整数(包含 0 和 100)。0 表示容器不使用交换分区,100 表示容器尽可能多的使用交换分区。对应的 cgroup 文件是 cgroup/memory/memory.swappiness。

复制代码
$ docker run --memory-swappiness=100 ubuntu:14.04 bash -c 'cat /sys/fs/cgroup/memory/memory.swappiness'
100

3.2 cpu 子系统

3.2.1 -c, --cpu-shares=0

对应的 cgroup 文件是 cgroup/cpu/cpu.shares。

复制代码
$ docker run --rm --cpu-shares 1600 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/cpu/cpu.shares"
1600

通过–cpu-shares 可以设置容器使用 CPU 的权重,这个权重设置是针对 CPU 密集型的进程的。如果某个容器中的进程是空闲状态,那么其它容器就能够使用本该由空闲容器占用的 CPU 资源。也就是说,只有当两个或多个容器都试图占用整个 CPU 资源时,–cpu-shares 设置才会有效。
我们使用如下命令来创建两个容器,它们的权重分别为 1024 和 512。

复制代码
$ docker run -ti --cpu-shares 1024 ubuntu:14.04 stress -c 1
stress: info: [1] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd
$ docker run -ti --cpu-shares 512 ubuntu:14.04 stress -c 1
stress: info: [1] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd

从如下 top 命令的 log 可以看到,第一个容器产生的进程 PID 为 1418,CPU 占用率为 66.1%,第二个容器产生进程 PID 为 1471,CPU 占用率为 32.9%。两个容器 CPU 占用率约为 2:1 的关系,测试结果与预期相符。

复制代码
top - 18:51:50 up 9 days, 2:07, 0 users, load average: 0.62, 0.15, 0.05
Tasks: 84 total, 3 running, 81 sleeping, 0 stopped, 0 zombie
%Cpu(s): 90.4 us, 2.2 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 7.4 si, 0.0 st
KiB Mem : 2052280 total, 71468 free, 117284 used, 1863528 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 1536284 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1418 root 20 0 7312 100 0 R 66.1 0.0 0:22.92 stress
1471 root 20 0 7312 96 0 R 32.9 0.0 0:04.97 stress

3.2.2 --cpu-period=""

内核默认的 Linux 调度 CFS(完全公平调度器)周期为 100ms, 我们通过–cpu-period 来设置容器对 CPU 的使用周期,同时–cpu-period 接口需要和–cpu-quota 接口一起来使用。–cpu-quota 接口设置了 CPU 的使用值。CFS(完全公平调度器) 是内核默认使用的调度方式,为运行的进程分配 CPU 资源。对于多核 CPU,根据需要调整–cpu-quota 的值。

对应的 cgroup 文件是 cgroup/cpu/cpu.cfs_period_us。以下命令创建了一个容器,同时设置了该容器对 CPU 的使用时间为 50000(单位为微秒),并验证了该接口对应的 cgroup 文件对应的值。

复制代码
$ docker run -ti --cpu-period 50000 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/cpu/cpu.cfs_period_us"
50000

以下命令将–cpu-period 的值设置为 50000,–cpu-quota 的值设置为 25000。该容器在运行时可以获取 50% 的 cpu 资源。

复制代码
$ docker run -ti --cpu-period=50000 --cpu-quota=25000 ubuntu:14.04 stress -c 1
stress: info: [1] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd

从 log 的最后一行中可以看出,该容器的 cpu 使用率约为 50.0%。

复制代码
top - 10:36:55 up 6 min, 0 users, load average: 0.49, 0.21, 0.10
Tasks: 68 total, 2 running, 66 sleeping, 0 stopped, 0 zombie
%Cpu(s): 49.3 us, 0.0 sy, 0.0 ni, 50.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 4050748 total, 3063952 free, 124280 used, 862516 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 3728860 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
770 root 20 0 7312 96 0 R 50.0 0.0 0:38.06 stress

3.2.3 --cpu-quota=0

对应的 cgroup 文件是 cgroup/cpu/cpu.cfs_quota_us。

复制代码
$ docker run --cpu-quota 1600 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us"
1600

–cpu-quota 接口设置了 CPU 的使用值,通常情况下它需要和–cpu-period 接口一起来使用。具体使用方法请参考–cpu-period 选项。

3.3 cpuset 子系统

3.3.1 --cpuset-cpus=""

该接口对应的 cgroup 文件是 cgroup/cpuset/cpuset.cpus。

在多核 CPU 的虚拟机中,启动一个容器,设置容器只使用 CPU 核 1,并查看该接口对应的 cgroup 文件会被修改为 1,log 如下所示。

复制代码
$ docker run -ti --cpuset-cpus 1 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/cpuset/cpuset.cpus"
1

通过以下命令指定容器使用 cpu 核 1,并通过 stress 命令加压。

复制代码
$ docker run -ti --cpuset-cpus 1 ubuntu:14.04 stress -c 1

查看 CPU 资源的 top 命令的 log 如下所示。需要注意的是,输入 top 命令并按回车键后,再按数字键 1,终端才能显示每个 CPU 的状态。

复制代码
top - 11:31:47 up 5 days, 21:00, 0 users, load average: 0.62, 0.82, 0.77
Tasks: 104 total, 3 running, 101 sleeping, 0 stopped, 0 zombie
%Cpu0 : 0.0 us, 0.0 sy, 0.0 ni, 99.6 id, 0.0 wa, 0.0 hi, 0.4 si, 0.0 st
%Cpu1 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 : 0.3 us, 0.3 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu3 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 2051888 total, 1130220 free, 127972 used, 793696 buff/cache
KiB Swap: 33554416 total, 33351848 free, 202568 used. 1739888 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
10266 root 20 0 7312 96 0 R 100.0 0.0 0:11.92 stress

从以上 log 得知,只有 CPU 核 1 的负载为 100%,而其它 CPU 核处于空闲状态,结果与预期结果相符。

3.3.2 --cpuset-mems=""

该接口对应的 cgroup 文件是 cgroup/cpuset/cpuset.mems。

复制代码
$ docker run -ti --cpuset-mems=0 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/cpuset/cpuset.mems"
0

以下命令将限制容器进程使用内存节点 1、3 的内存。

复制代码
$ docker run -it --cpuset-mems="1,3" ubuntu:14.04 bash

以下命令将限制容器进程使用内存节点 0、1、2 的内存。

复制代码
$ docker run -it --cpuset-mems="0-2" ubuntu:14.04 bash

3.4 blkio 子系统

3.4.1 --blkio-weight=0

通过–blkio-weight 接口可以设置容器块设备 IO 的权重,有效值范围为 10 至 1000 的整数 (包含 10 和 1000)。默认情况下,所有容器都会得到相同的权重值 (500)。对应的 cgroup 文件为 cgroup/blkio/blkio.weight。以下命令设置了容器块设备 IO 权重为 10,在 log 中可以看到对应的 cgroup 文件的值为 10。

复制代码
$ docker run -ti --rm --blkio-weight 10 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.weight"
10

通过以下两个命令来创建不同块设备 IO 权重值的容器。

复制代码
$ docker run -it --name c1 --blkio-weight 300 ubuntu:14.04 /bin/bash
$ docker run -it --name c2 --blkio-weight 600 ubuntu:14.04 /bin/bash

如果在两个容器中同时进行块设备操作(例如以下命令)的话,你会发现所花费的时间和容器所拥有的块设备 IO 权重成反比。

复制代码
$ time dd if=/mnt/zerofile of=test.out bs=1M count=1024 oflag=direct

3.4.2 --blkio-weight-device=""

通过–blkio-weight-device="设备名: 权重"接口可以设置容器对特定块设备 IO 的权重,有效值范围为 10 至 1000 的整数 (包含 10 和 1000)。
对应的 cgroup 文件为 cgroup/blkio/blkio.weight_device。

复制代码
$ docker run --blkio-weight-device "/dev/sda:1000" ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.weight_device"
8:0 1000

以上 log 中的"8:0"表示 sda 的设备号,可以通过 stat 命令来获取某个设备的设备号。从以下 log 中可以查看到 /dev/sda 对应的主设备号为 8,次设备号为 0。

复制代码
$ stat -c %t:%T /dev/sda
8:0

如果–blkio-weight-device 接口和–blkio-weight 接口一起使用,那么 Docker 会使用–blkio-weight 值作为默认的权重值,然后使用–blkio-weight-device 值来设定指定设备的权重值,而早先设置的默认权重值将不在这个特定设备中生效。

复制代码
$ docker run --blkio-weight 300 --blkio-weight-device "/dev/sda:500" ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.weight_device"
8:0 500

通过以上 log 可以看出,当–blkio-weight 接口和–blkio-weight-device 接口一起使用的时候,/dev/sda 设备的权重值由–blkio-weight-device 设定的值来决定。

3.4.3 --device-read-bps=""

该接口用来限制指定设备的读取速率,单位可以是 kb、mb 或者 gb。对应的 cgroup 文件是 cgroup/blkio/blkio.throttle.read_bps_device。

复制代码
$ docker run -it --device /dev/sda:/dev/sda --device-read-bps /dev/sda:1mb ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.throttle.read_bps_device"
8:0 1048576

以上 log 中显示 8:0 1000,8:0 表示 /dev/sda, 该接口对应的 cgroup 文件的值为 1048576,是 1MB 所对应的字节数,即 1024 的平方。

创建容器时通过–device-read-bps 接口设置设备读取速度为 1MB/s。从以下 log 中可以看出, 读取速度被限定为 1.0MB/s, 与预期结果相符合。

复制代码
$ docker run -it --device /dev/sda:/dev/sda --device-read-bps /dev/sda:1mB ubuntu:14.04 bash
root@df1de679fae4:/# dd iflag=direct,nonblock if=/dev/sda of=/dev/null bs=5M count=1
1+0 records in
1+0 records out
5242880 bytes (5.2 MB) copied, 5.00464 s, 1.0 MB/s

3.4.4 --device-write-bps=""

该接口用来限制指定设备的写速率,单位可以是 kb、mb 或者 gb。对应的 cgroup 文件是 cgroup/blkio/blkio.throttle.write_bps_device。

复制代码
$ docker run -it --device /dev/sda:/dev/sda --device-write-bps /dev/sda:1mB ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.throttle.write_bps_device"
8:0 1048576

以上 log 中显示 8:0 1000,8:0 表示 /dev/sda, 该接口对应的 cgroup 文件的值为 1048576,是 1MB 所对应的字节数,即 1024 的平方。

创建容器时通过–device-write-bps 接口设置设备写速度为 1MB/s。从以下 log 中可以看出, 读取速度被限定为 1.0MB/s, 与预期结果相符合。

限速操作:

复制代码
$ docker run -it --device /dev/sda:/dev/sda --device-write-bps /dev/sda:1mb ubuntu:14.04 bash
root@18dc79b91cd4:/# dd oflag=direct,nonblock of=/dev/sda if=/dev/urandom bs=10K count=1000
1000+0 records in
1000+0 records out
10240000 bytes (10 MB) copied, 10.1987 s, 1.0 MB/s

3.4.5 --device-read-iops=""

该接口设置了设备的 IO 读取速率,对应的 cgroup 文件是 cgroup/blkio/blkio.throttle.read_iops_device。

复制代码
$ docker run -it --device /dev/sda:/dev/sda --device-read-iops /dev/sda:400 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.throttle.read_iops_device"
8:0 400

可以通过"–device-read-iops /dev/sda:400"来限定 sda 的 IO 读取速率 (400 次 / 秒),log 如下所示。

复制代码
$ docker run -ti --device /dev/sda:/dev/sda --device-read-iops /dev/sda:400 ubuntu:14.04
root@71910742c445:/# dd iflag=direct,nonblock if=/dev/sda of=/dev/null bs=1k count=1000
1000+0 records in
1000+0 records out
1024000 bytes (1.0 MB) copied, 2.42874 s, 422 kB/s

通过上面的 log 信息可以看出,容器每秒 IO 的读取次数为 400,共需要读取 1000 次(log 第二行:count=1000),测试结果显示执行时间为 2.42874 秒,约为 2.5(1000/400) 秒, 与预期结果相符。

3.4.6 --device-write-iops=""

该接口设置了设备的 IO 写速率,对应的 cgroup 文件是 cgroup/blkio/blkio.throttle.write_iops_device。

复制代码
$ docker run -it --device /dev/sda:/dev/sda --device-write-iops /dev/sda:400 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.throttle.write_iops_device"
8:0 400

可以通过"–device-write-iops /dev/sda:400"来限定 sda 的 IO 写速率 (400 次 / 秒),log 如下所示。

复制代码
$ docker run -ti --device /dev/sda:/dev/sda --device-write-iops /dev/sda:400 ubuntu:14.04
root@ef88a516d6ed:/# dd oflag=direct,nonblock of=/dev/sda if=/dev/urandom bs=1K count=1000
1000+0 records in
1000+0 records out
1024000 bytes (1.0 MB) copied, 2.4584 s, 417 kB/s

通过上面的 log 信息可以看出,容器每秒 IO 的写入次数为 400,共需要写 1000 次(log 第二行:count=1000),测试结果显示执行时间为 2.4584 秒,约为 2.5(1000/400) 秒, 与预期结果相符。

4. 总结

Docker 的资源管理依赖于 Linux 内核 Cgroups 机制。理解 Docker 资源管理的原理并不难,读者可以根据自己兴趣补充一些有针对性的测试。关于 Cgroups 的实现机制已经远超本文的范畴。感兴趣的读者可以自行查看相关文章和内核手册。

作者简介

孙远,华为中央软件研究院资深工程师,硕士毕业,9 年软件行业经验。目前在华为从事容器 Docker 项目的测试工作。工作涉及到功能测试、性能测试、压力测试、稳定性测试、安全测试、测试管理、工程能力构建等内容。参与编写了《Docker 进阶与实战》的 Docker 测试章节。先前曾经就职于美国风河系统公司,作为 team leader 从事风河 Linux 产品测试工作。活跃于 Docker 社区和内核测试 ltp 社区,目前有大量测试用例被开源社区接收。
研究方向:容器技术、Docker、Linux 内核、软件测试、自动化测试、测试过程改进。

薛婉菊,中软国际科技服务有限公司软件测试工程师,4 年软件行业经验。目前参与容器 Docker 项目的测试工作,工作涉及到容器功能测试、性能测试、压力测试等内容。
研究方向:容器技术、Docker、自动化测试。

参考资料

  1. http://www.cnblogs.com/hustcat/p/3980244.html
  2. http://www.lupaworld.com/article-250948-1.html
  3. http://www.tuicool.com/articles/Qrq2Ynz
  4. https://github.com/docker/docker/blob/master/docs/reference/run.md
  5. http://www.infoq.com/cn/articles/docker-kernel-knowledge-namespace-resource-isolation
  6. https://github.com/torvalds/Linux/tree/master/Documentation/cgroup-v1
  7. http://www.361way.com/increase-swap/1957.html
  8. https://goldmann.pl/blog/2014/09/11/resource-management-in-docker/
  9. https://www.datadoghq.com/blog/how-to-collect-docker-metrics/

感谢木环对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016 年 12 月 04 日 16:5123284

评论

发布
暂无评论
发现更多内容

线程池面试必考

叫练

面试 线程池 线程池工作原理

一篇学会RSA JavaScript加密,涉及OpenSSL

梁龙先森

JavaScript 前端 28天写作 2月春节不断更

这些面试题你会吗?连续四年百度Android岗必问面试题!深度好文

欢喜学安卓

android 程序员 面试 移动开发

【进阶】面试官问我Chrome浏览器的渲染原理(6000字长文)

魔王哪吒

学习 程序员 chorme 28天写作 2月春节不断更

透彻解析!如何快速的开发一个完整的直播app,薪资翻倍

欢喜学安卓

android 程序员 面试 移动开发

go get下载包失败问题

happlyfox

Go 28天写作 2月春节不断更

Selenium 常用方法与属性、鼠标悬停与 Select 操作

梦想橡皮擦

Python 28天写作 2月春节不断更

“云原生”的应用价值及关键属性解读

浪潮云

话题讨论 | 技术人员的职业发展困惑,你也有么?

架构精进之路

职业规划 话题讨论 28天写作 技术人员

LiteOS:盘点那些重要的数据结构

华为云开发者社区

源码 数据结构 LiteOS LOS_DL_LIST Priority Queue

我在极客时间录课的故事(四):学习产生惰性是一种正常现象

石桥码农

我在极客时间录课的故事

从磁盘读取成本分析两种 100% 遍历思路:按格子遍历 & 按线遍历

宫水三叶的刷题日记

面试 LeetCode 数据结构与算法

诊所数字化:最大的数据资产-患者数字档案内容

boshi

电子病历 数字化医疗 七日更 28天写作

关于GaussDB(DWS)的正则表达式知多少?人人都能看得懂的详解来了!

华为云开发者社区

正则表达式 GaussDB

Fast AI人工智能审图平台-建筑图纸设计效率的倍增器

AI AI审图

基于matlab的控制系统与仿真2-传递函数模型

AXYZdong

matlab 2月春节不断更

工业互联网助力数字中国建设(新论)

浪潮云

工业互联网

前端面试常考题:JS垃圾回收机制

华为云开发者社区

JavaScript Vue 前端 js 垃圾回收

产业实践推动科技创新,京东科技集团3篇论文入选ICASSP 2021

京东科技开发者

人工智能 机器学习 信号 语音识别

解决dyld: Library not loaded icu4c

一个大红包

brew icu4c

产品经理训练营第四周总结

产品经理训练营

读书笔记-MySQL

crush

MySQL

教你如何优雅的改写“if-else”

华为云开发者社区

代码

「产品经理训练营」第五章作业

Sòrγy_じò ぴé

产品经理训练营

《第一财经(月刊2021年02期)》

石云升

读书笔记 28天写作 2月春节不断更 第一财经

用例的流程图

李钊悌

上天的源码要不要——GitHub 热点速览 v.21.08

HelloGitHub

Python Go GitHub 开源

如何解决深度推荐系统中的Embedding冷启动问题?

王喆

机器学习 深度学习 推荐系统 计算广告 Embedding

透彻解析!在字节跳动我是如何当面试官的,讲的明明白白!

欢喜学安卓

android 程序员 面试 移动开发

Java lambda表达式人类使用指南

ES_her0

28天写作

西北大学研发猴脸识别技术,金丝猴可刷脸打卡;IJCAI 2020丨基于学习实例隐空间的文本风格转换

京东科技开发者

人脸识别 IT 量子通讯

Docker资源管理探秘:Docker背后的内核Cgroups机制-InfoQ