理解Docker(4):Docker 容器使用 cgroups 限制資源使用
來源:http://www.cnblogs.com/sammyliu/p/5886833.html
上一篇文章將到 Docker 容器使用 linux namespace 來隔離其執行環境,使得容器中的程序看起來就像愛一個獨立環境中執行一樣。但是,光有執行環境隔離還不夠,因為這些程序還是可以不受限制地使用系統資源,比如網路、磁碟、CPU以及記憶體 等。關於其目的,一方面,是為了防止它佔用了太多的資源而影響到其它程序;另一方面,在系統資源耗盡的時候,linux 核心會觸發 OOM,這會讓一些被殺掉的程序成了無辜的替死鬼。因此,為了讓容器中的程序更加可控,Docker 使用 Linux cgroups 來限制容器中的程序允許使用的系統資源。
1. 基礎知識:Linux control groups
1.1 概念
Linux Cgroup 可讓您為系統中所運行任務(進程)的用戶定義組群分配資源 — 比如 CPU 時間、系統內存、網絡帶寬或者這些資源的組合。您可以監控您配置的 cgroup,拒絕 cgroup 訪問某些資源,甚至在運行的系統中動態配置您的 cgroup。所以,可以將 controll groups 理解為 controller (system resource) (for) (process)groups,也就是是說它以一組程序為目標進行系統資源分配和控制。
它主要提供瞭如下功能:
- Resource limitation: 限制資源使用,比如記憶體使用上限以及檔案系統的快取限制。
- Prioritization: 優先順序控制,比如:CPU利用和磁碟IO吞吐。
- Accounting: 一些審計或一些統計,主要目的是為了計費。
- Control: 掛起程序,恢復執行程序。
使用 cgroup,系統管理員可更具體地控制對系統資源的分配、優先順序、拒絕、管理和監控。可更好地根據任務和用戶分配硬件資源,提高總體效率。
在實踐中,系統管理員一般會利用CGroup做下面這些事(有點像為某個虛擬機器分配資源似的):
- 隔離一個程序集合(比如:nginx的所有程序),並限制他們所消費的資源,比如繫結CPU的核。
- 為這組程序分配其足夠使用的記憶體
- 為這組程序分配相應的網路頻寬和磁碟儲存限制
- 限制訪問某些裝置(通過設定裝置的白名單)
Linux 系統中,一切皆檔案。Linux 也將 cgroups 實現成了檔案系統,方便使用者使用。在我的 Ubuntu 14.04 測試環境中:
[email protected]:/home/sammy# mount -t cgroup cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,relatime,cpuset) cgroup on /sys/fs/cgroup/cpu type cgroup (rw,relatime,cpu) systemd on /sys/fs/cgroup/systemd type cgroup (rw,noexec,nosuid,nodev,none,name=systemd) [email protected]:/home/sammy# lssubsys -m cpuset /sys/fs/cgroup/cpuset cpu /sys/fs/cgroup/cpu cpuacct /sys/fs/cgroup/cpuacct memory /sys/fs/cgroup/memory devices /sys/fs/cgroup/devices freezer /sys/fs/cgroup/freezer blkio /sys/fs/cgroup/blkio perf_event /sys/fs/cgroup/perf_event hugetlb /sys/fs/cgroup/hugetlb [email protected]:/home/sammy# ls /sys/fs/cgroup/ -l total 0 drwxr-xr-x 3 root root 0 Sep 18 21:46 blkio drwxr-xr-x 3 root root 0 Sep 18 21:46 cpu drwxr-xr-x 3 root root 0 Sep 18 21:46 cpuacct drwxr-xr-x 3 root root 0 Sep 18 21:46 cpuset drwxr-xr-x 3 root root 0 Sep 18 21:46 devices drwxr-xr-x 3 root root 0 Sep 18 21:46 freezer drwxr-xr-x 3 root root 0 Sep 18 21:46 hugetlb drwxr-xr-x 3 root root 0 Sep 18 21:46 memory drwxr-xr-x 3 root root 0 Sep 18 21:46 perf_event drwxr-xr-x 3 root root 0 Sep 18 21:46 systemd
我們看到 /sys/fs/cgroup 目錄中有若干個子目錄,我們可以認為這些都是受 cgroups 控制的資源以及這些資源的資訊。
- 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系統進行限制,這是一個大頁檔案系統。
預設的話,在 Ubuntu 系統中,你可能看不到 net_cls 和 net_prio 目錄,它們需要你手工做 mount:
[email protected]:/sys/fs/cgroup# modprobe cls_cgroup [email protected]:/sys/fs/cgroup# mkdir net_cls [email protected]:/sys/fs/cgroup# mount -t cgroup -o net_cls none net_cls [email protected]:/sys/fs/cgroup# modprobe netprio_cgroup [email protected]:/sys/fs/cgroup# mkdir net_prio [email protected]:/sys/fs/cgroup# mount -t cgroup -o net_prio none net_prio [email protected]:/sys/fs/cgroup# ls net_prio/cgroup.clone_children cgroup.procs net_prio.ifpriomap notify_on_release tasks cgroup.event_control cgroup.sane_behavior net_prio.prioidx release_agent [email protected]:/sys/fs/cgroup# ls net_cls/ cgroup.clone_children cgroup.event_control cgroup.procs cgroup.sane_behavior net_cls.classid notify_on_release release_agent tasks
1.2 實驗
1.2.1 通過 cgroups 限制程序的 CPU
寫一段最簡單的 C 程式:
int main(void) { int i = 0; for(;;) i++; return 0; }
編譯,執行,發現它佔用的 CPU 幾乎到了 100%:
top - 22:43:02 up 1:14, 3 users, load average: 0.24, 0.06, 0.06 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 2304 root 20 0 4188 356 276 R 99.6 0.0 0:11.77 hello
接下來我們做如下操作:
[email protected]:/home/sammy/c# mkdir /sys/fs/cgroup/cpu/hello [email protected]:/home/sammy/c# cd /sys/fs/cgroup/cpu/hello [email protected]:/sys/fs/cgroup/cpu/hello# ls cgroup.clone_children cgroup.procs cpu.cfs_quota_us cpu.stat tasks cgroup.event_control cpu.cfs_period_us cpu.shares notify_on_release [email protected]:/sys/fs/cgroup/cpu/hello# cat cpu.cfs_quota_us -1 [email protected]:/sys/fs/cgroup/cpu/hello# echo 20000 > cpu.cfs_quota_us [email protected]:/sys/fs/cgroup/cpu/hello# cat cpu.cfs_quota_us 20000 [email protected]:/sys/fs/cgroup/cpu/hello# echo 2428 > tasks
然後再來看看這個程序的 CPU 佔用情況:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 2428 root 20 0 4188 356 276 R 19.9 0.0 0:46.03 hello
它佔用的 CPU 幾乎就是 20%,也就是我們預設的閾值。這說明我們通過上面的步驟,成功地將這個程序執行所佔用的 CPU 資源限制在某個閾值之內了。
如果此時再啟動另一個 hello 程序並將其 id 加入 tasks 檔案,則兩個程序會共享設定的 CPU 限制:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 2428 root 20 0 4188 356 276 R 10.0 0.0 285:39.54 hello 12526 root 20 0 4188 356 276 R 10.0 0.0 0:25.09 hello
1.2.2 通過 cgroups 限制程序的 Memory
同樣地,我們針對它佔用的記憶體做如下操作:
[email protected]:/sys/fs/cgroup/memory# mkdir hello [email protected]:/sys/fs/cgroup/memory# cd hello/ [email protected]:/sys/fs/cgroup/memory/hello# cat memory.limit_in_bytes 18446744073709551615 [email protected]:/sys/fs/cgroup/memory/hello# echo 64k > memory.limit_in_bytes [email protected]:/sys/fs/cgroup/memory/hello# echo 2428 > tasks [email protected]:/sys/fs/cgroup/memory/hello#
上面的步驟會把程序 2428 說佔用的記憶體閾值設定為 64K。超過的話,它會被殺掉。
1.2.3 限制程序的 I/O
執行命令:
sudo dd if=/dev/sda1 of=/dev/null
通過 iotop 命令看 IO (此時磁碟在快速轉動),此時其寫速度為 242M/s:
TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND 2555 be/4 root 242.60 M/s 0.00 B/s 0.00 % 61.66 % dd if=/dev/sda1 of=/dev/null
接著做下面的操作:
[email protected]:/home/sammy# mkdir /sys/fs/cgroup/blkio/io [email protected]:/home/sammy# cd /sys/fs/cgroup/blkio/io [email protected]:/sys/fs/cgroup/blkio/io# ls -l /dev/sda1 brw-rw---- 1 root disk 8, 1 Sep 18 21:46 /dev/sda1 [email protected]:/sys/fs/cgroup/blkio/io# echo '8:0 1048576' > /sys/fs/cgroup/blkio/io/blkio.throttle.read_bps_device [email protected]:/sys/fs/cgroup/blkio/io# echo 2725 > /sys/fs/cgroup/blkio/io/tasks
結果,這個程序的IO 速度就被限制在 1Mb/s 之內了:
TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND 2555 be/4 root 990.44 K/s 0.00 B/s 0.00 % 96.29 % dd if=/dev/sda1 of=/dev/null
1.3 術語
cgroups 的術語包括:
- 任務(Tasks):就是系統的一個程序。
- 控制組(Control Group):一組按照某種標準劃分的程序,比如官方文件中的Professor和Student,或是WWW和System之類的,其表示了某程序組。Cgroups中的資源控制都是以控制組為單位實現。一個程序可以加入到某個控制組。而資源的限制是定義在這個組上,就像上面示例中我用的 hello 一樣。簡單點說,cgroup的呈現就是一個目錄帶一系列的可配置檔案。
- 層級(Hierarchy):控制組可以組織成hierarchical的形式,既一顆控制組的樹(目錄結構)。控制組樹上的子節點繼承父結點的屬性。簡單點說,hierarchy就是在一個或多個子系統上的cgroups目錄樹。
- 子系統(Subsystem):一個子系統就是一個資源控制器,比如CPU子系統就是控制CPU時間分配的一個控制器。子系統必須附加到一個層級上才能起作用,一個子系統附加到某個層級以後,這個層級上的所有控制族群都受到這個子系統的控制。Cgroup的子系統可以有很多,也在不斷增加中。
2. Docker 對 cgroups 的使用
2.1 預設情況
預設情況下,Docker 啟動一個容器後,會在 /sys/fs/cgroup 目錄下的各個資源目錄下生成以容器 ID 為名字的目錄(group),比如:
/sys/fs/cgroup/cpu/docker/03dd196f415276375f754d51ce29b418b170bd92d88c5e420d6901c32f93dc14
此時 cpu.cfs_quota_us 的內容為 -1,表示預設情況下並沒有限制容器的 CPU 使用。在容器被 stopped 後,該目錄被刪除。
執行命令 docker run -d --name web41 --cpu-quota 25000 --cpu-period 100 --cpu-shares 30 training/webapp python app.py 啟動一個新的容器,結果:
[email protected]:/sys/fs/cgroup/cpu/docker/06bd180cd340f8288c18e8f0e01ade66d066058dd053ef46161eb682ab69ec24# cat cpu.cfs_quota_us 25000 [email protected]:/sys/fs/cgroup/cpu/docker/06bd180cd340f8288c18e8f0e01ade66d066058dd053ef46161eb682ab69ec24# cat tasks 3704 [email protected]:/sys/fs/cgroup/cpu/docker/06bd180cd340f8288c18e8f0e01ade66d066058dd053ef46161eb682ab69ec24# cat cpu.cfs_period_us 2000
Docker 會將容器中的程序的 ID 加入到各個資源對應的 tasks 檔案中。表示 Docker 也是以上面的機制來使用 cgroups 對容器的 CPU 使用進行限制。
相似地,可以通過 docker run 中 mem 相關的引數對容器的記憶體使用進行限制:
--cpuset-mems string MEMs in which to allow execution (0-3, 0,1) --kernel-memory string Kernel memory limit -m, --memory string Memory limit --memory-reservation string Memory soft limit --memory-swap string Swap limit equal to memory plus swap: '-1' to enable unlimited swap --memory-swappiness int Tune container memory swappiness (0 to 100) (default -1)
比如 docker run -d --name web42 --blkio-weight 100 --memory 10M --cpu-quota 25000 --cpu-period 2000 --cpu-shares 30 training/webapp python app.py:
[email protected]:/sys/fs/cgroup/memory/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410# cat memory.limit_in_bytes 10485760
[email protected]:/sys/fs/cgroup/blkio/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410# cat blkio.weight
100
目前 docker 已經幾乎支援了所有的 cgroups 資源,可以限制容器對包括 network,device,cpu 和 memory 在內的資源的使用,比如:
[email protected]:/sys/fs/cgroup# find -iname ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./net_prio/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./net_cls/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./systemd/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./hugetlb/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./perf_event/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./blkio/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./freezer/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./devices/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./memory/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./cpuacct/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./cpu/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./cpuset/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
2.2 net_cls
net_cls 和 tc 一起使用可用於限制程序發出的網路包所使用的網路頻寬。當使用 cgroups network controll net_cls 後,指定程序發出的所有網路包都會被加一個 tag,然後就可以使用其他工具比如 iptables 或者 traffic controller (TC)來根據網路包上的 tag 進行流量控制。關於 TC 的文件,網上很多,這裡不再贅述,只是用一個簡單的例子來加以說明。
關於 classid,它的格式是 0xAAAABBBB,其中,AAAA 是十六進位制的主ID(major number),BBBB 是十六進位制的次ID(minor number)。因此,0X10001 表示 10:1,而 0x00010001 表示 1:!。
(1)首先在host 的網絡卡 eth0 上做如下設定:
tc qdisc del dev eth0 root #刪除已有的規則
tc qdisc add dev eth0 root handle 10: htb default 12
tc class add dev eth0 parent 10: classid 10:1 htb rate 1500kbit ceil 1500kbit burst 10k #限速
tc filter add dev eth0 protocol ip parent 10:0 prio 1 u32 match ip protocol 1 0xff flowid 10:1 #只處理 ping 引數的網路包
其結果是:
- 在網絡卡 eth0 上建立了一個 HTB root 佇列,hangle 10: 表示佇列控制代碼也就是major number 為 10
- 建立一個分類 10:1,限制它的出發網路頻寬為 80 kbit (千位元每秒)
- 建立一個分類器,將 eth0 上 IP IMCP 協議 的 major ID 為 10 的 prio 為 1 的網路流量都分類到 10:1 類別
(2)啟動容器
容器啟動後,其 init 程序在host 上的 PID 就被加入到 tasks 檔案中了:
[email protected]:/sys/fs/cgroup/net_cls/docker/ff8d9715b7e11a5a69446ff1e3fde3770078e32a7d8f7c1cb35d51c75768fe33# ps -ef | grep 10047 231072 10047 10013 1 07:08 ? 00:00:00 python app.py
設定 net_cls classid:
echo 0x100001 > net_cls.classid
再在容器啟動一個 ping 程序,其 ID 也被加入到 tasks 檔案中了。
(3)檢視tc 情況: tc -s -d class show dev eth0
Every 2.0s: tc -s class ls dev eth0 Wed Sep 21 04:07:56 2016
class htb 10:1 root prio 0 rate 1500Kbit ceil 1500Kbit burst 10Kb cburst 1599b
Sent 17836 bytes 182 pkt (dropped 0, overlimits 0 requeues 0)
rate 0bit 0pps backlog 0b 0p requeues 0
lended: 182 borrowed: 0 giants: 0
tokens: 845161 ctokens: 125161
我們可以看到 tc 已經在處理 ping 程序產生的資料包了。再來看一下 net_cls 和 ts 合作的限速效果:
10488 bytes from 192.168.1.1: icmp_seq=35 ttl=63 time=12.7 ms 10488 bytes from 192.168.1.1: icmp_seq=36 ttl=63 time=15.2 ms 10488 bytes from 192.168.1.1: icmp_seq=37 ttl=63 time=4805 ms 10488 bytes from 192.168.1.1: icmp_seq=38 ttl=63 time=9543 ms
其中:
- 後兩條說使用的 tc class 規則是 tc class add dev eth0 parent 10: classid 10:1 htb rate 1500kbit ceil 15kbit burst 10k
- 前兩條所使用的 tc class 規則是 tc class add dev eth0 parent 10: classid 10:1 htb rate 1500kbit ceil 10Mbit burst 10k
3. Docker run 命令中 cgroups 相關命令
block IO: --blkio-weight value Block IO (relative weight), between 10 and 1000 --blkio-weight-device value Block IO weight (relative device weight) (default []) --cgroup-parent string Optional parent cgroup for the container CPU: --cpu-percent int CPU percent (Windows only) --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) --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) Device: --device value Add a host device to the container (default []) --device-read-bps value Limit read rate (bytes per second) from a device (default []) --device-read-iops value Limit read rate (IO per second) from a device (default []) --device-write-bps value Limit write rate (bytes per second) to a device (default []) --device-write-iops value Limit write rate (IO per second) to a device (default []) Memory: --kernel-memory string Kernel memory limit -m, --memory string Memory limit --memory-reservation string Memory soft limit --memory-swap string Swap limit equal to memory plus swap: '-1' to enable unlimited swap --memory-swappiness int Tune container memory swappiness (0 to 100) (default -1)
一些說明:
1. cgroup 只能限制 CPU 的使用,而不能保證CPU的使用。也就是說, 使用 cpuset-cpus,可以讓容器在指定的CPU或者核上執行,但是不能確保它獨佔這些CPU;cpu-shares 是個相對值,只有在CPU不夠用的時候才其作用。也就是說,當CPU夠用的時候,每個容器會分到足夠的CPU;不夠用的時候,會按照指定的比重在多個容器之間分配CPU。
2. 對記憶體來說,cgroups 可以限制容器最多使用的記憶體。使用 -m 引數可以設定最多可以使用的記憶體。