【實戰】Docker容器資源管理

hxw2ljj發表於2015-11-20
本文作者是Red Hat的軟體工程師 - ,這篇文章詳細介紹了Docker容器的資源管理,總共分了三大部分:CPU、記憶體以及磁碟IO。作者透過實踐舉例給讀者勾勒出一幅清晰明瞭的Docker資源管理的畫卷。 在這篇部落格文章中,我想談談Docker容器資源管理的話題。我們往往不清楚它是怎樣工作的以及我們能做什麼不能做什麼。我希望你讀完這篇部落格文章之後,可以幫助你更容易理解有關Docker資源管理的內容。 

注意:我們假設你在一個systemd可用的系統上執行了 Docker。如果你是使用RHEL/CentOS 7+或Fedora 19+,systemd肯定可用,但是請注意在不同的systemd版本之間可能配置選項會有變化。有疑問時,使用你所工作的系統的systemd的幫助說明。

1. 基礎概念

Docker使用 cgroups 歸類執行在容器中的程式。這使你可以管理一組程式的資源,可想而知,這是非常寶貴的。 

如果你執行一個作業系統,其使用 管理服務。每個程式(不僅僅是容器中的程式)都將被放入一個cgroups樹中。如果你執行systemd-cgls命令,你自己可以看到這個結構: 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ systemd-cgls
├─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
├─machine.slice
│ └─machine-qemu\x2drhel7.scope
│   └─29898 /usr/bin/qemu-system-x86_64 -machine accel=kvm -name rhel7 -S -machine pc-i440fx-1.6,accel=kvm,usb=off -cpu SandyBridge -m 2048
├─system.slice
│ ├─avahi-daemon.service
│ │ ├─ 905 avahi-daemon: running [mistress.local
│ │ └─1055 avahi-daemon: chroot helpe
│ ├─dbus.service
│ │ └─890 /bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation
│ ├─firewalld.service
│ │ └─887 /usr/bin/python -Es /usr/sbin/firewalld --nofork --nopid
│ ├─lvm2-lvmetad.service
│ │ └─512 /usr/sbin/lvmetad -f
│ ├─abrtd.service
│ │ └─909 /usr/sbin/abrtd -d -s
│ ├─wpa_supplicant.service
│ │ └─1289 /usr/sbin/wpa_supplicant -u -f /var/log/wpa_supplicant.log -c /etc/wpa_supplicant/wpa_supplicant.conf -u -f /var/log/wpa_supplica
│ ├─systemd-machined.service
│ │ └─29899 /usr/lib/systemd/systemd-machined
 
[SNIP]

當我們想管理資源的時候,這個方法提供了很大的靈活性,因為我們可以分別管理每個組。儘管這篇部落格文章著重於容器,但同樣的原則也適用於其他的程式。 

注意:如果你想知道更多關於systemd的知識,我強烈推薦RHEL 7的 Resource Management and Linux Containers Guide(資源管理與Linux容器指南)

1.1 測試說明

在我的例子中,我將使用stress工具來幫助我生成容器的一些負載,因此我可以真實地看到資源的申請限制。我使用這個Dockerfile建立了一個名為stress的定製的Docker映象: 
?
1
2
3
FROM fedora:latest
RUN yum -y install stress && yum clean all
ENTRYPOINT ["stress"]

1.2 關於資源報告工具的說明

你使用這個工具來報告cgroups不知道的使用情況如top,/proc/meminfo等等。這意味著你將報告關於這臺主機的資訊即使是它們在容器內執行。關於這個主題,我發現了 來自於Fabio Kung。讀一讀它吧。 

因此,我們能做什麼? 

如果你想快速發現在該主機上使用最多資源的容器(或是最近的所有systemd服務),我推薦systemd-cgtop命令: 
?
1
2
3
4
5
6
7
8
9
10
$ systemd-cgtop
Path                                    Tasks   %CPU   Memory  Input/s Output/s
 
/                                         226   13.0     6.7G        -        -
/system.slice                              47    2.2    16.0M        -        -
/system.slice/gdm.service                   2    2.1        -        -        -
/system.slice/rngd.service                  1    0.0        -        -        -
/system.slice/NetworkManager.service        2      -        -        -        -
 
[SNIP]

這個工具能快速預覽在系統上正在執行的東西。但是如果你想得到關係使用情況的更詳細資訊(比如,你需要建立一個好看的圖表),你可以去分析/sys/fs/cgroup/…目錄。我將向你展示去哪裡能找到我們將討論的每個有用的資原始檔(看下面的CGroups fs段落)。 

2. CPU

Docker能夠指定(透過執行 )給一個容器的可用的CPU分配值。這是一個相對權重,與實際的處理速度無關。事實上,沒有辦法說一個容器只可以獲得1Ghz CPU。請記住。 

每個新的容器預設的將有1024CPU配額,當我們單獨講它的時候,這個值並不意味著什麼。但是如果我們啟動兩個容器並且兩個都將使用 100%CPU,CPU時間將在這兩個容器之間平均分割,因為它們兩個都有同樣的CPU配額(為了簡單起見,假設沒有任何其他程式在執行)。 

如果我們設定容器的CPU配額是512,相對於另外一個容器,它將使用一半的CPU時間。但這不意味著它僅僅能使用一半的CPU時間。如果另外一個容器(1024配額的)是空閒的 - 我們的容器將被允許使用100%CPU。這是需要注意的另外一件事。 

限制僅僅當它們應該被執行的時候才會強制執行。CGroups不限制程式預先使用(比如,不允許它們更快地執行即使它們有空餘資源)。相反的,它提供了它儘可能提供的以及它僅僅在必需的時候限制(比如,當太多的程式同時大量地使用CPU)。 

當然,這很難說清楚(我想說的是這不可能說清楚的)多少資源應該被分配給你的程式。這實際取決於其他程式的行為以及多少配額被分配給它們。 

2.1 示例:管理一個容器的 CPU 分配

正如我在前面提到的,你可以使用-c開關來分配給執行在容器中的所有程式的配額值。 

因為在我的機器上我有4核,我將使用4壓測: 
?
1
2
$ docker run -it --rm stress --cpu 4
stress: info: [1] dispatching hogs: 4 cpu, 0 io, 0 vm, 0 hdd

如果你想以相同的方式啟動兩個容器,兩個都將使用 50% 左右的 CPU。但是當我們修改其中一個容器的 shares 時,將發生什麼? 
?
1
2
$ docker run -it --rm -c 512 stress --cpu 4
stress: info: [1] dispatching hogs: 4 cpu, 0 io, 0 vm, 0 hdd

 

正如你所看到的,CPU在兩個容器之間是以這樣的方式分割了,第一個容器使用了60%的CPU,另外一個使用了30%左右。這似乎是預期的結果。 

注意:丟失的約10%CPU被GNOME、Chrome和我的音樂播放器使用了。

2.2 Attaching containers to cores

除了限制 CPU配額,我們可以做更多的事情:我們可以把容器的程式固定到特定的處理器(core)。為了做到這個,我們使用docker run命令的--cpuset開關。 

為了允許僅在第一個核上執行: 
?
1
docker run -it --rm --cpuset=0 stress --cpu 1

為了允許僅在前兩個核上執行: 
?
1
docker run -it --rm --cpuset=0,1 stress --cpu 2

你當然可以混合使用選項--cpuset與-c。 

注意:Share enforcement僅僅發生在當程式執行在相同的核上的時候。這意味著如果你把一個容器固定在第一個核,而把另外一個容器固定在另外一個核,兩個都將 使用各自核的 100%,即使它們有不同的CPU配額設定(再次宣告,我假設僅僅有兩個容器執行在主機上)。

2.3 變更一個正在執行的容器的配額

有可能改變一個正在執行的容器的配額(當然或是任何其他程式)。你可以直接與cgroups檔案系統互動,但是因為我們有systemds,我們可以透過它來為我們管理。 

為了這個目的,我們將使用systemctl命令和set-property引數。使用docker run命令新的容器將有一個systemd scope,自動分配到其內的所有程式都將被執行。為了改變容器中所有程式的CPU配額,我們僅僅需要在scope內改變它,像這樣: 
?
1
$ sudo systemctl set-property docker-4be96b853089bc6044b29cb873cac460b429cfcbdd0e877c0868eb2a901dbf80.scope CPUShares=512


注意:新增--runtime暫時地改變設定。否則,當主機重啟後,這個設定會被記住。
把預設值從1024變更到512。你可以看到下面的結果。這一變化發生在記錄中。請注意CPU使用率。在systemd-cgtop中100%意味著滿額使用了一核,並且這是正確的,因為我繫結了兩個容器在相同的核上。 

注意:為了顯示所有的屬性,你可以使用systemctl show docker-4be96b853089bc6044b29cb873cac460b429cfcbdd0e877c0868eb2a901dbf80.scope命令。想要列出所有可用的屬性,請檢視man systemd.resource-control。

2.4 CGroups fs

你可以在/sys/fs/cgroup/cpu/system.slice/docker-$FULL_CONTAINER_ID.scope/下發現指定容器的關於CPU的所有資訊,例如: 
?
1
2
3
4
5
$ ls /sys/fs/cgroup/cpu/system.slice/docker-6935854d444d78abe52d629cb9d680334751a0cda82e11d2610e041d77a62b3f.scope/
cgroup.clone_children  cpuacct.usage_percpu  cpu.rt_runtime_us  tasks
cgroup.procs           cpu.cfs_period_us     cpu.shares
cpuacct.stat           cpu.cfs_quota_us      cpu.stat
cpuacct.usage          cpu.rt_period_us      notify_on_release

注意:關於這些檔案的更多資訊,請移步 RHEL Resource Management Guide檢視。

2.5 概要重述

需要記住的一些事項: 
  1. CPU配額僅僅是一個數字 - 與CPU速度無關
  2. 新容器預設有1024配額
  3. 在一臺空閒主機上,低配額的容器仍可以使用100%的CPU
  4. 如果你想,你可以把容器固定到一個指定核

3. 記憶體

現在讓我看下記憶體限制。 

第一件事需要注意的是,預設一個容器可以使用主機上的所有記憶體。 

如果你想為容器中的所有程式限制記憶體,使用docker run命令的-m開關即可。你可以使用bytes值定義它的值或是新增字尾(k,m或g)。 

3.1 示例:管理一個容器的記憶體分配

你可以像這樣使用-m開關: 
?
1
$ docker run -it --rm -m 128m fedora bash

為了顯示限制的實際情況,我將再次使用我的stress映象。考慮一下的執行: 
?
1
2
$ docker run -it --rm -m 128m stress --vm 1 --vm-bytes 128M --vm-hang 0
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd

stress工具將建立一個程式,並嘗試分配128MB記憶體給它。它工作的很好,但是如果我們使用的比實際分配給容器的更多的記憶體會發生什麼? 
?
1
2
$ docker run -it --rm -m 128m stress --vm 1 --vm-bytes 200M --vm-hang 0
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd

它照樣正常工作,是不是很奇怪?是的,我同意。 

我們可以在 找到解釋(cgroups的Docker介面)。我們可以看到原始碼中預設的memory.memsw.limit_in_bytes值是被設定成我們指定的記憶體引數的兩倍,當我們啟動一個容器的時候。memory.memsw.limit_in_bytes參數列達了什麼?它是 memory和swap的總和。這意味著Docker將分配給容器-m記憶體值以及-mswap值。 

當前的Docker介面不允許我們指定(或者是完全禁用它)多少的swap應該被使用,所以我們現在需要一起使用它。 

有了以上資訊,我們可以再次執行我們的示例。這次我們嘗試分配超過我們分配的兩倍記憶體。它將使用所有的記憶體和所有的 swap,然後玩完了。 
?
1
2
3
4
5
6
$ docker run -it --rm -m 128m stress --vm 1 --vm-bytes 260M --vm-hang 0
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: FAIL: [1] (415) <-- worker 6 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 5s

如果你嘗試再次分配比如 250MB(--vm-bytes 250M),它將會很好的工作。 

警告:如果你不透過-m開關限制記憶體,swap也被不會被限制。(這在技術上是不正確的; 這有限度, 但是它設定的值在我們當前執行的系統是不可達的。 例如在我的筆記本上 16GB 的記憶體值是 18446744073709551615,這是 ~18.5 exabytes…)
不限制記憶體將導致一個容器可以很容易使得整個系統不穩定的問題。因此請記住要一直使用-m引數。( 或者是使用MemoryLimit屬性。) 

3.2 CGroups fs

你可以在/sys/fs/cgroup/memory/system.slice/docker-$FULL_CONTAINER_ID.scope/下面發現關於記憶體的所有資訊,例如: 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ls /sys/fs/cgroup/memory/system.slice/docker-48db72d492307799d8b3e37a48627af464d19895601f18a82702116b097e8396.scope/
cgroup.clone_children               memory.memsw.failcnt
cgroup.event_control                memory.memsw.limit_in_bytes
cgroup.procs                        memory.memsw.max_usage_in_bytes
memory.failcnt                      memory.memsw.usage_in_bytes
memory.force_empty                  memory.move_charge_at_immigrate
memory.kmem.failcnt                 memory.numa_stat
memory.kmem.limit_in_bytes          memory.oom_control
memory.kmem.max_usage_in_bytes      memory.pressure_level
memory.kmem.slabinfo                memory.soft_limit_in_bytes
memory.kmem.tcp.failcnt             memory.stat
memory.kmem.tcp.limit_in_bytes      memory.swappiness
memory.kmem.tcp.max_usage_in_bytes  memory.usage_in_bytes
memory.kmem.tcp.usage_in_bytes      memory.use_hierarchy
memory.kmem.usage_in_bytes          notify_on_release
memory.limit_in_bytes               tasks
memory.max_usage_in_bytes

注意:想了解關於這些檔案的更多資訊,請移步到 RHEL資源管理指南,記憶體篇

4. 塊裝置(磁碟)

對於塊裝置,我們可以考慮兩種不同型別的限制: 
  1. 讀寫速率
  2. 可寫的空間 (定額)

第一個是非常容易實施的,但是第二個仍未解決。 

注意:我假設你正在使用 devicemapper storage作為Docker的後端。使用其他後端,任何事情都將不是確定的。

4.1 限制讀寫速率

Docker沒有提供任何的開關來定義我們可以多快的讀或是寫資料到塊裝置中。但是CGroups內建了。它甚至透過BlockIO*屬性暴露給了systemd。 

為了限制讀寫速率我們可以分別使用BlockIOReadBandwidth和BlockIOWriteBandwidth屬性。 

預設頻寬是沒有被限制的。這意味著一個容器可以使得硬碟”發熱“,特別是它開始使用swap的時候。 

4.2 示例:限制寫速率

讓我測試沒有執行限制的速率: 
?
1
2
3
4
5
6
7
8
9
$ docker run -it --rm --name block-device-test fedora bash
bash-4.2# time $(dd if=/dev/zero of=testfile0 bs=1000 count=100000 && sync)
100000+0 records in
100000+0 records out
100000000 bytes (100 MB) copied, 0.202718 s, 493 MB/s
 
real  0m3.838s
user  0m0.018s
sys   0m0.213s

花費了 3.8秒來寫入100MB資料,大概是26MB/s。讓我們嘗試限制一點磁碟的速率。 

為了能調整容器可用的頻寬,我們需要明確的知道容器掛載的檔案系統在哪裡。當你在容器裡面執行mount命令的時候,你可以發現它,發現裝置掛載在root檔案系統: 
?
1
2
3
4
5
6
$ mount
/dev/mapper/docker-253:0-3408580-d2115072c442b0453b3df3b16e8366ac9fd3defd4cecd182317a6f195dab3b88 on / type ext4 (rw,relatime,context="system_u:object_r:svirt_sandbox_file_t:s0:c447,c990",discard,stripe=16,data=ordered)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev type tmpfs (rw,nosuid,context="system_u:object_r:svirt_sandbox_file_t:s0:c447,c990",mode=755)
 
[SNIP]

在我們的示例中是/dev/mapper/docker-253:0-3408580-d2115072c442b0453b3df3b16e8366ac9fd3defd4cecd182317a6f195dab3b88。 

你也可以使用nsenter得到這個值,像這樣: 
?
1
2
$ sudo /usr/bin/nsenter --target $(docker inspect -f '{{ .State.Pid }}' $CONTAINER_ID) --mount --uts --ipc --net --pid mount | head -1 | awk '{ print $1 }'
/dev/mapper/docker-253:0-3408580-d2115072c442b0453b3df3b16e8366ac9fd3defd4cecd182317a6f195dab3b88

現在我們可以改變BlockIOWriteBandwidth屬性的值,像這樣: 
?
1
$ sudo systemctl set-property --runtime docker-d2115072c442b0453b3df3b16e8366ac9fd3defd4cecd182317a6f195dab3b88.scope "BlockIOWriteBandwidth=/dev/mapper/docker-253:0-3408580-d2115072c442b0453b3df3b16e8366ac9fd3defd4cecd182317a6f195dab3b88 10M"

這應該把磁碟的速率限制在10MB/s,讓我們再次執行dd: 
?
1
2
3
4
5
6
7
8
bash-4.2# time $(dd if=/dev/zero of=testfile0 bs=1000 count=100000 && sync)
100000+0 records in
100000+0 records out
100000000 bytes (100 MB) copied, 0.229776 s, 435 MB/s
 
real  0m10.428s
user  0m0.012s
sys   0m0.276s

可以看到,它花費了10s來把100MB資料寫入磁碟,因此這速率是 10MB/s。 

注意:你可以使用BlockIOReadBandwidth屬性同樣的限制你的讀速率

4.3 限制磁碟空間

正如我前面提到的,這是艱難的話題,預設你每個容器有10GB的空間,有時候它太大了,有時候不能滿足我們所有的資料放在這裡。不幸的是,為此我們什麼都不能做。 

我們能做的唯一的事情就是改變新容器的預設值,如果你認為一些其他的值(比如 5GB)更適合你的情況,你可以透過指定Docker daemon的--storage-opt來實現,像這樣: 
?
1
docker -d --storage-opt dm.basesize=5G

你可以 調整一些其他的東西,但是請記住,這需要在後面重起你的Docker daemon,想了解更多的資訊,請看 這裡(譯註:這是Docker 1.2版本的README)。 

4.4 CGroups fs

你可以在/sys/fs/cgroup/blkio/system.slice/docker-$FULL_CONTAINER_ID.scope/目錄下發現關於塊裝置的所有資訊,例如: 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ls /sys/fs/cgroup/blkio/system.slice/docker-48db72d492307799d8b3e37a48627af464d19895601f18a82702116b097e8396.scope/
blkio.io_merged                   blkio.sectors_recursive
blkio.io_merged_recursive         blkio.throttle.io_service_bytes
blkio.io_queued                   blkio.throttle.io_serviced
blkio.io_queued_recursive         blkio.throttle.read_bps_device
blkio.io_service_bytes            blkio.throttle.read_iops_device
blkio.io_service_bytes_recursive  blkio.throttle.write_bps_device
blkio.io_serviced                 blkio.throttle.write_iops_device
blkio.io_serviced_recursive       blkio.time
blkio.io_service_time             blkio.time_recursive
blkio.io_service_time_recursive   blkio.weight
blkio.io_wait_time                blkio.weight_device
blkio.io_wait_time_recursive      cgroup.clone_children
blkio.leaf_weight                 cgroup.procs
blkio.leaf_weight_device          notify_on_release
blkio.reset_stats                 tasks
blkio.sectors

注意:想了解關於這些檔案的更多資訊,請移步到 RHEL Resource Management Guide, blkio section

總結

正如你所看到的,Docker容器的資源管理是可行的。甚至非常簡單。唯一的事情就是我們不能為磁碟使用設定一個定額,Docker開源專案裡有一個的  -- 跟蹤它並且評論。 

希望你發現我的文章對你有用。Docker萬歲! 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/18796236/viewspace-1841968/,如需轉載,請註明出處,否則將追究法律責任。

相關文章