1. 程式人生 > >docker容器資源配額控制

docker容器資源配額控制

文/ 天雲軟體 容器技術團隊

docker通過cgroup來控制容器使用的資源配額,包括CPU、記憶體、磁碟三大方面,基本覆蓋了常見的資源配額和使用量控制。

cgroup簡介

cgroup是Control Groups的縮寫,是Linux 核心提供的一種可以限制、記錄、隔離程序組所使用的物理資源(如 cpu、memory、磁碟IO等等) 的機制,被LXC、docker等很多專案用於實現程序資源控制。cgroup將任意程序進行分組化管理的 Linux 核心功能。cgroup本身是提供將程序進行分組化管理的功能和介面的基礎結構,I/O 或記憶體的分配控制等具體的資源管理功能是通過這個功能來實現的。這些具體的資源管理功能稱為cgroup子系統,有以下幾大子系統實現:

  1. blkio:設定限制每個塊裝置的輸入輸出控制。例如:磁碟,光碟以及usb等等。
  2. cpu:使用排程程式為cgroup任務提供cpu的訪問。
  3. cpuacct:產生cgroup任務的cpu資源報告。
  4. cpuset:如果是多核心的cpu,這個子系統會為cgroup任務分配單獨的cpu和記憶體。
  5. devices:允許或拒絕cgroup任務對裝置的訪問。
  6. freezer:暫停和恢復cgroup任務。
  7. memory:設定每個cgroup的記憶體限制以及產生記憶體資源報告。
  8. net_cls:標記每個網路包以供cgroup方便使用。
  9. ns:名稱空間子系統。
  10. perf_event:增加了對每group的監測跟蹤的能力,即可以監測屬於某個特定的group的所有執行緒以及執行在特定CPU上的執行緒。

目前docker只是用了其中一部分子系統,實現對資源配額和使用的控制。

可以使用stress工具來測試CPU和記憶體。使用下面的Dockerfile來建立一個基於Ubuntu的stress工具映象。

FROM ubuntu:14.04
RUN apt-get update &&apt-get install stress

CPU資源配額控制

CPU份額控制

docker提供了–cpu-shares引數,在建立容器時指定容器所使用的CPU份額值。使用示例:

使用命令docker run -tid –cpu-shares 100 ubuntu:stress,建立容器,則最終生成的cgroup的cpu份額配置可以下面的檔案中找到:

[email protected]:~# cat /sys/fs/cgroup/cpu/docker/<容器的完整長ID>/cpu.shares
100

cpu-shares的值不能保證可以獲得1個vcpu或者多少GHz的CPU資源,僅僅只是一個彈性的加權值。

預設情況下,每個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週期控制

docker提供了–cpu-period–cpu-quota兩個引數控制容器可以分配到的CPU時鐘週期。–cpu-period是用來指定容器對CPU的使用要在多長時間內做一次重新分配,而–cpu-quota是用來指定在這個週期內,最多可以有多少時間用來跑這個容器。跟–cpu-shares不同的是這種配置是指定一個絕對值,而且沒有彈性在裡面,容器對CPU資源的使用絕對不會超過配置的值。

cpu-periodcpu-quota的單位為微秒(μs)。cpu-period的最小值為1000微秒,最大值為1秒(10^6 μs),預設值為0.1秒(100000 μs)。cpu-quota的值預設為-1,表示不做控制。

舉個例子,如果容器程序需要每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 ubuntu,建立容器,則最終生成的cgroup的cpu週期配置可以下面的檔案中找到:

[email protected]:~# cat /sys/fs/cgroup/cpu/docker/<容器的完整長ID>/cpu.cfs_period_us
100000
[email protected]:~# cat /sys/fs/cgroup/cpu/docker/<容器的完整長ID>/cpu.cfs_quota_us
200000

關於cpu-sharescpu-periodcpu-quota這些配置的詳細介紹,大家可以深入閱讀RedHat文件中。

CPU core控制

對多核CPU的伺服器,docker還可以控制容器執行限定使用哪些cpu核心和記憶體節點,即使用–cpuset-cpus–cpuset-mems引數。對具有NUMA拓撲(具有多CPU、多記憶體節點)的伺服器尤其有用,可以對需要高效能運算的容器進行效能最優的配置。如果伺服器只有一個記憶體節點,則–cpuset-mems的配置基本上不會有明顯效果。

使用示例:

命令docker run -tid –name cpu1 –cpuset-cpus 0-2 ubuntu,表示建立的容器只能用0、1、2這三個核心。最終生成的cgroup的cpu核心配置如下:

[email protected]:~# cat /sys/fs/cgroup/cpuset/docker/<容器的完整長ID>/cpuset.cpus
0-2

通過docker exec <容器ID> taskset -c -p 1(容器內部第一個程序編號一般為1),可以看到容器中程序與CPU核心的繫結關係,可以認為達到了繫結CPU核心的目的。

quota01

CPU配額控制引數的混合使用

當上面這些引數中時,cpu-shares控制只發生在容器競爭同一個核心的時間片時,如果通過cpuset-cpus指定容器A使用核心0,容器B只是用核心1,在主機上只有這兩個容器使用對應核心的情況,它們各自佔用全部的核心資源,cpu-shares沒有明顯效果。

cpu-periodcpu-quota這兩個引數一般聯合使用,在單核情況或者通過cpuset-cpus強制容器使用一個cpu核心的情況下,即使cpu-quota超過cpu-period,也不會使容器使用更多的CPU資源。

cpuset-cpuscpuset-mems只在多核、多記憶體節點上的伺服器上有效,並且必須與實際的物理配置匹配,否則也無法達到資源控制的目的。

在系統具有多個CPU核心的情況下,需要通過cpuset-cpus為容器CPU核心才能比較方便地進行測試。

試用下列命令建立測試用的容器:

docker run -tid –name cpu2 –cpuset-cpus 3 –cpu-shares 512 ubuntu:stress stress -c 10
docker run -tid –name cpu3 –cpuset-cpus 3 –cpu-shares 1024 ubuntu:stress stress -c 10

上面的ubuntu:stress映象安裝了stress工具來測試CPU和記憶體的負載。兩個容器的命令stress -c 10&,這個命令將會給系統一個隨機負載,產生10個程序,每個程序都反覆不停的計算由rand()產生隨機數的平方根,直到資源耗盡。

觀察到宿主機上的CPU試用率如下圖所示,第三個核心的使用率接近100%,並且一批程序的CPU使用率明視訊記憶體在2:1的使用比例的對比:

quota02

容器cpu2的CPU使用如下所示:

quota03

容器cpu3的CPU使用如下圖示:

quota04

分別進入容器後,使用top命令可以明顯地看出容器之間的資源使用對比,並且也達到了繫結CPU核心的目的。

注意:如果使用nsenter之類的工具進入容器,再使用stress -c 10進行測試,就可以發現cpuset-cpus的限制是可以被突破的,從而使stress測試程序使用宿主機的所有CPU核心。這是因為nsenter使用掛載的方式直接進入了容器的名稱空間,突破了名稱空間中的cgroup控制。

記憶體配額控制

和CPU控制一樣,docker也提供了若干引數來控制容器的記憶體使用配額,可以控制容器的swap大小、可用記憶體大小等各種記憶體方面的控制。主要有以下引數:

  • memory-swappiness:控制程序將實體記憶體交換到swap分割槽的傾向,預設係數為60。係數越小,就越傾向於使用實體記憶體。值範圍為0-100。當值為100時,表示儘量使用swap分割槽;當值為0時,表示禁用容器 swap 功能(這點不同於宿主機,宿主機 swappiness 設定為 0 也不保證 swap 不會被使用)。
  • –kernel-memory:核心記憶體,不會被交換到swap上。一般情況下,不建議修改,可以直接參考docker的官方文件。
  • –memory:設定容器使用的最大記憶體上限。預設單位為byte,可以使用K、G、M等帶單位的字串。
  • –memory-reservation:啟用彈性的記憶體共享,當宿主機資源充足時,允許容器儘量多地使用記憶體,當檢測到記憶體競爭或者低記憶體時,強制將容器的記憶體降低到memory-reservation所指定的記憶體大小。按照官方說法,不設定此選項時,有可能出現某些容器長時間佔用大量記憶體,導致效能上的損失。
  • –memory-swap:等於記憶體和swap分割槽大小的總和,設定為-1時,表示swap分割槽的大小是無限的。預設單位為byte,可以使用K、G、M等帶單位的字串。如果–memory-swap的設定值小於–memory的值,則使用預設值,為–memory-swap值的兩倍。

預設情況下,容器可以使用主機上的所有空閒記憶體。

與CPU的cgroups配置類似,docker會自動為容器在目錄/sys/fs/cgroup/memory/docker/<容器的完整長ID>中建立相應cgroup配置檔案,例如下面的檔案:

quota05

這些檔案與docker的相關配置是一一對應的,可以參考RedHat的文件來檢視它們的作用。

記憶體配額控制使用示例

設定容器的記憶體上限,參考命令如下所示:

docker run -tid —name mem1 —memory 128m ubuntu:stress /bin/bash

預設情況下,除了–memory指定的記憶體大小以外,docker還為容器分配了同樣大小的swap分割槽,也就是說,上面的命令創建出的容器實際上最多可以使用256MB記憶體,而不是128MB記憶體。如果需要自定義swap分割槽大小,則可以通過聯合使用–memory–swap引數來實現控制。

對上面的命令建立的容器,可以檢視到在cgroups的配置檔案中,檢視到容器的記憶體大小為128MB (128×1024×1024=134217728B),記憶體和swap加起來大小為256MB (256×1024×1024=268435456B)。

cat /sys/fs/cgroup/memory/docker/<容器的完整ID>/memory.limit_in_bytes
134217728
cat /sys/fs/cgroup/memory/docker/<容器的完整ID>/memory.memsw.limit_in_bytes
268435456

注意:執行上述命令時,命令列可能會輸出下面的警告:

WARNING: Your kernel does not support swap limit capabilities, memory limited without swap.

這是因為主機上預設不啟用cgroup來控制swap分割槽,可以參考,修改grub啟動引數。

在容器中,依次使用下面的stress命令,即可對容器的記憶體進行壓力測試,確認記憶體。

stress –vm 1 –vm-bytes 256M –vm-hang 0 &
stress –vm 1 –vm-bytes 250M –vm-hang 0 &

quota06

可以發現,使用256MB進行壓力測試時,由於超過了記憶體上限(128MB記憶體+128MB swap),程序被OOM殺死。使用250MB進行壓力測試時,程序可以正常執行,並且通過docker stats可以檢視到容器的記憶體已經滿負載了。

quota07

磁碟IO配額控制

相對於CPU和記憶體的配額控制,docker對磁碟IO的控制相對不成熟,大多數都必須在有宿主機裝置的情況下使用。主要包括以下引數:

  • –device-read-bps:限制此裝置上的讀速度(bytes per second),單位可以是kb、mb或者gb。
  • –device-read-iops:通過每秒讀IO次數來限制指定裝置的讀速度。
  • –device-write-bps :限制此裝置上的寫速度(bytes per second),單位可以是kb、mb或者gb。
  • –device-write-iops:通過每秒寫IO次數來限制指定裝置的寫速度。
  • –blkio-weight:容器預設磁碟IO的加權值,有效值範圍為10-100。
  • –blkio-weight-device: 針對特定裝置的IO加權控制。其格式為DEVICE_NAME:WEIGHT

儲存配額控制的相關引數,可以參考,瞭解它們的詳細作用。

磁碟IO配額控制示例

blkio-weight

要使–blkio-weight生效,需要保證IO的排程演算法為CFQ。可以使用下面的方式檢視:

[email protected]:~# cat /sys/block/sda/queue/scheduler
noop [deadline] cfq

使用下面的命令建立兩個–blkio-weight值不同的容器:

docker run -ti –rm –blkio-weight 100 ubuntu:stress
docker run -ti –rm –blkio-weight 1000 ubuntu:stress

在容器中同時執行下面的dd命令,進行測試:

time dd if=/dev/zero of=test.out bs=1M count=1024 oflag=direct

最終輸出如下圖所示:

quota08

device-write-bps

使用下面的命令建立容器,並執行命令驗證寫速度的限制。

docker run -tid –name disk1 –device-write-bps /dev/sda:1mb ubuntu:stress

通過dd來驗證寫速度,輸出如下圖示:

quota09

可以看到容器的寫磁碟速度被成功地限制到了1MB/s。device-read-bps等其他磁碟IO限制引數可以使用類似的方式進行驗證。

容器空間大小限制

在docker使用devicemapper作為儲存驅動時,預設每個容器和映象的最大大小為10G。如果需要調整,可以在daemon啟動引數中,使用dm.basesize來指定,但需要注意的是,修改這個值,不僅僅需要重啟docker daemon服務,還會導致宿主機上的所有本地映象和容器都被清理掉。

使用aufs或者overlay等其他儲存驅動時,沒有這個限制。

~~~以上所有截圖測試環境,宿主機為Ubuntu 14.04.4,docker版本為1.10.3~~~