Docker資源限制實現——cgroup


摘要

  隨着Docker技術被越來越多的個人、企業所接受,其用途也越來越廣泛。Docker資源管理包含對CPU、內存、IO等資源的限制,但大部分Docker使用者在使用資源管理接口時往往還比較模糊。
  本文將嘗試介紹Docker資源管理背后的Cgroups機制,並且列舉主要的資源管理接口對應的Cgroups接口,讓Docker使用者對資源管理更加清晰。

一、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的寫速率,數字需要使用正整數。

二、Docker資源管理原理——Cgroups子系統介紹

  Cgroups是control groups的縮寫,最初由google的工程師提出,后來被整合進Linux內核。Cgroups是Linux內核提供的一種可以限制、記錄、隔離進程組(process groups)所使用的物理資源(如:CPU、內存、IO等)的機制。Cgroups由7個子系統組成:分別是cpuset、cpu、cpuacct、blkio、devices、freezer、memory。不同類型資源的分配和管理是由各個cgroup子系統負責完成的。

  在 /sys/fs/cgroup/子系統名稱/docker 目錄中為每個容器創建一個 cgroup 目錄,並且以容器長ID命名,如下 cpu 資源系統,目錄中包含所有與 cpu 相關的 cgroup 配置

[vagrant@localhost docker]$ pwd
/sys/fs/cgroup/cpu/docker
[vagrant@localhost docker]$ ll
total 0
drwxr-xr-x. 2 root root 0 Feb 27 02:37 02c8442f5e65909bc1f7ecfc1596f1474d7d9d1dcd3e2cd34d059cabc578d2c5

[vagrant@localhost docker]$ cd 02c8442f5e65909bc1f7ecfc1596f1474d7d9d1dcd3e2cd34d059cabc578d2c5/
[vagrant@localhost 02c8442f5e65909bc1f7ecfc1596f1474d7d9d1dcd3e2cd34d059cabc578d2c5]$ ll
total 0
-rw-r--r--. 1 root root 0 Feb 27 02:37 cgroup.clone_children
--w--w--w-. 1 root root 0 Feb 27 02:37 cgroup.event_control
-rw-r--r--. 1 root root 0 Feb 27 02:37 cgroup.procs
-r--r--r--. 1 root root 0 Feb 27 02:37 cpuacct.stat
-rw-r--r--. 1 root root 0 Feb 27 02:37 cpuacct.usage
-r--r--r--. 1 root root 0 Feb 27 02:37 cpuacct.usage_percpu
-rw-r--r--. 1 root root 0 Feb 27 02:37 cpu.cfs_period_us
-rw-r--r--. 1 root root 0 Feb 27 02:37 cpu.cfs_quota_us
-rw-r--r--. 1 root root 0 Feb 27 02:37 cpu.rt_period_us
-rw-r--r--. 1 root root 0 Feb 27 02:37 cpu.rt_runtime_us
-rw-r--r--. 1 root root 0 Feb 27 02:37 cpu.shares
-r--r--r--. 1 root root 0 Feb 27 02:37 cpu.stat
-rw-r--r--. 1 root root 0 Feb 27 02:37 notify_on_release
-rw-r--r--. 1 root root 0 Feb 27 02:37 tasks

 

  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=
    • 讀取內存對應的cgroups文件
      [vagrant@localhost cgroup]$ docker run -ti --rm -m 200M centos bash -c "cat /sys/fs/cgroup/memory/memory.limit_in_bytes"                   
      209715200

      當內存限制在200M時,cgoups的文件數值為 209715200,單位為字節,剛好等於200M。其中,--rm 表示退出之后刪除創建的容器。

    • 讀取交換內存對應的cgroups文件
      [vagrant@localhost cgroup]$ docker run -ti --rm -m 200M --memory-swap=300M centos bash -c "cat /sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"
      314572800

      當內存限制在300M時,cgoups的文件數值為 314572800,單位為字節,剛好等於300M。

    其余的,以此類推。

  2. 使用stress鏡像學習如何為容器分配內存    

     centos-stress-source:1.0.2鏡像已在上一篇中創建完成,這邊直接使用,鏈接

    • 分配的內存比指定的內存和交換內存小時,執行正常,不斷釋放與分配
      [vagrant@localhost cgroup]$  docker run -ti --rm -m 200M --memory-swap=300M centos-stress-source:1.0.2 --vm 1 --vm-bytes 280M
      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 [5] forked
      stress: dbug: [5] allocating 293601280 bytes ...
      stress: dbug: [5] touching bytes in strides of 4096 bytes ...
      stress: dbug: [5] freed 293601280 bytes
      stress: dbug: [5] allocating 293601280 bytes ...
      stress: dbug: [5] touching bytes in strides of 4096 bytes ...
    • 分配的內存比指定的內存和交換內存大時
      [vagrant@localhost cgroup]$  docker run -ti --rm -m 200M --memory-swap=300M centos-stress-source:1.0.2 --vm 1 --vm-bytes 310M
      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 [5] forked
      stress: dbug: [5] allocating 325058560 bytes ...
      stress: dbug: [5] touching bytes in strides of 4096 bytes ...
      stress: FAIL: [1] (415) <-- worker 5 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 1s

      分配的內存試圖超過300M時,stress線程報錯,容器強行退出。 

    • 不指定swap-memory時,默認swap的值確認

      1、指定 -m 內存值為100M
      [vagrant@localhost tmp]$ docker run -ti --rm -m 100M centos bash -c "cat /sys/fs/cgroup/memory/memory.limit_in_bytes && cat /sys/fs/cgroup/memory/memory.memsw.limit_in_bytes" 
      104857600
      209715200
      可以看到上述情況,在不指定 memory-swap 大小的情況下,默認取memory的兩倍值, 即 200M。

      2、當一方的指定值為-1時,表示無限大
      [vagrant@localhost tmp]$docker run -ti --rm -m 100M --memory-swap -1 centos bash -c "cat /sys/fs/cgroup/memory/memory.limit_in_bytes && cat /sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"    
      104857600
      9223372036854771712

      可以看到,memory-swap為 無限大。


      3、memory 和 memory-swap的關系
      memory-swap = memory + swap
      所以,當memory-swap值小於memory設定值的時候,會報錯如下
      [vagrant@localhost tmp]$ docker run -ti --rm -m 200M --memory-swap 100M centos 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'.


  3. cpu子系統

子系統常用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=""

 

    • 設置cpu權重,容器競爭cpu資源時是才起作用,但容器情況下可以使用到全部的容器資源
      啟動容器 container_a,設置 cpu share = 1024
      [vagrant@localhost tmp]$ docker run --rm --name container_a -it -c 1024 centos-stress-source:1.0.2  --cpu 1
      stress: info: [1] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd
      stress: dbug: [1] using backoff sleep of 3000us
      stress: dbug: [1] --> hogcpu worker 1 [5] forked

      啟動容器 container_b,設置 cpu share = 512

      [vagrant@localhost ~]$ docker run --rm --name container_b -it -c 512 centos-stress-source:1.0.2  --cpu 1 
      stress: info: [1] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd
      stress: dbug: [1] using backoff sleep of 3000us
      stress: dbug: [1] --> hogcpu worker 1 [5] forked

      在host中執行 top 查看 cpu 的使用情況

      [vagrant@localhost ~]$ top
      top - 12:09:26 up 10:39, 3 users, load average: 2.07, 0.82, 0.40 Tasks: 101 total, 3 running, 98 sleeping, 0 stopped, 0 zombie %Cpu(s): 99.7 us, 0.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 500108 total, 160592 free, 107168 used, 232348 buff/cache KiB Swap: 1572860 total, 1370868 free, 201992 used. 337508 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 7996 root 20 0 7264 96 0 R 66.3 0.0 2:37.42 stress 8142 root 20 0 7264 96 0 R 33.3 0.0 0:20.98 stress

      以上,可以看到 container_a 占用的 cpu 資源是 container_b 的兩倍。
      暫停container_a 可以發現數據如下

      [vagrant@localhost ~]$ docker pause container_a
      container_a
      [vagrant@localhost ~]$ top
      top - 12:11:08 up 10:41,  3 users,  load average: 2.02, 1.18, 0.58
      Tasks: 101 total,   2 running,  99 sleeping,   0 stopped,   0 zombie
      %Cpu(s):100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
      KiB Mem :   500108 total,   160468 free,   107288 used,   232352 buff/cache
      KiB Swap:  1572860 total,  1370868 free,   201992 used.   337384 avail Mem 
      
        PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                         
       8142 root      20   0    7264     96      0 R 99.3  0.0   1:01.57 stress                          
       3276 root      20   0  644028  28028   9832 S  0.3  5.6   1:41.57 dockerd         

      可以看到,當 在container_a空閑的時候,container_b 能夠用滿整個 cpu。

    • PS. 需要退出當前測試時,需要直接ctrl+c,或者 直接執行 docker stop <container name>,執行過 docker pause <container name>的,只能執行stop退出。
  • 4. Block IO

    子系統常用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=""
    • 磁盤的讀寫權重

      docker 可通過設置權重、限制 bps (每秒讀寫的數據量)和 iops(每秒 IO 的次數) 的方式控制容器讀寫磁盤的帶寬。

      1、限制讀寫 IO 為50M/s,則最終的讀寫速度會在50M左右。
      [vagrant@localhost tmp]$ docker  run -it --rm --device-write-bps /dev/sda:50M centos               [root@540f9b91405e /]# time dd if=/dev/zero of=test.out bs=1M count=800 oflag=direct
      800+0 records in
      800+0 records out
      838860800 bytes (839 MB) copied, 15.9115 s, 52.7 MB/s
      
      real    0m15.913s
      user    0m0.002s
      sys     0m0.321s

      2、不限制 IO

      [vagrant@localhost tmp]$ docker  run -it --rm centos
      [root@77f63fec8b34 /]# time dd if=/dev/zero of=test.out bs=1M count=800 oflag=direct
      800+0 records in
      800+0 records out
      838860800 bytes (839 MB) copied, 1.6477 s, 509 MB/s
      
      real    0m1.649s
      user    0m0.000s
      sys     0m0.302s

      PS.注意oflag=direct,需要指定IO方式,目前BLKIO限額只對direct(不使用文件緩存)生效。因為容器的文件系統在 host /dev/sda 上,所以在容器中寫文件,相當於對 host /dev/sda 進行寫操作

    • BLKIO限額具有競爭資源的情況,與 cpu 配額一樣

      1、是 container_b 的BLKIO 優先級是 container_a 的兩倍

       設置 container_a 的 --blkio-weight 300
      [vagrant@localhost tmp]$ docker  run -it --rm --name container_a --blkio-weight 300 centos 
      [root@7cd7ff76edb4 /]# time dd if=/dev/zero of=test.out bs=1M count=800 oflag=direct
      800+0 records in
      800+0 records out
      838860800 bytes (839 MB) copied, 3.12988 s, 268 MB/s
      
      real    0m3.131s
      user    0m0.001s
      sys     0m0.335s

      設置 container_b 的 --blkio-weight 600

      [vagrant@localhost ~]$ docker  run -it --rm --name container_b --blkio-weight 600 centos 
      [root@89aa5264b9d1 /]# time dd if=/dev/zero of=test.out bs=1M count=800 oflag=direct
      800+0 records in
      800+0 records out
      838860800 bytes (839 MB) copied, 2.10906 s, 398 MB/s
      
      real    0m2.111s
      user    0m0.001s
      sys     0m0.296s

      ps. 由於收到執行命令沒有辦法做到同時IO,所以讀寫速度上的比例並沒有嚴格的1:2。

      引用
        [1] Docker資源管理探秘:Docker背后的內核Cgroups機制 
        [2] 《每天5分鍾玩轉Docker容器技術》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM