Docker資源限制與Cgroups
一、Linux control groups
簡介
Linux CGroup全稱Linux Control Group, 是Linux核心的一個功能,用來限制,控制與分離一個程序組群的資源(如CPU、記憶體、磁碟輸入輸出等)。這個專案最早是由Google的工程師在2006年發起(主要是Paul Menage和Rohit Seth),最早的名稱為程序容器(process containers)。在2007年時,因為在Linux核心中,容器(container)這個名詞太過廣泛,為避免混亂,被重新命名為cgroup,並且被合併到2.6.24版的核心中。
主要提供瞭如下功能:
- Resource limitation: 限制資源使用,比如記憶體使用上限以及檔案系統的快取限制。
- Prioritization: 優先順序控制,比如:CPU利用和磁碟IO吞吐。
- Accounting: 一些審計或一些統計,主要目的是為了計費。
- Control: 掛起程序,恢復執行程序。
CGroup的術語
任務(Tasks):就是系統的一個程序。
控制組(Control Group):一組按照某種標準劃分的程序,其表示了某程序組,Cgroups中的資源控制都是以控制組為單位實現,一個程序可以加入到某個控制組。而資源的限制是定義在這個組上,簡單點說,cgroup的呈現就是一個目錄帶一系列的可配置檔案。
層級(Hierarchy):控制組可以組織成hierarchical的形式,既一顆控制組的樹(目錄結構)。控制組樹上的子節點繼承父結點的屬性。簡單點說,hierarchy就是在一個或多個子系統上的cgroups目錄樹。
子系統(Subsystem):一個子系統就是一個資源控制器,比如CPU子系統就是控制CPU時間分配的一個控制器。子系統必須附加到一個層級上才能起作用,一個子系統附加到某個層級以後,這個層級上的所有控制族群都受到這個子系統的控制。Cgroup的子系統可以有很多,也在不斷增加中。以下為核心3.10+支援的子系統(可以通過 ls /sys/fs/cgroup 檢視到):
-
blkio — 這個子系統為塊裝置設定輸入/輸出限制,比如物理裝置(磁碟,固態硬碟,USB 等等)。
-
cpu — 這個子系統使用排程程式提供對 CPU 的 cgroup 任務訪問。
-
cpuacct — 這個子系統自動生成 cgroup 中任務所使用的 CPU 報告。
-
cpuset — 這個子系統為 cgroup 中的任務分配獨立 CPU(在多核系統)和記憶體節點。
-
devices — 這個子系統可允許或者拒絕 cgroup 中的任務訪問裝置。
-
freezer — 這個子系統掛起或者恢復 cgroup 中的任務。
-
memory — 這個子系統設定 cgroup 中任務使用的記憶體限制,並自動生成記憶體資源使用報告。
-
net_cls — 這個子系統使用等級識別符(classid)標記網路資料包,可允許 Linux 流量控制程式(tc)識別從具體 cgroup 中生成的資料包。
-
net_prio — 這個子系統用來設計網路流量的優先順序
-
hugetlb — 這個子系統主要針對於HugeTLB系統進行限制,這是一個大頁檔案系統。
操作介面
在linux系統中一皆檔案,當然對CGroup的介面操作也是通過檔案來實現的,我們可以通過mount命令檢視其掛載目錄:
以上目錄都可以是限制的物件,也就是對應的術語中的各個子系統。例如檢視這裡的CPU限制目錄:
如果你熟悉CPU管理的話,cfs_period 和 cfs_quota兩個可能不會陌生,這兩個引數需要組合使用,可以用來限制程序在長度為cfs_period 的一段時間內,只能被分配到總量為cfs_quota 的 CPU 時間。以下示例將會演示如何限制。
限制CPU
限制CPU的使用需要在子系統目錄(/sys/fs/cgroup/cpu)下建立目錄,這裡建立一個cpu_limit_demo目錄:
可以看到當我們在CPU子系統下建立目錄時候,其對應的限制配置檔案會自動進行建立,現在在後臺寫個死迴圈跑滿CPU:
此時使用top命令檢視pid為10069的程序CPU使用狀況:
如圖可見,此時該程序使用的CPU1已經是100%,即%Cpu :100.0 us,我們先來檢視我們剛才建立的cpu_limit_demo目錄下這兩個引數預設值是多少:

cpu.cfs_quota_us值為-1代表沒有任何限制,cpu.cfs_period_us 則是預設的 100 ms,即100000us,下面將向cpu_limit_demo控制組的cpu.cfs_quota_us檔案寫入50ms即50000us,這表示在100ms週期內,cpu最多使用%50,同時將該程序的pid號為10069寫入對應的tasks檔案,表示對那個程序限制:
於是pid為10069的程序cpu就被限制成了%Cpu :50.0 us,此時利用top在此檢視cpu使用情況(top後按1可看到每個邏輯cpu具體使用):
以上可以看到此時pid10069的程序使用率已經變成了50%了,說明限制生效了。同樣的道理,在/sys/fs/cgroup下的子系統都可以限制不通的資源使用如Block IO、Memory等。
限制記憶體
首先在 /sys/fs/cgroup/memory 下新建一個名為 limit_memory_demo 的 cgroup:
mkdir /sys/fs/cgroup/memory/limit_memory_demo
限制該 cgroup 的記憶體使用量為 500 MB:
# 實體記憶體500M(下面單位Byte) MB並且不使用swap echo 524288000 > /sys/fs/cgroup/memory/limit_memory/memory.limit_in_bytes echo 0 > /sys/fs/cgroup/memory/limit_memory/memory.swappiness
最後將需要限制的程序號的pid寫入task檔案就可以了:
echo [pid] > /sys/fs/cgroup/memory/limit_memory_demo/tasks
限制磁碟IO
使用dd寫到null裝置:
[root@app51 blkio]# dd if=/dev/sda of=/dev/nullbs=1M
使用iotop(安裝使用yum install -y iotop)檢視io速率,此時讀的速率為2.17G/s
建立一個blkio(塊裝置IO)的cgroup,並檢視裝置號,然後將pid和限制的裝置以及速度寫入檔案:
然後在觀察dd命令的程序:10178,如圖顯示該程序讀的的速率已經變成了3.8M/s了
注:8:0 是裝置號,你可以通過ls -l /dev/sda1檢視。
可以簡寫為以下步驟:
mkdir /sys/fs/cgroup/blkio/ echo '8:0 1048576'> /sys/fs/cgroup/blkio/limit_io/blkio.throttle.read_bps_device echo [pid] > /sys/fs/cgroup/blkio/limit_io/tasks
二、Docker中資源限制原理
在瞭解了Cgroup對資源的限制方法,再來理解Docker中的資源限制其實就變的容易了。預設情況下,Docker會在需要限制的子系統下建立一個目錄為docker的控制組如下:
當容器執行後,會在這些目錄生成以容器ID為目錄的子目錄用於限制的容器資源。例如,以cpu為例,docker run啟動引數中--cpu-quota 引數可以指定預設時間內使用的cpu:
[root@app51 docker]# docker run -d --name nginx-c1 --cpu-quota 50000nginx:latest e9432a513e4bed0a744a29a8eaba2b27d9e40efabfe479d19d32f9558888ed29 [root@app51 docker]#
此時我們檢視cpu對應的容器資源限制:
[root@app51 docker]# cd /sys/fs/cgroup/cpu/docker/ [root@app51 docker]# cat e9432a513e4bed0a744a29a8eaba2b27d9e40efabfe479d19d32f9558888ed29/cpu.cfs_quota_us 50000 [root@app51 docker]# cat e9432a513e4bed0a744a29a8eaba2b27d9e40efabfe479d19d32f9558888ed29/tasks 10561 10598
從上面結果我們能看出,docker run的啟動限制引數值,被注入到了Cgroups的cpu控制組的對應配置檔案中了。同時還有看到有兩個程序同時被限制了,這是因為我們啟動的nginx,nginx的主程序會啟動多個子程序,使用ps -ef可以檢視:
[root@app51 docker]# ps -ef |grep 10561 root10561 105440 16:32 ?00:00:00 nginx: master process nginx -g daemon off; 10110598 105610 16:32 ?00:00:00 nginx: worker process root10614 101790 16:38 pts/200:00:00 grep --color=auto 10561 [root@app51 docker]#
不難看到,其中主程序為10561(nginx master),子程序為10598(nginx worker)。以上則是docker的資源限制原理。Docker對資源限制主要是CPU和記憶體,其中還有很多引數可以使用,以下將會介紹docker中CPU和MEMERY限制引數。
三、CPU限制引數
預設情況下,容器的資源是不受限制的,宿主機提供多少資源,容器就可以使用多少資源,如果不對容器做資源限制,很有可能一個或幾個容器把宿主機的資源耗盡,而導致應用或者服務不可用。所以對容器資源的限制顯得非常重要,而docker主要提供兩種類別的資源限制:CPU和記憶體,通過docker run 時指定引數實現。cpu限制資源限制有多種維度,以下將詳細介紹。
限制cpu配額
引數通過--cpu-period=<value>和--cpu-quota=<value>共同起作用,即介紹上述Cgroups使用的例子。表示在cpu-period時間(預設100ms)內,可用的cpu配額。
示例:
docker run -d --cpu-period=100000 --cpu-quota=250000 --name test-c1 nginx:latest
限制cpu可用數量
引數通過--cpus=<value>指定,意思限制可用cpu個數,列如--cpus=2.5表示該容器可使用的cpu個數最多是2.5個,這相當與設定了--cpu-period=100000和 --cpu-quota=250000該指令在docker版本1.13以及以上使用。
示例:
[root@app51 ~]# docker run -d --cpus=2 --name test-c2 nginx:latest 5347269d0974e37af843b303124d8799c6f4336a14f61334d21ce9356b1535bc
使用固定的cpu
通過--cpuset-cpus引數指定,表示指定容器執行在某個或某些個固定的cpu上,多個cpu使用逗號隔開。例如四個cpu,0代表第一個cpu,--cpuset-cpus=1,3代表該容器只能執行在第二個或第四個cpu上。檢視cpu可以通過cat /proc/cpuinfo檢視。
示例:
[root@app51 ~]# docker run -d --cpuset-cpus=1,3 --name test-c3 nginx:latest 276056fce04982c2de7969ca309560ce60b0ebf960cf7197808616d65aa112d4
設定CPU比例(權重)
通過引數--cpu-shares指定,值為大於或小於1024的整數(預設1024),代表當主機cpu資源使用緊張時,每個容器所使用的cpu資源的比例(權重)。當有足夠的CPU資源時,所有容器都會根據需要使用盡可能多的CPU。當第一個容器設定該引數的值為2048,第二個容器設定該引數的值為4096,那麼當cpu資源不夠用時候,兩個容器cpu資源使用比例為1:2,
示例:
[root@app51 ~]# docker run -d --cpu-shares=2048 --name test-c4 nginx:latest 578506d61324b38d7a01bf1d2ec87cb5d1ab50276ef6f7b28858f2d2e78b2860 [root@app51 ~]# docker run -d --cpu-shares=4096 --name test-c5 nginx:latest d56a90bf080b70d11d112468348874e48fe4a78d09d98813a0377b34fa382924
四、MEMORY限制引數
記憶體是一種不可壓縮資源,一旦某個程序或者容器中應用記憶體不足就會引起OOM異常(Out Of Memory Exception),這將導致應用不可用,並且在Linux主機上,如果核心檢測到沒有足夠的記憶體來執行重要的系統功能,系統會按照一定優先順序殺死程序來釋放系統記憶體。docker對記憶體的限制分為swap限制和實體記憶體限制,單位可以使用b,k, m,g。
限制最大實體記憶體使用
通過-m或者—memory指定,是硬限制,如果設定此選項,則允許設定的最小值為4m,該引數是最常用引數。
示例:
[root@app51 ~]# docker run -d--memory=512m --name mem-c1 nginx:latest 67b0cb645c401bc6df3235d27d629185870716351396c71dfa3877abbbd377c8
限制swap使用
通過--memory-swap引數指定,不過此引數設定時候,情況比較較多。當容器中的實體記憶體使用不足時候,swap才會被允許使用,所以當--memory引數設定時候,此引數才會有意義:
- --memory-swap 設定為正整數,那麼這兩個--memory和 --memory-swap 必須設定。--memory-swap 表示可以使用的memory 和 swap,並且--memory控制非交換記憶體(實體記憶體)使用的量。列如--memory=300m 和--memory-swap=1g,容器可以使用300m的記憶體和700m(1g - 300m)swap。
- --memory-swap 設定為0,則忽略該設定,並將該值視為未設定。
- --memory-swap 設定為與值相同的值--memory,並且--memory設定為正整數,則容器不能使用swap,可以通過此方法限制容器不使用swap。
- --memory-swap 未設定並--memory 設定,則容器可以使用兩倍於--memory設定的swap,主機容器需要配置有swap。例如,如果設定--memory="300m" 和--memory-swap 未設定,容器可以使用300m的記憶體和600m的swap。
- --memory-swap 設定為-1,則允許容器使用無限制swap,最多可達宿主機系統上可用的數量。
示例:
[root@app51 ~]# docker run -d--memory=512m --memory-swap=512m --name mem-c2 nginx:latest 6b52c015a53be2c3e0e509eea918125a760c1c14df4cc977f05b5b31b83161d5
其他
- --oom-kill-disable :預設情況下,如果發生記憶體不足(OOM)錯誤,核心會終止容器中的程序,如要更改此行為,使用該--oom-kill-disable選項,但是該選項有個前提就是-m選項必須設定。
- --memory-swappiness:預設情況下,容器的核心可以交換出一定比例的匿名頁。--memory-swappiness就是用來設定這個比例的。--memory-swappiness可以設定為從 0 到 100。0 表示關閉匿名頁面交換。100 表示所有的匿名頁都可以交換。預設情況下,如果不適用--memory-swappiness,則該值從父程序繼承而來。
- --memory-reservation:Memory reservation 是一種軟性限制,用於限制實體記憶體的最大用值,它只是確保容器不會長時間佔用超過--memory-reservation限制的記憶體大小。給--memory-reservation設定一個比-m小的值後,雖然容器最多可以使用-m使用的記憶體大小,但在宿主機記憶體資源緊張時,在系統的下次記憶體回收時,系統會回收容器的部分記憶體頁,強迫容器的記憶體佔用回到--memory-reservation設定的值大小。
五、總結
本文簡單介紹了Linux Cgroups以及它對資源限制的使用方法,並提供了簡單的示例。此外還介紹了Docker如何使用該方式對容器進行資源限制,本質上是docker run時將限制引數注入到Cgroups的各個資源限制的配置檔案中,從而達到對容器的資源限制。希望大家對容器的資源限制有一定的理解。
參考: