docker资源配额

docker资源配额

Docker 通过 cgroup 来控制容器使用的资源限制,可以对 docker 限制的资源包括 CPU、内存和磁盘。

指定docker容器可以使用的cpu份额

CPU shares (relative weight) 在创建容器时指定容器所使用的 CPU 份额值。cpu-shares 的值不能保证可以获得 1 个 vcpu 或者多少 GHz 的 CPU 资源,仅仅只是一个弹性的加权值。

默认每个docker容器的cpu份额都是1024。在同一个CPU核心上,同时运行多个容器时,容器的cpu加权的效果才能体现出来。

例如:两个容器 A、B 的 cpu 份额分别为 1000 和 500,结果会怎么样?

情况 1:A 和 B 正常运行,占用同一个 CPU,在 cpu 进行时间片分配的时候,容器 A 比容器 B 多一倍的机会获得 CPU 的时间片。

情况 2:分配的结果取决于当时其他容器的运行状态。比如容器 A 的进程一直是空闲的,那么容器 B是可以获取比容器 A 更多的 CPU 时间片的; 比如主机上只运行了一个容器,即使它的 cpu 份额只有50,它也可以独占整个主机的 cpu 资源。

cgroups 只在多个容器同时争抢同一个 cpu 资源时,cpu 配额才会生效。因此,无法单纯根据某个容器的 cpu 份额来确定有多少 cpu 资源分配给它,资源分配结果取决于同时运行的其他容器的 cpu 分配和容器中进程运行情况。

例如:给容器实例分配512权重的cpu使用份额

[root@docker-master ~]# docker run -it --cpu-shares 512 centos bash
[root@31c17fd9c5d0 /]# cat /sys/fs/cgroup/cpu/cpu.shares 
512

注意:单独一个容器,看不出来使用的 cpu 的比例。 因没有 docker 实例同此 docker 实例竞争。测试需要启动多个容器,测试一下是不是只能使用 512 份额的 cpu 资源。

通过-c 设置的 cpu share 并不是 CPU 资源的绝对数量,而是一个相对的权重值。某个容器最终能分配到的 CPU 资源取决于它的 cpu share 占所有容器 cpu share 总和的比例。通过 cpu share可以设置容器使用 CPU 的优先级。

docker CPU core控制

对多核 CPU 的服务器,docker 还可以控制容器运行限定使用哪些 cpu 内核和内存节点,即使用--cpuset-cpus 和--cpuset-mems 参数。对具有 NUMA 拓扑(具有多 CPU、多内存节点)的服务器尤其有用,可以对需要高性能计算的容器进行性能最优的配置。如果服务器只有一个内存节点,则--cpuset-mems 的配置基本上不会有明显效果。

服务器架构一般分为SMP、NUMA、MPP 体系结构,这三个体系结构都是用于并行计算的,但它们在硬件组织和工作方式上有所不同。

  1. SMP(Symmetric Multiprocessing)

    • SMP 是一种对称多处理体系结构,它在单个系统内部有多个处理器(CPU),并且每个处理器都可以访问相同的物理内存和 I/O 设备。
    • 所有处理器之间共享相同的系统总线和内存总线。每个处理器可以访问系统中的所有内存区域,这使得 SMP 系统中的任何处理器都可以运行任何任务。
    • SMP 系统通常由几个 CPU 芯片组成,每个 CPU 有自己的缓存,但所有 CPU 共享同一组内存。
    • SMP 系统通常用于普通服务器和工作站,以及一般用途的多处理器计算机。
  2. NUMA(Non-Uniform Memory Access)

    • NUMA 是一种非一致性内存访问体系结构,它在一个系统中有多个处理器和多个内存节点,但每个处理器只能访问其本地节点的内存(本地内存)较快,而访问远程节点的内存则较慢。
    • NUMA 系统通常由多个处理器组成,每个处理器都与本地内存节点相连。每个内存节点与一组处理器连接,并且有自己的内存。
    • NUMA 系统中的处理器和内存被分割成多个 NUMA 区域,每个区域内的处理器可以访问相同的内存节点。
    • NUMA 系统通常用于高性能计算(HPC)、大型数据库和虚拟化环境等需要大量内存和高吞吐量的场景。
  3. MPP(Massively Parallel Processing)

    • MPP 是一种大规模并行处理体系结构,它由大量的处理器和存储节点组成,这些节点通过高速网络连接在一起。
    • 每个节点通常包含处理器、内存和存储,并且在节点之间共享数据和处理任务。
    • MPP 系统的设计旨在处理大规模数据并行任务,每个节点负责处理数据的一个子集,然后将结果汇总或合并。
    • MPP 系统通常用于大型数据仓库、数据分析、科学计算等需要高度并行处理的场景。

总的来说,SMP 适用于中等规模的多处理器系统,每个处理器可以访问相同的内存和 I/O;NUMA 适用于需要大量内存和高性能的系统,但内存访问速度可能不同;而 MPP 适用于大规模的数据并行处理任务,需要大量的处理器和存储节点。

docker cpu配额控制参数的混合使用

cpu-shares 控制只发生在容器竞争同一个 cpu 的时间片时有效。

如果通过 cpuset-cpus 指定容器 A 使用 cpu 0,容器 B 只是用 cpu1,在主机上只有这两个容器使用对应内核的情况,它们各自占用全部的内核资源,cpu-shares 没有明显效果。但是容器 A 和容器 B 配置上 cpuset-cpus 值都绑定到同一个 cpu 上,然后同时抢占 cpu 资源,就可以观察到控制效果。

拓展:stress命令

安装压力测试软件stress

[root@docker-master ~]# yum -y install eple-release
[root@docker-master ~]# yum -y install stress

stress常用参数及解释如下表所示

参数 解释
-c 产生 n 个进程,每个进程都反复不停的计算随机数的平方根,测试 cpu
-i 产生 n 个进程,每个进程反复调用 sync(),sync()用于将内存上的内容写到硬盘上,测试磁盘 io
-m 产生 n 个进程,每个进程不断调用内存分配 malloc()和内存释放 free()函数 ,测试内存
--vm-bytes 指定 malloc 时内存的字节数 (默认 256MB)
--vm-hang 指定在 free 栈的秒数
-v 显示版本信息
-n 显示已完成的指令信息
-t 指定运行 N 秒后停止
--backoff 等待指定微秒数后开始运行

产生2个cpu进程,2个io进程,20秒后停止

[root@docker-master ~]# stress -c 2 -i 2 -t 20s
stress: info: [119123] dispatching hogs: 2 cpu, 2 io, 0 vm, 0 hdd

在程序运行的过程中,使用top命令,查看cpu的负载情况

top - 04:11:27 up 6 days, 24 min,  3 users,  load average: 0.61, 0.30, 0.15
Tasks: 152 total,   5 running, 142 sleeping,   5 stopped,   0 zombie
%Cpu0  : 59.3 us, 40.7 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
#%Cpu1  : 62.9 us, 36.8 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.3 si,  0.0 st
KiB Mem :  1863032 total,   146140 free,   411696 used,  1305196 buff/cache
KiB Swap:  2097148 total,  2091772 free,     5376 used.  1166572 avail Mem 

#   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                          
#119124 root      20   0    7312     96      0 R  50.0  0.0   0:04.12 stress                                           
119126 root      20   0    7312     96      0 R  50.0  0.0   0:04.13 stress                                           
119125 root      20   0    7312     96      0 R  40.1  0.0   0:03.26 stress                                           
119127 root      20   0    7312     96      0 D  40.1  0.0   0:03.30 stress               ...省略部分输出...

对#号的行进行参数的解析

参数 解释
%Cpu1 第二个CPU
us 用户空间,表示 CPU 用于执行用户进程的时间百分比,100.0表示 CPU 的所有时间都花在了用户进程上
sy 系统内核,表示 CPU 用于执行内核进程的时间百分比
ni nice值,表示 CPU 用于执行用户进程,但是这些进程的优先级被调整了
id 空闲,表示 CPU 处于空闲状态的时间百分比,0表示 CPU 没有空闲时间
wa I/O等待, CPU 等待 I/O 操作完成的时间百分比
hi 硬中断,表示 CPU 处理硬中断的时间百分比
si 软中断,表示 CPU 处理软中断的时间百分比
st 虚拟机偷窃,表示被虚拟化环境偷窃的时间百分比
参数 解释
PID 进程 ID,用于唯一标识每个进程
USER 启动进程的用户
PR 进程的优先级
NI 进程的 nice 值,表示进程的优先级调整
VIRT 进程占用的虚拟内存大小(以 KB 为单位)
RES 进程当前使用的物理内存大小(以 KB 为单位)
SHR 进程使用的共享内存大小(以 KB 为单位)
S 进程状态(R表示运行,D表示不可中断的睡眠,S表示睡眠,Z表示僵尸进程等)
%CPU 进程占用 CPU 的百分比
%MEN 进程占用内存的百分比
TIME+ 进程已经运行的时间
COMMAND 启动进程的命令

根据第三行的#号条目,可以得出

1、进程 ID 为 119124 的进程是一个名为stress的进程,它在进行 CPU 压力测试(R状态,表示正在运行)。

2、该进程占用了 50.0% 的 CPU 资源。

3、它占用了较少的虚拟内存(VIRT)和物理内存(RES)。

4、这个进程启动时使用的命令是stress。

例如:测试 cpu-shares 和 cpuset-cpus 混合使用运行效果,创建两个容器实例:docker10 和 docker20,容器只允许在cpu0上运行,并为两个容器设置不同的份额,观察实验效果。

创建docker10实例,只允许在cpu0上运行,使用份额为512

[root@docker-master ~]# docker run -itd --name docker10 --cpuset-cpus 0 --cpu-shares 512 centos-repo /bin/bash
a6a71f6b3f50ec8829424c69db625e8bf30221b9e5cf1df4b8eaaa5363c8376f

创建docker20实例,只允许在cpu0上运行,使用份额为1024

[root@docker-master ~]# docker run -itd --name docker20 --cpuset-cpus 0 --cpu-shares 1024 centos-repo /bin/bash
a4f99b3c1c3cfea29d6b760ad30860fd0b9963acdc2c4c30f5c89e9a822381fe

在docker10容器内安装stress命令,进行压测

[root@1fff2ce6c839 /]# yum -y install eple-release
[root@1fff2ce6c839 /]# yum -y install stress
[root@1fff2ce6c839 /]# stress -c 2 -t 10m
stress: info: [101] dispatching hogs: 2 cpu, 0 io, 0 vm, 0 hdd

在docker20容器内安装stress命令,进行压测

[root@0de66602e400 /]# yum -y install eple-release
[root@0de66602e400 /]# yum -y install stress
[root@0de66602e400 /]# stress -c 2 -t 10m
stress: info: [101] dispatching hogs: 2 cpu, 0 io, 0 vm, 0 hdd

新建终端,使用top命令,查看cpu负载情况。两个容器只在 cpu0 上运行,说明 cpu 绑定限制成功。而 docker20 是 docker10 使用 cpu 的 2倍。说明--cpu-shares 限制资源成功。

top - 04:50:40 up 6 days,  1:03,  4 users,  load average: 2.53, 0.98, 0.46
Tasks: 162 total,   5 running, 152 sleeping,   5 stopped,   0 zombie
%Cpu0  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  0.3 us,  0.3 sy,  0.0 ni, 97.4 id,  0.0 wa,  0.0 hi,  2.0 si,  0.0 st
KiB Mem :  1863032 total,   192844 free,   443132 used,  1227056 buff/cache
KiB Swap:  2097148 total,  2090172 free,     6976 used.  1136848 avail Mem 

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                          
120597 root      20   0    7960     92      0 R  33.6  0.0   0:07.67 stress                                           
120598 root      20   0    7960     92      0 R  33.2  0.0   0:07.66 stress                                           
120507 root      20   0    7960     96      0 R  16.6  0.0   0:24.55 stress                                           
120508 root      20   0    7960     96      0 R  16.6  0.0   0:24.55 stress 
...省略部分输出...
docker容器控制内存

docker使用-m参数对内存使用量进行控制

例如:允许容器使用的内存上限为128M

[root@docker-master ~]# docker run -it -m 128m centos
[root@161bfae868fe /]# cat /sys/fs/cgroup/memory/memory.limit_in_bytes 
134217728
docker容器控制IO

docker使用--device-write-bps value参数控制容器对宿主机IO设备的读写速度

例如:限制容器实例对硬盘的最高写入速度为2MB/s

生成容器,限制容器对IO写入的速度为2MB/s

[root@docker-master ~]# docker run -it --rm --device-write-bps /dev/sda:2MB centos
[root@28e417935901 /]# 

docker inspect命令查看限制写入的参数是否生效

[root@28e417935901 /]# docker inspect 28e417935901
...省略部分输出...
            "BlkioWeight": 0,
            "BlkioWeightDevice": [],
            "BlkioDeviceReadBps": null,
            "BlkioDeviceWriteBps": [
                {
                    "Path": "/dev/sda",
                    "Rate": 2097152
                }
            ],
            "BlkioDeviceReadIOps": null,
            "BlkioDeviceWriteIOps": null,
...省略部分输出...

使用time和dd命令生成测试文件,观察到当前读写速度为669MB/s

[root@28e417935901 /]# time dd if=/dev/zero of=test bs=2M count=10          
10+0 records in
10+0 records out
20971520 bytes (21 MB, 20 MiB) copied, 0.0313692 s, 669 MB/s

real    0m0.035s
user    0m0.000s
sys     0m0.035s

添加direct和nonblock参数,再次测试,发现写入速度降低,证明容器读写IO限制成功

[root@28e417935901 /]# time dd if=/dev/zero of=test bs=2M count=10 oflag=direct,nonblock
10+0 records in
10+0 records out
20971520 bytes (21 MB, 20 MiB) copied, 10.0226 s, 2.1 MB/s

real    0m10.025s
user    0m0.006s
sys     0m0.000s
参数 解释
direct 读写数据采用直接 IO 方式,不走缓存。直接从内存写硬盘上
nonblock 读写数据采用非阻塞 IO 方式,优先写 dd 命令的数据
docker容器运行结束后自动释放资源

自动释放资源的使用场景适用于许多情况,特别是在以下情况下:

  1. 开发和测试环境:在开发和测试过程中,经常需要运行各种容器化的应用程序。当这些应用程序不再需要时,自动释放资源可以确保不浪费资源,并使得开发和测试环境更加高效。
  2. CI/CD 流水线:在持续集成和持续交付(CI/CD)流水线中,会频繁地启动容器来运行测试、构建和部署任务。一旦任务完成,这些容器就可以自动释放资源,从而避免在无用的容器堆积中浪费资源。
  3. 临时性工作负载:有些工作负载只需要暂时运行,比如数据处理任务、临时服务等。在这种情况下,自动释放资源可以确保资源在不再需要时被回收,节省成本和资源。
  4. 资源密集型任务:某些任务可能需要大量的计算资源,但是只需要在特定时间段内运行。通过自动释放资源,可以确保在任务完成后立即释放这些资源,以便其他任务或服务可以使用它们。
  5. 负载均衡和伸缩:在负载均衡和自动伸缩的环境中,自动释放资源可以确保在负载下降时释放不再需要的容器,从而减少资源浪费。

docker使用rm参数使容器命令运行结束后,自动删除容器,自动释放资源。

例如:

运行一个名为 "rm" 的容器,基于 CentOS 镜像,并在容器内执行 sleep 6命令,意味着容器将休眠 6 秒钟,指定rm参数,容器命令运行完成后删除容器。

[root@docker-master ~]# docker run -itd --rm --name rm centos sleep 6
7a5b62e21e8e3747fab61546531556cf405c611b8d9f8a8800ffe6425c6ada2e

在宿主机上查看容器运行状态,等待6秒后,再次查看容器状态,由于命令已经被执行,所以容器已经被删除

[root@docker-master ~]# docker ps
CONTAINER ID   IMAGE     COMMAND     CREATED         STATUS         PORTS     NAMES
7a5b62e21e8e   centos    "sleep 6"   3 seconds ago   Up 2 seconds             rm
[root@docker-master ~]# docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
[root@docker-master ~]# docker ps -a | grep rm
[root@docker-master ~]#