docker/docker-资源限制.md
2025-04-04 17:31:40 +08:00

287 lines
15 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<h2><center>Docker 资源限制</center></h2>
------
## 一Docker 资源限制
在使用 Docker 运行容器时,一台主机上可能会运行几百个容器,这些容器虽然互相隔离,但是底层却使用着相同的 CPU、内存和磁盘资源。如果不对容器使用的资源进行限制那么容器之间会互相影响小的来说会导致容器资源使用不公平大的来说可能会导致主机和集群资源耗尽服务完全不可用。
CPU 和内存的资源限制已经是比较成熟和易用,能够满足大部分用户的需求。磁盘限制也是不错的,虽然现在无法动态地限制容量,但是限制磁盘读写速度也能应对很多场景。
至于网络Docker 现在并没有给出网络限制的方案,也不会在可见的未来做这件事情,因为目前网络是通过插件来实现的,和容器本身的功能相对独立,不是很容易实现,扩展性也很差。
资源限制一方面可以让我们为容器(应用)设置合理的 CPU、内存等资源方便管理另外一方面也能有效地预防恶意的攻击和异常对容器来说是非常重要的功能。如果你需要在生产环境使用容器请务必要花时间去做这件事情。
### 1. 系统压力测试
`stress`是一个`linux`下的压力测试工具,专门为那些想要测试自己的系统,完全高负荷和监督这些设备运行的用户。
**安装**
```bash
yum -y install stress
```
**测试场景举例**
```bash
测试CPU负荷
# stress -c 4
增加4个cpu进程处理sqrt()函数函数以提高系统CPU负荷
内存测试
# stress i 4 --vm 10 --vm-bytes 1G --vm-hang 100 --timeout 100s
新增4个io进程10个内存分配进程每次分配大小1G分配后不释放测试100S
磁盘I/O测试
# stress d 1 --hdd-bytes 3G
新增1个写进程每次写3G文件块
硬盘测试(不删除)
# stress -i 1 -d 10 --hdd-bytes 3G hdd-noclean
新增1个IO进程10个写进程每次写入3G文件块且不清除会逐步将硬盘耗尽。
```
`stress`各主用参数说明:
```shell
--help 显示帮助信息
--version 显示软件版本信息
-t secs:
--timeout secs指定运行多少秒
-c forks:
--cpu forks 产生多个处理sqrt()函数的CPU进程
-m forks
--vm forks:产生多个处理malloc()内存分配函数的进程,后接进程数量
-i forks
--io forks:产生多个处理sync()函数的磁盘I/O进程
--vm-bytes bytes指定内存的byte数默认值是1
--vm-hang:表示malloc分配的内存多少时间后在free()释放掉
-d :
--hdd:写进程写入固定大小通过mkstemp()函数写入当前目录
--hdd-bytes bytes:指定写的byte数默认1G
--hdd-noclean:不要将写入随机ascii数据的文件unlink则写入的文件不删除会保留在硬盘空间。
```
### 2. 限制CPU share
**CPU 资源:**
主机上的进程会通过时间分片机制使用 CPUCPU 的量化单位是频率,也就是每秒钟能执行的运算次数。为容器限制 CPU 资源并不能改变 CPU 的运行频率,而是改变每个容器能使用的 CPU 时间片。理想状态下CPU 应该一直处于运算状态(并且进程需要的计算量不会超过 CPU 的处理能力)。
**Docker 限制 CPU Share**
docker 允许用户为每个容器设置一个数字,代表容器的 CPU share默认情况下每个容器的 share 是 1024。这个 share 是相对的,本身并不能代表任何确定的意义。当主机上有多个容器运行时,每个容器占用的 CPU 时间比例为它的 share 在总额中的比例。docker 会根据主机上运行的容器和进程动态调整每个容器使用 CPU 的时间比例。
例子:
如果主机上有两个一直使用 CPU 的容器(为了简化理解,不考虑主机上其他进程),其 CPU share 都是 1024那么两个容器 CPU 使用率都是 50%;如果把其中一个容器的 share 设置为 512那么两者 CPU 的使用率分别为 67% 和 33%;如果删除 share 为 1024 的容器,剩下来容器的 CPU 使用率将会是 100%。
**好处:**
能保证 CPU 尽可能处于运行状态,充分利用 CPU 资源,而且保证所有容器的相对公平。
**缺点:**
无法指定容器使用 CPU 的确定值。
**设置 CPU share 的参数:**
-c --cpu-shares它的值是一个整数
我的机器是 4 核 CPU因此运行一个stress容器,使用 stress 启动 4 个进程来产生计算压力:
```bash
# docker pull progrium/stress
# yum install htop -y
# docker run --rm -it progrium/stress --cpu 4
stress: info: [1] dispatching hogs: 4 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 12000us
stress: dbug: [1] --> hogcpu worker 4 [7] forked
stress: dbug: [1] using backoff sleep of 9000us
stress: dbug: [1] --> hogcpu worker 3 [8] forked
stress: dbug: [1] using backoff sleep of 6000us
stress: dbug: [1] --> hogcpu worker 2 [9] forked
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogcpu worker 1 [10] forked
```
在另外一个 terminal 使用 htop 查看资源的使用情况:
上图中看到CPU 四个核资源都达到了 100%。四个 stress 进程 CPU 使用率没有达到 100% 是因为系统中还有其他机器在运行
为了比较,另外启动一个 share 为 512 的容器
```bash
# docker run --rm -it -c 512 progrium/stress --cpu 4
stress: info: [1] dispatching hogs: 4 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 12000us
stress: dbug: [1] --> hogcpu worker 4 [6] forked
stress: dbug: [1] using backoff sleep of 9000us
stress: dbug: [1] --> hogcpu worker 3 [7] forked
stress: dbug: [1] using backoff sleep of 6000us
stress: dbug: [1] --> hogcpu worker 2 [8] forked
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogcpu worker 1 [9] forked
```
因为默认情况下,容器的 CPU share 为 1024所以这两个容器的 CPU 使用率应该大致为 21下面是启动第二个容器之后的监控截图
两个容器分别启动了四个 stress 进程,第一个容器 stress 进程 CPU 使用率都在 54% 左右,第二个容器 stress 进程 CPU 使用率在 25% 左右,比例关系大致为 21符合之前的预期
### 3. 限制容器能使用的 CPU 核数
-c --cpu-shares 参数只能限制容器使用 CPU 的比例,或者说优先级,无法确定地限制容器使用 CPU 的具体核数;从 1.13 版本之后docker 提供了 --cpus 参数可以限定容器能使用的 CPU 核数。这个功能可以让我们更精确地设置容器 CPU 使用量,是一种更容易理解也因此更常用的手段。
--cpus 后面跟着一个浮点数,代表容器最多使用的核数,可以精确到小数点二位,也就是说容器最小可以使用 0.01 核 CPU。
限制容器只能使用 1.5 核数 CPU
```bash
# docker run --rm -it --cpus 1.5 progrium/stress --cpu 3
stress: info: [1] dispatching hogs: 3 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 9000us
stress: dbug: [1] --> hogcpu worker 3 [7] forked
stress: dbug: [1] using backoff sleep of 6000us
stress: dbug: [1] --> hogcpu worker 2 [8] forked
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogcpu worker 1 [9] forked
```
在容器里启动三个 stress 来跑 CPU 压力,如果不加限制,这个容器会导致 CPU 的使用率为 300% 左右(也就是说会占用三个核的计算能力)。实际的监控如下图:
可以看到,每个 stress 进程 CPU 使用率大约在 50%,总共的使用率为 150%,符合 1.5 核的设置。
如果设置的 --cpus 值大于主机的 CPU 核数docker 会直接报错:
```bash
# docker run --rm -it --cpus 8 progrium/stress --cpu 3
docker: Error response from daemon: Range of CPUs is from 0.01 to 4.00, as there are only 4 CPUs available.
See 'docker run --help'.
```
如果多个容器都设置了 --cpus ,并且它们之和超过主机的 CPU 核数,并不会导致容器失败或者退出,这些容器之间会竞争使用 CPU具体分配的 CPU 数量取决于主机运行情况和容器的 CPU share 值。也就是说 --cpus 只能保证在 CPU 资源充足的情况下容器最多能使用的 CPU 数docker 并不能保证在任何情况下容器都能使用这么多的 CPU因为这根本是不可能的
### 4. 内存资源
Docker 默认没有对容器内存进行限制,容器可以使用主机提供的所有内存。
不限制内存带来的问题:
这是非常危险的事情,如果某个容器运行了恶意的内存消耗软件,或者代码有内存泄露,很可能会导致主机内存耗尽,因此导致服务不可用。可以为每个容器设置内存使用的上限,一旦超过这个上限,容器会被杀死,而不是耗尽主机的内存。
限制内存带来的问题:
限制内存上限虽然能保护主机,但是也可能会伤害到容器里的服务。如果为服务设置的内存上限太小,会导致服务还在正常工作的时候就被 OOM 杀死;如果设置的过大,会因为调度器算法浪费内存。
合理做法:
- 为应用做内存压力测试,理解正常业务需求下使用的内存情况,然后才能进入生产环境使用。
- 一定要限制容器的内存使用上限,尽量保证主机的资源充足,一旦通过监控发现资源不足,就进行扩容或者对容器进行迁移如果可以(内存资源充足的情况)。
- 尽量不要使用 swapswap 的使用会导致内存计算复杂,对调度器非常不友好
**Docker 限制容器内存使用量:**
在 docker 启动参数中,和内存限制有关的包括(参数的值一般是内存大小,也就是一个正数,后面跟着内存单位 b、k、m、g分别对应 bytes、KB、MB、和 GB
```shell
-m --memory容器能使用的最大内存大小最小值为 4m
--memory-swap容器能够使用的 swap 大小
--memory-swappiness默认情况下主机可以把容器使用的匿名页anonymous pageswap 出来,你可以设置一个 0-100 之间的值,代表允许 swap 出来的比例
--memory-reservation设置一个内存使用的 soft limit(软限制),如果 docker 发现主机内存不足,会执行 OOM 操作。这个值必须小于 --memory 设置的值
--kernel-memory容器能够使用的 kernel memory (内核内存)大小,最小值为 4m。
--oom-kill-disable是否运行 OOM 的时候杀死容器。只有设置了 -m才可以把这个选项设置为 false(),否则容器会耗尽主机内存,而且导致主机应用被杀死
```
- 关于 --memory-swap 的设置: --memory-swap 必须在 --memory 也配置的情况下才能有用。
- 如果 --memory-swap 的值大于 --memory那么容器能使用的总内存内存 + swap为 --memory-swap 的值,能使用的 swap 值为 --memory-swap 减去 --memory 的值。
- 如果 --memory-swap 为 0或者和 --memory 的值相同,那么容器能使用两倍于内存的 swap 大小,如果 --memory 对应的值是 200M那么容器可以使用 400M swap。
- 如果 --memory-swap 的值为 -1那么不限制 swap 的使用,也就是说主机有多少 swap容器都可以使用
如果限制容器的内存使用为 64M在申请 64M 资源的情况下,容器运行正常(如果主机上内存非常紧张,并不一定能保证这一点)
```bash
# docker run --rm -it -m 64m progrium/stress --vm 1 --vm-bytes 64M --vm-hang 0
WARNING: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogvm worker 1 [7] forked
stress: dbug: [7] allocating 67108864 bytes ...
stress: dbug: [7] touching bytes in strides of 4096 bytes ...
stress: dbug: [7] sleeping forever with allocated memory
.....
```
而如果申请 100M 内存,会发现容器里的进程被 kill 掉了worker 7 got signal 9signal 9 就是 kill 信号)。
```bash
# docker run --rm -it -m 64m progrium/stress --vm 1 --vm-bytes 100M --vm-hang 0
WARNING: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogvm worker 1 [7] forked
stress: dbug: [7] allocating 104857600 bytes ...
stress: dbug: [7] touching bytes in strides of 4096 bytes ...
stress: FAIL: [1] (415) &lt;-- worker 7 got signal 9
stress: WARN: [1] (417) now reaping child worker processes
stress: FAIL: [1] (421) kill error: No such process
stress: FAIL: [1] (451) failed run completed in 0s
```
### 5. IO 资源
对于磁盘来说,考量的参数是容量和读写速度,因此对容器的磁盘限制也应该从这两个维度出发。目前 docker 支持对磁盘的读写速度进行限制,但是并没有方法能限制容器能使用的磁盘容量(一旦磁盘 mount 到容器里,容器就能够使用磁盘的所有容量)。
限制磁盘的读写速率docker 允许你直接限制磁盘的读写速率,对应的参数有:
- --device-read-bps磁盘每秒最多可以读多少比特bytes
- --device-write-bps磁盘每秒最多可以写多少比特bytes
上面两个参数的值都是磁盘以及对应的速率,限制 limit 为正整数,单位可以是 kb、mb 和 gb
比如可以把设备的读速率限制在 1mb
```bash
# docker run -it --device /dev/sda:/dev/sda --device-read-bps /dev/sda:1mb ubuntu:16.04 bash
root@6c048edef769:/# cat /sys/fs/cgroup/blkio/blkio.throttle.read_bps_device
8:0 1048576
root@6c048edef769:/# dd iflag=direct,nonblock if=/dev/sda of=/dev/null bs=5M count=10
10+0 records in
10+0 records out
52428800 bytes (52 MB) copied, 50.0154 s, 1.0 MB/s
```
从磁盘中读取 50m 花费了 50s 左右,说明磁盘速率限制起了作用
另外两个参数可以限制磁盘读写频率(每秒能执行多少次读写操作):
- --device-read-iops磁盘每秒最多可以执行多少 IO 读操作
- --device-write-iops磁盘每秒最多可以执行多少 IO 写操作
上面两个参数的值都是磁盘以及对应的 IO 上限
比如,可以让磁盘每秒最多读 100 次:
```bash
# docker run -it --device /dev/sda:/dev/sda --device-read-iops /dev/sda:100 ubuntu:16.04 bash
root@2e3026e9ccd2:/# 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, 9.9159 s, 103 kB/s
```
从测试中可以看出,容器设置了读操作的 iops 为 100在容器内部从 block 中读取 1m 数据(每次 1k一共要读 1000 次),共计耗时约 10s换算起来就是 100。
总结:
Linux Cgroups 的设计还是比较易用的,简单粗暴地理解呢,它就是一个子系统目录加上一组资源限制文件的组合。