Docker 通过 Cgroup 来控制容器使用的资源配额,包括 CPU、内存、磁盘三大方面,基本覆盖了常见的资源配额和使用量控制。
Cgroup 是 Control Groups 的缩写,是 Linux 内核提供的一种可以限制、记录、隔离进程组所使用的物理资源(如CPU、内存、磁盘IO等等)的机制,被LXC、docker等很多项目用于实现进程资源控制。
Cgroup 本身是提供将进程进行分组化管理的功能和接口的基础结构,I/O或内存的分配控制等具体的资源管理是通过该功能来实现的,这些具体的资源管理功能称为Cgroup子系统,有以下几大子系统实现:
blkio 设置限制每个块设备的输入输出控制。例如:磁盘,光盘以及usb等等。
CPU 使用调度程序为 cgroup 任务提供 CPU 的访问。
cpuacct 产生 cgroup 任务的 CPU 资源报告。
cpuset 如果是多核心的 CPU ,这个子系统会为 cgroup 任务分配单独的 CPU 的内存。
devices 允许或拒绝 cgroup 任务对设备的访问。
freezer 暂停和恢复 cgroup 任务。
memory 设置每个 cgroup 的内存限制以及产生内存资源报告。
net_cls 标记每个网络包以供 cgroup 方便使用。
ns 命名空间子系统。
perf_event 增加了对每个 group 的监测跟踪的能力,可以监测属于某个特定的 group 的所有线程以及运行在特定 CPU 上的线程。
补充:cpu在一个时刻(时间上的一个点)只会给一个进程提供算例;3.5GHZ是频率,表示给进程提供算例的频率;时间分片,就是cpu在一段时间里,将这一段时间中再次划分时间段,在这些时间段中给不同的进程提供算例(一个时间只能供一个进程)。
使用Dockerfile来创建一个基于Centos的stress工具镜像。
mkdir /opt/stress
vim /opt/stress/Dockerfile
FROM centos:7
MAINTAINER li
RUN yum install -y wget
RUN wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
RUN yum install -y stress
cd /opt/stress/
docker build -t centos:stress .
使用如下命令创建容器,命令中的- -cpu-shares参数值不能保证可以获得1个vcpu或者多少GHz的CPU资源,它仅是一个弹性的加权值。
docker run -itd --cpu-shares 100 centos:stress
说明:默认情况下,每个Docker容器的CPU份额都是1024。单独一个容器的份额是没有意义的。只有在同时运行多个容器时,容器的CPU加权的效果才能体现出来。
例如,两个容器A、B的CPU份额分别为1000和500,在CPU进行时间片分配的时候,容器A比容器B多一倍的机会获得CPU的时间片。但分配的结果取决于当时主机和其他容器的运行状态,实际上也无法保证容器A一定能获得CPU时间片。如果容器A的进程一直是空闲的,那么容器B是可以获取比容器A更多的CPU时间片的。极端情况下,例如主机上只运行了一个容器,即使它的CPU份额只有50,它也可以独占整个主机的CPU资源。
Cgroups只在容器分配的资源紧缺时,即在需要对容器使用的资源进行限制时,才会生效。因此,无法单纯根据某个容器的CPU份额来确定有多少CPU资源分配给它,资源分配结果取决于同时运行的其他容器的CPU分配和容器中进程运行情况。
可以通过cpu share可以设置容器使用CPU的优先级,比如启动了两个容器及运行查看CPU使用百分比。
//先创建两个容器
//容器产生10个子函数进程,设置cpu优先级为512
docker run -tid --name cpu512 --cpu-shares 512 centos:stress stress -c 10
//容器产生10个子函数进程,设置cpu优先级为1024
docker run -tid --name cpu1024 --cpu-shares 1024 centos:stress stress -c 10
docker ps -a
//进入容器使用top查看cpu使用情况
docker exec -it c6067932d3a6 bash
top
exit
//进容器使用top对比两个容器的%CPU,比例是1:2
docker exec -it f6a39487959d bash
top
exit
Docker提供了–cpu-period、–cpu-quota两个参数控制容器可以分配到的CPU时钟周期。
--cpu-period 是用来指定容器对CPU的使用要在多长时间内做一次重新分配
--cpu-quota 是用来指定在这个周期内,最多可以有多少时间用来跑这个容器
与 --cpu-shares 不同的是,这种配置是指定一个绝对值,容器对 CPU 资源的使用绝对不会超过配置的值。
cpu-period 和 cpu-quota 的单位为微秒(us)。 cpu-period 的最小值为1000微秒,最大值为1秒(10^6 us),默认值为 0.1 秒(100000 us)。
cpu-quota的值默认为-1,表示不做控制。
cpu-period和cpu-quota参数一般联合使用。
例如:容器进程需要每1秒使用单个CPU的0.2秒时间,可以将 cpu-period 设置为1000000(即1秒),cpu-quota 设置为 200000 (0.2秒)。
当然,在多核情况下,如果允许容器进程完全占用两个CPU,则可以将 cpu-period 设置为100000(即0.1秒),cpu-quota 设置为200000(0.2秒)。
docker run -tid --cpu-period 100000 --cpu-quota 200000 centos:stress
docker exec -it 602299e4f013 bash
cat /sys/fs/cgroup/cpu/cpu.cfs_period_us
100000
cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us
200000
对多核 CPU 的服务器,Docker 还可以控制容器运行使用哪些CPU内核,即使用- -cpuset-cpus 参数。
这对具有多CPU的服务器尤其有用,可以对需要高性能计算的容器进行性能最优的配置
docker run -tid --name cpu1 --cpuset-cpus 0-1 centos:stress
执行以上命令需要宿主机为双核,表示创建的容器只能用0、1两个内核。最终生成的cgroup的CPU内核配置如下;
docker exec -it 30bc930bfa59 bash
cat /sys/fs/cgroup/cpuset/cpuset.cpus
0-1
通过下面指令可以看到容器中进程与 CPU 内核的绑定关系,达到绑定CPU内核的目的。
//容器内部第一个进程号pid为1被绑定到指定CPU上运行
docker exec 30bc930bfa59 taskset -c -p 1
通过cpuset-cpus参数指定容器A使用CPU内核0,容器B只是用CPU内核1。
在主机上只有这两个容器使用对应CPU内核的情况,它们各自占用全部的内核资源,cpu-shares没有明显效果。
cpuset-cpus、cpuset-mems参数只在多核、多内存节点上的服务器上有效,并且必须与实际的物理配置匹配,否则也无法达到资源控制的目的。
在系统具有多个CPU内核的情况下,需要通过cpuset-cpus 参数为设置容器CPU内核才能方便地进行测试。
//测试前需要将宿主系统修改为4核心CPU
//创建两个容器,分别指定不同的cpu
docker run -tid --name cpu2 --cpuset-cpus 1 --cpu-shares 512 centos:stress stress -c 1
docker run -tid --name cpu3 --cpuset-cpus 3 --cpu-shares 1024 centos:stress stress -c 1
top //记住按1查看每个核心的占用
总结:上面的centos:stress镜像安装了stress 工具,用来测试CPU和内存的负载。通过在两个容器上分别执行stress -c1 命令,将会给系统一个随机负载,产生1个进程。这个进程都反复不停的计算由 rand() 产生随机数的平方根,直到资源耗尽。
观察到宿主机上的CPU使用率,第三个内核的使用率接近100%,并且一批进程的CPU使用率明显存在2:1的使用比例的对比。
与操作系统类似,容器可使用的内存包括两部分:物理内存和Swap。
Docker 通过下面两组参数来控制容器内存的使用量。
-m 或--memory:设置内存的使用限额,例如100M、1024M。
--memory-swap:设置内存+swap的使用限额。
执行如下命令允许该容器最多使用200M的内存和300M的swap。
docker run -it -m 200M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 280M
--vm 1:启动1个内存工作线程。
--vm-bytes 280M:每个线程分配280M内存。
默认情况下,容器可以使用主机上的所有空闲内存。
与CPU的cgroups配置类似,Docker会自动为容器在目录/sys/fs/cgroup/memory/docker/<容器的完整长ID>中创建相应cgroup配置文件
如果让工作线程分配的内存超过300M,分配的内存超过限额,stress线程报错,容器退出。
docker run -it -m 200M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 310M
默认情况下,所有容器能平等地读写磁盘,可以通过设置- -blkio-weight 参数来改变容器 block IO 的优先级。- -blkio-weight 与- -cpu-shares类似,设置的是相对权重值,默认为500。
在下面的例子中,容器A读写磁盘的带宽是容器B的两倍。
docker run -it --name container_A --blkio-weight 600 centos:stress
cat /sys/fs/cgroup/blkio/blkio.weight
600
docker run -it --name container_B --blkio-weight 300 centos:stress
cat /sys/fs/cgroup/blkio/blkio.weight
300
bps是byte per second,每秒读写的数据量(吞吐量)。
iops 是io per second,每秒IO的次数。
可通过以下参数控制容器的bps和iops:
--device-read-bps,限制读某个设备的bps。
--device-write-bps,限制写某个设备的bps。
--device-read-iops,限制读某个设备的iops。
--device-write-iops,限制写某个设备的iops.
下面的示例是限制容器写/dev/sda的速率为5MB/s.
docker run -it --device-write-bps /dev/sda:5MB centos:stress
dd if=/dev/zero of=test bs=1M count=1024 oflag=direct //可以按ctrl+c中断查看
#-------------------------输出内容---------------------------------
^C18+0 records in
18+0 records out
18874368 bytes (19 MB) copied, 3.60306 s, 5.2 MB/s
上图说明:通过dd命令测试在容器中写磁盘的速度。因为容器的文件系统是在 host /dev/sda 上的,在容器中写文件相当于对host /dev/sda 进行写操作。另外,oflag=direct 指定用direct lO方式写文件,这样- -device-write-bps才能生效。
结果表明限速5MB/s左右。作为对比测试,如果不限速,结果如下。
docker run -it centos:stress
dd if=/dev/zero of=test bs=1M count=1024 oflag=direct
#-----------------------输出内容-------------------------------
^C980+0 records in
980+0 records out
1027604480 bytes (1.0 GB) copied, 0.93197 s, 1.1 GB/s