暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

一文搞定Docker容器资源限制

稻壳编程 2021-06-25
1651

211工程院校贵州大学管理学院硕士研究生、全国百强城商行资讯科技部五级专技工程师、互联网金融行业资深DevOps研发工程师、金融科技运维自动化平台研发项目经理。曾在国内多家知名互联网公司 平安科技、微众银行、顺丰科技、魅族任职. 具有多年国内一线互联网公司自动化运维平台设计与开发经验。



 1 

 前言

默认情况下,宿主机对运行中的容器能占用多少系统资源是没有限制的,因此运行中的容器可以使用宿主机内核调度程序所允许的尽可能多的系统资源。Docker提供了一种用来控制能够使用多少内存和CPU的方法——即在使用docker run命令启动容器时通过指定配置标识来实现。本节内容主要讲解的内容包括,何时应启用资源限制以及设置这些限制可能会产生的影响两部分。Docker通过cgroup来控制容器使用的资源配额,cgroup是Linux系统内核提供的一种用来限制、记录、隔离进程组所使用的物理资源的机制,能够实现对进程的分组化管理。


 2 

 CPU资源限制

Docker在创建容器时所支持的CPU限制选项包括:
    [root@localhost ~]#  docker run --help | grep cpu 
    --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)
    --cpus decimal Number of CPUs
    --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)
    说明:
    --cpus:用来限制cpu的使用上限
    --cpu-period和--cpu-quota:组合起来限制cpu的使用上限,可以使用cpus替代
    --cpu-shares:
    用来设置CPU发生竞争时的权重比例,资源充足时没有效果
    --cpset-cpus:设置容器只能使用某个或某几个核心
    --cpuset-mems:设置容器使用的内存节点,只有在NUMA架构下才支持,有关NUMA架构的相关知识可参考文档:http://cenalulu.github.io/linux/numa/


     2.1 

     限制CPU的使用率

    通过参数--cpus可限制容器可用的CPU使用率上限,若不做限制而直接启动容器,通过stress工具对容器的CPU使用做压力测试,那么最终宿主机的CPU资源将被耗尽,启动压测工具,如下所示:

      [root@localhost ~]# docker run --rm -it polinux/stress stress --cpu 2 
      注:在stress压测工具中配置使用2个CPU,是因为笔者宿主机环境就是2个CPU。
      在Docker宿主机上使用htop工具,观察CPU使用情况,如下图所示:

      综上,由于未对容器做资源限制,运行了stress压测工具的容器,将会占满宿主机的两个CPU,每个CPU资源的使用率均为100%,合计为200%;接下来重新启动容器,在docker run命令中附加--cpus参数并设置其值为1.5,此时容器的CPU使用率被限制为150%,再次启动压测工具,如下所示:
        [root@localhost ~]# docker run --rm --cpus=1.5  -it polinux/stress stress --cpu 2 
        在Docker宿主机上使用htop工具,观察CPU使用情况,如下图所示:

        综上,2个CPU核心的使用率求和约为150%,这与预期相符合。在旧版v1.13之前不支持--cpus参数,限制容器CPU使用率可以通过参数--cpu-quota和--cpu-period配合实现,两个参数的单位均为微秒,其中--cpu-period是指调度周期,默认值为100ms;CPU使用率上限可通过公式--cpus = --cpu-quota --cpu-period 求得,在宿主机上与容器CPU使用率限制策略对应的cgroup配置位于/sys/fs/cgroup/cpu/docker路径下,目录结构如下所示:
          [root@localhost ~]# ls -l sys/fs/cgroup/cpu/docker/  
          total 0
          drwxr-xr-x 2 root root 0 Jun 21 18:15 7aad8ce1cd05f7a72d5cdac59bd1666e68b1eb831842ac2cd54a9c3fd3485798
          -rw-r--r-- 1 root root 0 Jun 20 11:37 cgroup.clone_children
          --w--w--w- 1 root root 0 Jun 20 11:37 cgroup.event_control
          -rw-r--r-- 1 root root 0 Jun 20 11:37 cgroup.procs
          -r--r--r-- 1 root root 0 Jun 20 11:37 cpuacct.stat
          -rw-r--r-- 1 root root 0 Jun 20 11:37 cpuacct.usage
          -r--r--r-- 1 root root 0 Jun 20 11:37 cpuacct.usage_percpu
          -rw-r--r-- 1 root root 0 Jun 20 11:37 cpu.cfs_period_us
          -rw-r--r-- 1 root root 0 Jun 20 11:37 cpu.cfs_quota_us
          -rw-r--r-- 1 root root 0 Jun 20 11:37 cpu.rt_period_us
          -rw-r--r-- 1 root root 0 Jun 20 11:37 cpu.rt_runtime_us
          -rw-r--r-- 1 root root 0 Jun 20 11:37 cpu.shares
          -r--r--r-- 1 root root 0 Jun 20 11:37 cpu.stat
          -rw-r--r-- 1 root root 0 Jun 20 11:37 notify_on_release
          -rw-r--r-- 1 root root 0 Jun 20 11:37 tasks
          在当前目录中,名为 7aad8ce1cd05f7a72d5cdac59bd1666e68b1eb831842ac2cd54a9c3fd3485798的目录是正在运行的stress服务容器的容器ID,该目录中存在文件cpu.cfs_period_us和cpu_quota_us,数值如下所示:
            [root@localhost 7aad8ce1cd05f7a72d5cdac59bd1666e68b1eb831842ac2cd54a9c3fd3485798]# cat cpu.cfs_period_us 
            100000
            [root@localhost 7aad8ce1cd05f7a72d5cdac59bd1666e68b1eb831842ac2cd54a9c3fd3485798]# cat cpu.cfs_quota_us
            150000

            以上两个值相除得到的结果正好为1.5,与容器启动时指定的—cpus=1.5一致。



             2.2 

             限制使用指定CPU

            通过参数--cpuset-cpus可限制容器始终在某一个或某几个CPU上运行,这是非常有意义的,因为当前的CPU多核架构中,每个CPU核心都有自己的独占缓存,如果频繁的将进程调度在不同的核心上执行,会带来更多的开销;以下在容器启动过程中,设置了--cpuset-cpus选项,并指定容器在运行时使用的CPU的编号为"1"。

              [root@localhost ~]# docker run --rm --cpuset-cpus="1"  -it polinux/stress stress --cpu 1

              综上,编号为1的CPU,也就是第二个CPU被打满,但却未影响到其他的核心使用,这与预期相符合。

               2.3 

               CPU权重设置

              默认情况下,所有容器对于CPU使用率的占比都是相同的,通过参数--cpu-shares可以设置CPU利用率权重,默认值为1024且该配置只会在CPU繁忙时才能体现出来,--cpu-shares是一个相对值,每个容器实际能占用多少CPU取决于系统上运行的容器数量。

              若在单核CPU宿主机上启动3个容器,第一个容器的--cpu-shares值为1024,另外两个容器的--cpu-shares值为512,当这三个容器中的进程尝试使用100%CPU时,权重设置为1024的容器会使用50%的CPU,计算公式为 1024 (1024 + 512 + 512 ) = 0.5; 另外两个权重为512的容器会使用25%的CPU,计算公式为: 512 (1024 + 512 + 512) = 0.25; 若在此时又添加了一个权重为1024的容器,那么两个权重设置为1024的容器将占用的CPU百分比为33%,计算公式为 1024 (1024 + 1024 + 512 + 512) = 0.33;而另外两个权重设置为512的容器将占用的CPU百分比为16.5%,计算公式为 512 (1024 + 1024 + 512 + 512);简言之,容器CPU使用率占比的计算方法就是将所有容器设置的权重值相加作为分母,每个容器的权重作为分子,相除结果即为CPU的利用率百分比,根据以上描述场景,测试CPU使用率占比,启动三个容器:

                [root@localhost ~]# docker run --rm  --cpu-shares=1024  --cpuset-cpus=1  -d polinux/stress stress   --cpu 1
                [root@localhost ~]# docker run --rm  --cpu-shares=512   --cpuset-cpus=1  -d polinux/stress stress   --cpu 1
                [root@localhost ~]# docker run --rm  --cpu-shares=512   --cpuset-cpus=1  -d polinux/stress stress   --cpu 1
                以上三个容器在运行时,争用编号为1的CPU,查看三个容器的CPU利用率情况:

                综上,CPU2被打满,运行在其上的三个容器对CPU的占用情况分别为50%、25%、25%,与预期相符合。 若再启动一个容器,且设置的权重为1024,再次观察CPU占用情况:
                  [root@localhost ~]# docker run --rm  --cpu-shares=1024  --cpuset-cpus=1  -d polinux/stress stress   --cpu 1

                  综上,CPU2仍处于被打满状态,此时运行在其上的容器对CPU的占用情况分别为:33%、33%、16.5%、16.5,与预期相符合。

                  下面再看多核CPU的场景,已知当前宿主机的核数为2,其上运行了两个容器,一个---cpu-shares参数值为1024、另一个设置为512,此时每个容器都能占用一个CPU,且占用情况均为100%,因为当前的CPU资源是足够的,启动容器如下所示:

                    [root@localhost ~]#  docker run --rm  --cpu-shares=1024  -d polinux/stress stress  --cpu 1 
                    [root@localhost ~]#  docker run --rm  --cpu-shares=512  -d polinux/stress stress   --cpu 1 
                    观察CPU占用情况:



                    若再启动一个容器,设置权重为1024,再次观察CPU占用情况:
                      [root@localhost ~]#  docker run --rm  --cpu-shares=1024  -d polinux/stress stress   --cpu 1

                      综上可见,每个容器对于CPU的占用情况约为80%、80%、40%,为什么是这个数值呢?若是在单核情况下,CPU占比计算公式为 1024 (1024 + 1024 + 512) 或 512 (1024 + 1024 + 512)  ,分别为40%、20%、20%,在双核情况下,以上百分比均需乘以2。

                       3 

                      内存资源限制

                      Docker在创建容器时所支持的内存限制选项包括:

                      -- memory:设置内存的使用限额

                      -- memory-swap:设置内存+swap的使用限额

                      对于以上两个内存限制参数,通常有四种设置方式:

                      情况1:不设置

                      若不设置--memory和--memory-swap,那么容器可以耗尽宿主机上的所有内存和swap空间;需要注意的是,如果容器耗尽宿主机的内存和swap空间后,容器会因为内存溢出(OOM)而被宿主机杀死,启动容器命令如下:

                        [root@localhost ~]# docker run --rm -it  polinux/stress stress --vm 1 --vm-bytes 8192M 

                        综上,在启动容器过程中没有设置--memory与--memory-swap参数,所以容器将耗尽宿主机上所有的内存与swap空间,已知当前宿主机的物理内存大小为4G、swap空间大小为2G,而stress容器将要占用8G内存空间,那必然会导致内存溢出,容器将自动终止。


                        情况2:设置memory不设置memory-swap
                        在这种情况下,若将--memory参数设置为x,则容器使用的内存大小为x,能使用的交换分区也为x,因为在默认情况下容器的交换分区大小与内存大小是相同的,此时位于容器中的应用程序最终能够占用的内存空间为2x。通过选项--memory可以限制容器能够使用的最大内存,启动容器命令如下:
                          [root@localhost ~]# docker run --rm -it -m 300M polinux/stress stress --vm 1 --vm-bytes 500M
                          综上,容器的内存空间被限制为300M、swap空间也为300M,那么容器总共可以使用600M的内存,此时stress应用程序产生了500M的内存空间占用,所以不会有问题。查看容器状态,验证内存资源占用情况,如下图所示:

                          综上可见,限制容器使用300M内存,现已全部用完。通过命令htop查看宿主机内存使用情况,如下图所示:

                          综上,VIRT是进程虚拟内存的大小,所以此处为500M;RES是实际分配的物理内存数量,这个值就在300M左右上下浮动。若stress应用程序产生的内存空间占用大于600,则会触发OOM,启动容器命令如下:
                            [root@localhost ~]# docker run --rm -it -m 300M polinux/stress stress --vm 1 --vm-bytes 700M

                            若想要在容器的内存占用已达最大限制时即内存与swap空间均被占满时,不被宿主机终止,应在启动容器时附加参数—oom-kill-disable,示例如下:
                              [root@localhost ~]#  docker run --rm -it -m 300M --oom-kill-disable polinux/stress stress --vm 1 --vm-bytes 700M
                              附加了--oom-kill-disable参数后,即便容器发生内存溢出,也不会被宿主机终止。


                              情况3:设置memory=x且memory-swap=y且y>x

                              在此情况下,x值是容器能使用的内存大小,y值是容器能使用的内存与swap空间的总和,所以y必须大于x;  容器swap分区大小=y-x,启动容器命令如下

                                [root@localhost ~]#  docker run --rm -it  --memory 300M --memory-swap 320M  polinux/stress stress --vm 1 --vm-bytes 310M
                                通过启动参数可知,容器能使用的最大内存为300M,swap空间大小为20M;stress容器启动后,将占用310M内存空间,如下图所示:

                                综上,VIRT是进程虚拟内存的大小,所以此处为310M;RES是实际分配的物理内存数量,这个值就在300M左右上下浮动。此时stress应用服务占用300M的物理内存和10M的SWAP分区空间,若stress应用程序产生的内存空间占用大于320M,容器会发生内存溢出而被宿主机终止。

                                情况4:设置memowry=x且memory-swap=-1
                                在此情况下,x是容器能使用的内存大小,y值为-1表示容器使用的swap空间与宿主机相同。宿主机的swap分区情况如下所示:

                                可见,宿主机的swap分区大小为2G,启动stress测试内存占用情况,命令如下:
                                  [root@localhost ~]#  docker run --rm -it  --memory 300M --memory-swap -1  polinux/stress stress --vm 1 --vm-bytes 2300M
                                  在容器启动过程中--memory参数为300M,memory-swap值为-1,已知宿主机swap分区大小为2G,那么容器能使用的最大内存空间为2348M,此时通过stress进程产生2300M的内存占用,效果如下图所示:

                                  综上,VIRT是进程虚拟内存的大小,所以此处为2300M;RES是实际分配的物理内存数量,这个值就在300M左右上下浮动,swap分区占用情况如下图所示:



                                   6 

                                  结束语

                                  如果本文可以对您的工作学习带来帮助,请扫描左侧赞赏码以资鼓励作者;文章勘误请扫描右侧二维码联系作者。


                                  文章转载自稻壳编程,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                  评论