Docker資源管理探秘:Docker背后的內核Cgroups機制

lynnpino 8年前發布 | 12K 次閱讀 Docker cgroups

隨著Docker技術被越來越多的個人、企業所接受,其用途也越來越廣泛。Docker資源管理包含對CPU、內存、IO等資源的限制,但大部分Docker使用者在使用資源管理接口時往往只知其然而不知其所以然。

本文將介紹Docker資源管理背后的Cgroups機制,并且列舉每一個資源管理接口對應的Cgroups接口,讓Docker使用者對資源管理知其然并且知其所以然。

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

2. Docker資源管理原理——Cgroups子系統介紹

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

下面介紹與docker資源管理接口相關的4個子系統。

2.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=""

2.2 cpu -- 使用調度程序提供對 CPU 的 cgroup 任務訪問。

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

2.3 cpuset -- 為 cgroup 中的任務分配獨立 CPU(在多核系統)和內存節點

子系統常用cgroups接口 描述 對應的docker接口
cgroup/cpuset/cpuset.cpus 允許進程使用的CPU列表(例如:0-4,9)。 --cpuset-cpus=""
cgroup/cpuset/cpuset.mems 允許進程使用的內存節點列表(例如:0-1)。 --cpuset-mems=""

2.4 blkio -- 為塊設備設定輸入/輸出限制,比如物理設備(磁盤、固態硬盤、USB等)

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

3.Docker資源管理接口詳解及應用示例

以下內容針對各資源管理接口做了詳盡的說明。為了加深讀者理解,部分接口附有測試用例。用例中的Docker版本為1.11.0。如果在你的鏡像中stress命令不可用,你可以通過sudo apt-get install stress來安裝stress工具。

3.1 memory子系統

3.1.1 -m, --memory=""

可以限制容器使用的內存量,對應的cgroup文件是cgroup/memory/memory.limit_in_bytes。

取值范圍:大于等于4M

單位:b,k,m,g

在默認情況下,容器可以占用無限量的內存,直至主機內存資源耗盡。

運行如下命令來確認容器內存的資源管理對應的cgroup文件。

$ docker run -it --memory 100M ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.limit_in_bytes"
104857600

可以看到,當內存限定為100M時,對應的cgroup文件數值為104857600,該數值的單位為字節,即104857600字節等于100M。

本機內存環境為:

$ free
          total        used        free      shared  buff/cache   available
Mem:        4050284      254668     3007564      180484      788052     3560532
Swap:             0           0           0

值得注意的是本機目前沒有配置交換分區(swap)。

我們使用stress工具來證明內存限定已經生效。stress是一個壓力工具,如下命令將要在容器內創建一個進程,在該進程中不斷的執行占用內存(malloc)和釋放內存(free)的操作。在理論上如果占用的內存少于限定值,容器會工作正常。注意,如果試圖使用邊界值,即試圖在容器中使用stress工具占用100M內存,這個操作通常會失敗,因為容器中還有其他進程在運行。

$ docker run -ti -m 100M ubuntu:14.04 stress --vm 1 --vm-bytes 50M
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd

當在限定內存為100M的容器中,試圖占用50M的內存時,容器工作正常。

如下所示,當試圖占用超過100M內存時,必然導致容器異常。

$ docker run -ti -m 100M ubuntu:14.04 stress --vm 1 --vm-bytes 101M
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: FAIL: [1] (416) <-- worker 6 got signal 9
stress: WARN: [1] (418) now reaping child worker processes
stress: FAIL: [1] (422) kill error: No such process
stress: FAIL: [1] (452) failed run completed in 0s

注意這種情況是在系統無交換分區(swap)的情況下出現的,如果我們添加了交換分區,情況又會怎樣?首先通過如下命令來添加交換分區(swap)。

$ dd if=/dev/zero of=/tmp/mem.swap bs=1M count=8192
    8192+0 records in
    8192+0 records out
    8589934592 bytes (8.6 GB) copied, 35.2693 s, 244 MB/s
    $ mkswap /tmp/mem.swap
Setting up swapspace version 1, size = 8388604 KiB
no label, UUID=55ea48e9-553d-4013-a2ae-df194f7941ed
$ sudo swapon /tmp/mem.swap 
    swapon: /tmp/mem.swap: insecure permissions 0664, 0600 suggested.
    swapon: /tmp/mem.swap: insecure file owner 1100, 0 (root) suggested.
    $ free -m
              total        used        free      shared  buff/cache   available
Mem:           3955         262          28         176        3665        3463
Swap:          8191           0        8191

之后再次嘗試占用大于限定的內存。

$ docker run -ti -m 100M ubuntu:14.04 stress --vm 1 --vm-bytes 101M
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd

在加入交換分區后容器工作正常,這意味著有部分存儲在內存中的信息被轉移到了交換分區中了。

注意,在實際容器使用場景中,如果不對容器使用內存量加以限制的話,可能導致一個容器會耗盡整個主機內存,從而導致系統不穩定。所以在使用容器時務必對容器內存加以限制。

3.1.2 --memory-swap=""

可以限制容器使用交換分區和內存的總和,對應的cgroup文件是cgroup/memory/memory.memsw.limit_in_bytes。

取值范圍:大于內存限定值

單位:b,k,m,g

運行如下命令來確認容器交換分區的資源管理對應的cgroup文件。

$ docker run -ti -m 300M --memory-swap 1G ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"
1073741824

可以看到,當memory-swap限定為1G時,對應的cgroup文件數值為1073741824,該數值的單位為字節,即1073741824B等于1G。

條件

結果

memory=無窮大, memory-swap=無窮大 (默認條件下) 系統不限定容器對內存和交換分區的使用量,容器能夠使用主機所能提供的所有內存。
memory=L<無窮大, memory-swap=無窮大 (設定memory限定值同時將memory-swap設置為-1) 容器的內存使用量不能超過L,但是交換分區的使用量不受限制(前提是主機支持交換分區)。
memory=L<無窮大, memory-swap=2*L (設定memory限定值而不設置memory-swap值) 容器的內存使用量不能超過L,而內存使用量和交換分區的使用量不能超過兩倍的L。
memory=L<無窮大, memory-swap=S<無窮大, L<=S (設定了memory和memory-swap的限定值) 容器的內存使用量不能超過L,而內存使用量和交換分區的使用量不能超過S。

例子:

以下命令沒有對內存和交換分區進行限制,這意味著容器可以使用無限多的內存和交換分區。

$ docker run -it ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.limit_in_bytes && cat /sys/fs/cgroup/memory/memory.memsw.limit_in_bytes" 
9223372036854771712
9223372036854771712

以下命令只限定了內存使用量300M,而沒有限制交換分區使用量(-1意味著不做限制)。

$ docker run -it -m 300M --memory-swap -1 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.limit_in_bytes && cat /sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"
314572800
9223372036854771712

以下命令僅僅限定了內存使用量,這意味著容器能夠使用300M的內存和300M的交換分區。在默認情況下,總的內存限定值(內存+交換分區)被設置為了內存限定值的兩倍。

$ docker run -it -m 300M ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.limit_in_bytes && cat /sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"
314572800
629145600

以下命令限定了內存和交換分區的使用量,容器可以使用300M的內存和700M的交換分區。

$ docker run -it -m 300M --memory-swap 1G ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.limit_in_bytes && cat /sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"
314572800
1073741824

當memory-swap限定值低于memory限定值時,系統提示"Minimum memoryswap limit should be larger than memory limit"錯誤。

$ docker run -it -m 300M --memory-swap 200M ubuntu:14.04 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'.

如下所示,當嘗試占用的內存數量超過memory-swap值時,容器出現異常。

$ docker run -ti -m 100M --memory-swap 200M ubuntu:14.04 stress --vm 1 --vm-bytes 201M
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: FAIL: [1] (416) <-- worker 7 got signal 9
stress: WARN: [1] (418) now reaping child worker processes
stress: FAIL: [1] (422) kill error: No such process
stress: FAIL: [1] (452) failed run completed in 0s

如下所示,當占用內存值大于memory限定值但小于memory-swap時,容器運行正常。

$ docker run -ti -m 100M --memory-swap 200M ubuntu:memory stress --vm 1 --vm-bytes 180M
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd

3.1.3 --memory-reservation=""

取值范圍:大于等于0的整數

單位:b,k,m,g

對應的cgroup文件是cgroup/memory/memory.soft_limit_in_bytes。

$ docker run -ti --memory-reservation 50M ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.soft_limit_in_bytes"
52428800

通常情況下,容器能夠使用的內存量僅僅由-m/--memory選項限定。如果設置了--memory-reservation選項,當內存使用量超過--memory-reservation選項所設定的值時,系統會強制容器執行回收內存的操作,使得容器內存消耗不會長時間超過--memory-reservation的限定值。

這個限制并不會阻止進程使用超過限額的內存,只是在系統內存不足時,會回收部分內存,使內存使用量向限定值靠攏。

在以下命令中,容器對內存的使用量不會超過500M,這是硬性限制。當內存使用量大于200M而小于500M時,系統會嘗試回收部分內存,使得內存使用量低于200M。

$ docker run -it -m 500M --memory-reservation 200M ubuntu:14.04 bash

在如下命令中,容器使用的內存量不受限制,但容器消耗的內存量不會長時間超過1G,因為當容器內存使用量超過1G時,系統會嘗試回收內存使內存使用量低于1G。

$ docker run -it --memory-reservation 1G ubuntu:14.04 bash

3.1.4 --kernel-memory=""

該接口限制了容器對內核內存的使用,對應的cgroup文件是cgroup/memory/memory.kmem.limit_in_bytes。

$ docker run -ti --kernel-memory 50M ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.kmem.limit_in_bytes"
52428800

如下命令可以限定容器最多可以使用500M的內存。在500M內存中,內核內存最多可以占用50M。

$ docker run -it -m 500M --kernel-memory 50M ubuntu:14.04 bash

如下命令可以限定容器最多可以使用50M的內核內存,而用戶空間的內存使用量不受限制。

$ docker run -it --kernel-memory 50M ubuntu:14.04 bash

3.1.5 --oom-kill-disable=false

當out-of-memory (OOM)發生時,系統會默認殺掉容器進程,如果你不想讓容器進程被殺掉,可以使用該接口。接口對應的cgroup文件是cgroup/memory/memory.oom_control。

當容器試圖使用超過限定大小的內存值時,就會觸發OOM。此時會有兩種情況,第一種情況是當接口--oom-kill-disable=false的時候,容器會被殺掉;第二種情況是當接口--oom-kill-disable=true的時候,容器會被掛起。

以下命令設置了容器的的內存使用限制為20M,將--oom-kill-disable接口的值設置為true。查看該接口對應的cgroup文件,oom_kill_disable的值為1。

$  docker run -m 20m --oom-kill-disable=true ubuntu:14.04 bash -c 'cat /sys/fs/cgroup/memory/memory.oom_control'
oom_kill_disable 1
under_oom 0

oom_kill_disable:取值為0或1,當值為1的時候表示當容器試圖使用超出內存限制時(即20M),容器會掛起。

under_oom:取值為0或1,當值為1的時候,OOM已經出現在容器中。

通過x=a; while true; do x=xx; done命令來耗盡內存并強制觸發OOM,log如下所示。

$ docker run -m 20m --oom-kill-disable=false ubuntu:14.04 bash -c 'x=a; while true; do x=$x$x$x$x; done'
    $ echo $?
137

通過上面的log可以看出,當容器的內存耗盡的時候,容器退出,退出碼為137。因為容器試圖使用超出限定的內存量,系統會觸發OOM,容器會被殺掉,此時under_oom的值為1。我們可以通過系統中cgroup文件(/sys/fs/cgroup/memory/docker/${container_id}/memory.oom_control)查看under_oom的值(oom_kill_disable 1,under_oom 1)。

當--oom-kill-disable=true的時候,容器不會被殺掉,而是被系統掛起。

$ docker run -m 20m --oom-kill-disable=true ubuntu:14.04 bash -c 'x=a; while true; do x=$x$x$x$x; done'

3.1.6 --memory-swappiness=""

該接口可以設定容器使用交換分區的趨勢,取值范圍為0至100的整數(包含0和100)。0表示容器不使用交換分區,100表示容器盡可能多的使用交換分區。對應的cgroup文件是cgroup/memory/memory.swappiness。

$ docker run --memory-swappiness=100 ubuntu:14.04 bash -c 'cat /sys/fs/cgroup/memory/memory.swappiness'
100

3.2 cpu子系統

3.2.1 -c, --cpu-shares=0

對應的cgroup文件是cgroup/cpu/cpu.shares。

$ docker run --rm --cpu-shares 1600 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/cpu/cpu.shares"
1600

通過--cpu-shares可以設置容器使用CPU的權重,這個權重設置是針對CPU密集型的進程的。如果某個容器中的進程是空閑狀態,那么其它容器就能夠使用本該由空閑容器占用的CPU資源。也就是說,只有當兩個或多個容器都試圖占用整個CPU資源時,--cpu-shares設置才會有效。

我們使用如下命令來創建兩個容器,它們的權重分別為1024和512。

$ docker run -ti --cpu-shares 1024 ubuntu:14.04 stress -c 1
stress: info: [1] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd

$ docker run -ti --cpu-shares 512 ubuntu:14.04 stress -c 1
stress: info: [1] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd

從如下top命令的log可以看到,第一個容器產生的進程PID為1418,CPU占用率為66.1%,第二個容器產生進程PID為1471,CPU占用率為32.9%。兩個容器CPU占用率約為2:1的關系,測試結果與預期相符。

top - 18:51:50 up 9 days,  2:07,  0 users,  load average: 0.62, 0.15, 0.05
Tasks:  84 total,   3 running,  81 sleeping,   0 stopped,   0 zombie
%Cpu(s): 90.4 us,  2.2 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  7.4 si,  0.0 st
KiB Mem :  2052280 total,    71468 free,   117284 used,  1863528 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  1536284 avail Mem 
PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
1418 root      20   0    7312    100      0 R 66.1  0.0   0:22.92 stress
1471 root      20   0    7312     96      0 R 32.9  0.0   0:04.97 stress

3.2.2 --cpu-period=""

內核默認的Linux 調度CFS(完全公平調度器)周期為100ms,我們通過--cpu-period來設置容器對CPU的使用周期,同時--cpu-period接口需要和--cpu-quota接口一起來使用。--cpu-quota接口設置了CPU的使用值。CFS(完全公平調度器) 是內核默認使用的調度方式,為運行的進程分配CPU資源。對于多核CPU,根據需要調整--cpu-quota的值。

對應的cgroup文件是cgroup/cpu/cpu.cfs_period_us。以下命令創建了一個容器,同時設置了該容器對CPU的使用時間為50000(單位為微秒),并驗證了該接口對應的cgroup文件對應的值。

$ docker run -ti --cpu-period 50000 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/cpu/cpu.cfs_period_us"
50000

以下命令將--cpu-period的值設置為50000,--cpu-quota的值設置為25000。該容器在運行時可以獲取50%的cpu資源。

$ docker run -ti --cpu-period=50000 --cpu-quota=25000 ubuntu:14.04 stress -c 1
stress: info: [1] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd

從log的最后一行中可以看出,該容器的cpu使用率約為50.0%。

top - 10:36:55 up 6 min,  0 users,  load average: 0.49, 0.21, 0.10
Tasks:  68 total,   2 running,  66 sleeping,   0 stopped,   0 zombie
%Cpu(s): 49.3 us,  0.0 sy,  0.0 ni, 50.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  4050748 total,  3063952 free,   124280 used,   862516 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  3728860 avail Mem 
PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                           
770 root      20   0    7312     96      0 R 50.0  0.0   0:38.06 stress

3.2.3 --cpu-quota=0

對應的cgroup文件是cgroup/cpu/cpu.cfs_quota_us。

$ docker run --cpu-quota 1600 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us"
1600

--cpu-quota接口設置了CPU的使用值,通常情況下它需要和--cpu-period接口一起來使用。具體使用方法請參考--cpu-period選項。

3.3 cpuset子系統

3.3.1 --cpuset-cpus=""

該接口對應的cgroup文件是cgroup/cpuset/cpuset.cpus。

在多核CPU的虛擬機中,啟動一個容器,設置容器只使用CPU核1,并查看該接口對應的cgroup文件會被修改為1,log如下所示。

$ docker run -ti --cpuset-cpus 1 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/cpuset/cpuset.cpus"
1

通過以下命令指定容器使用cpu核1,并通過stress命令加壓。

$ docker run -ti --cpuset-cpus 1 ubuntu:14.04 stress -c 1

查看CPU資源的top命令的log如下所示。需要注意的是,輸入top命令并按回車鍵后,再按數字鍵1,終端才能顯示每個CPU的狀態。

top - 11:31:47 up 5 days, 21:00,  0 users,  load average: 0.62, 0.82, 0.77
Tasks: 104 total,   3 running, 101 sleeping,   0 stopped,   0 zombie
%Cpu0  :  0.0 us,  0.0 sy,  0.0 ni, 99.6 id,  0.0 wa,  0.0 hi,  0.4 si,  0.0 st
%Cpu1  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  0.3 us,  0.3 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  2051888 total,  1130220 free,   127972 used,   793696 buff/cache
KiB Swap: 33554416 total, 33351848 free,   202568 used.  1739888 avail Mem 

PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
10266 root      20   0    7312     96      0 R 100.0  0.0   0:11.92 stress

從以上log得知,只有CPU核1的負載為100%,而其它CPU核處于空閑狀態,結果與預期結果相符。

3.3.2 --cpuset-mems=""

該接口對應的cgroup文件是cgroup/cpuset/cpuset.mems。

$ docker run -ti --cpuset-mems=0 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/cpuset/cpuset.mems"
0

以下命令將限制容器進程使用內存節點1、3的內存。

$ docker run -it --cpuset-mems="1,3" ubuntu:14.04 bash

以下命令將限制容器進程使用內存節點0、1、2的內存。

$ docker run -it --cpuset-mems="0-2" ubuntu:14.04 bash

3.4 blkio子系統

3.4.1 --blkio-weight=0

通過--blkio-weight接口可以設置容器塊設備IO的權重,有效值范圍為10至1000的整數(包含10和1000)。默認情況下,所有容器都會得到相同的權重值(500)。對應的cgroup文件為cgroup/blkio/blkio.weight。以下命令設置了容器塊設備IO權重為10,在log中可以看到對應的cgroup文件的值為10。

$ docker run -ti --rm --blkio-weight 10 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.weight"
10

通過以下兩個命令來創建不同塊設備IO權重值的容器。

$ docker run -it --name c1 --blkio-weight 300 ubuntu:14.04 /bin/bash
    $ docker run -it --name c2 --blkio-weight 600 ubuntu:14.04 /bin/bash

如果在兩個容器中同時進行塊設備操作(例如以下命令)的話,你會發現所花費的時間和容器所擁有的塊設備IO權重成反比。

$ time dd if=/mnt/zerofile of=test.out bs=1M count=1024 oflag=direct

3.4.2 --blkio-weight-device=""

通過--blkio-weight-device="設備名:權重"接口可以設置容器對特定塊設備IO的權重,有效值范圍為10至1000的整數(包含10和1000)。

對應的cgroup文件為cgroup/blkio/blkio.weight_device。

$ docker run --blkio-weight-device "/dev/sda:1000" ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.weight_device"
8:0 1000

以上log中的"8:0"表示sda的設備號,可以通過stat命令來獲取某個設備的設備號。從以下log中可以查看到/dev/sda對應的主設備號為8,次設備號為0。

$ stat -c %t:%T /dev/sda
8:0

如果--blkio-weight-device接口和--blkio-weight接口一起使用,那么Docker會使用--blkio-weight值作為默認的權重值,然后使用--blkio-weight-device值來設定指定設備的權重值,而早先設置的默認權重值將不在這個特定設備中生效。

$ docker run --blkio-weight 300 --blkio-weight-device "/dev/sda:500" ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.weight_device"
8:0 500

通過以上log可以看出,當--blkio-weight接口和--blkio-weight-device接口一起使用的時候,/dev/sda設備的權重值由--blkio-weight-device設定的值來決定。

3.4.3 --device-read-bps=""

該接口用來限制指定設備的讀取速率,單位可以是kb、mb或者gb。對應的cgroup文件是cgroup/blkio/blkio.throttle.read_bps_device。

$ docker run -it --device /dev/sda:/dev/sda --device-read-bps /dev/sda:1mb ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.throttle.read_bps_device"
8:0 1048576

以上log中顯示8:0 1000,8:0表示/dev/sda, 該接口對應的cgroup文件的值為1048576,是1MB所對應的字節數,即1024的平方。

創建容器時通過--device-read-bps接口設置設備讀取速度為1MB/s。從以下log中可以看出,讀取速度被限定為1.0MB/s,與預期結果相符合。

$ docker run -it --device /dev/sda:/dev/sda --device-read-bps /dev/sda:1mB ubuntu:14.04 bash
root@df1de679fae4:/# dd iflag=direct,nonblock if=/dev/sda of=/dev/null bs=5M count=1
1+0 records in
1+0 records out
5242880 bytes (5.2 MB) copied, 5.00464 s, 1.0 MB/s

3.4.4 --device-write-bps=""

該接口用來限制指定設備的寫速率,單位可以是kb、mb或者gb。對應的cgroup文件是cgroup/blkio/blkio.throttle.write_bps_device。

$ docker run -it --device /dev/sda:/dev/sda --device-write-bps /dev/sda:1mB ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.throttle.write_bps_device"
8:0 1048576

以上log中顯示8:0 1000,8:0表示/dev/sda, 該接口對應的cgroup文件的值為1048576,是1MB所對應的字節數,即1024的平方。

創建容器時通過--device-write-bps接口設置設備寫速度為1MB/s。從以下log中可以看出,讀取速度被限定為1.0MB/s,與預期結果相符合。

限速操作:

$ docker run -it --device /dev/sda:/dev/sda --device-write-bps /dev/sda:1mb ubuntu:14.04 bash
root@18dc79b91cd4:/# dd oflag=direct,nonblock of=/dev/sda if=/dev/urandom bs=10K count=1000
1000+0 records in
1000+0 records out
10240000 bytes (10 MB) copied, 10.1987 s, 1.0 MB/s

3.4.5 --device-read-iops=""

該接口設置了設備的IO讀取速率,對應的cgroup文件是cgroup/blkio/blkio.throttle.read_iops_device。

$ docker run -it --device /dev/sda:/dev/sda --device-read-iops /dev/sda:400 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.throttle.read_iops_device"
8:0 400

可以通過"--device-read-iops /dev/sda:400"來限定sda的IO讀取速率(400次/秒),log如下所示。

$ docker run -ti --device /dev/sda:/dev/sda  --device-read-iops    /dev/sda:400 ubuntu:14.04
root@71910742c445:/# dd iflag=direct,nonblock if=/dev/sda of=/dev/null bs=1k count=1000
1000+0 records in
1000+0 records out
1024000 bytes (1.0 MB) copied, 2.42874 s, 422 kB/s

通過上面的log信息可以看出,容器每秒IO的讀取次數為400,共需要讀取1000次(log第二行:count=1000),測試結果顯示執行時間為2.42874秒,約為2.5(1000/400)秒, 與預期結果相符。

3.4.6 --device-write-iops=""

該接口設置了設備的IO寫速率,對應的cgroup文件是cgroup/blkio/blkio.throttle.write_iops_device。

$ docker run -it --device /dev/sda:/dev/sda --device-write-iops /dev/sda:400 ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.throttle.write_iops_device"
8:0 400

可以通過"--device-write-iops /dev/sda:400"來限定sda的IO寫速率(400次/秒),log如下所示。

$ docker run -ti --device /dev/sda:/dev/sda --device-write-iops /dev/sda:400 ubuntu:14.04
root@ef88a516d6ed:/# dd oflag=direct,nonblock of=/dev/sda if=/dev/urandom bs=1K count=1000
1000+0 records in
1000+0 records out
1024000 bytes (1.0 MB) copied, 2.4584 s, 417 kB/s

通過上面的log信息可以看出,容器每秒IO的寫入次數為400,共需要寫1000次(log第二行:count=1000),測試結果顯示執行時間為2.4584秒,約為2.5(1000/400)秒, 與預期結果相符。

4.總結

Docker的資源管理依賴于Linux內核Cgroups機制。理解Docker資源管理的原理并不難,讀者可以根據自己興趣補充一些有針對性的測試。關于Cgroups的實現機制已經遠超本文的范疇。感興趣的讀者可以自行查看相關文章和內核手冊。

作者簡介

孫遠,華為中央軟件研究院資深工程師,碩士畢業,9年軟件行業經驗。目前在華為從事容器Docker項目的測試工作。工作涉及到功能測試、性能測試、壓力測試、穩定性測試、安全測試、測試管理、工程能力構建等內容。參與編寫了《Docker進階與實戰》的Docker測試章節。先前曾經就職于美國風河系統公司,作為team leader從事風河Linux產品測試工作。活躍于Docker社區和內核測試ltp社區,目前有大量測試用例被開源社區接收。

研究方向:容器技術、Docker、Linux內核、軟件測試、自動化測試、測試過程改進。

薛婉菊,中軟國際科技服務有限公司軟件測試工程師,4年軟件行業經驗。目前參與容器Docker項目的測試工作,工作涉及到容器功能測試、性能測試、壓力測試等內容。

研究方向:容器技術、Docker、自動化測試。

參考資料

  1. http://www.cnblogs.com/hustcat/p/3980244.html
  2. http://www.lupaworld.com/article-250948-1.html
  3. http://www.tuicool.com/articles/Qrq2Ynz
  4. https://github.com/docker/docker/blob/master/docs/reference/run.md
  5. http://www.infoq.com/cn/articles/docker-kernel-knowledge-namespace-resource-isolation
  6. https://github.com/torvalds/Linux/tree/master/Documentation/cgroup-v1
  7. http://www.361way.com/increase-swap/1957.html
  8. https://goldmann.pl/blog/2014/09/11/resource-management-in-docker/
  9. https://www.datadoghq.com/blog/how-to-collect-docker-metrics/

感謝木環對本文的審校。

給InfoQ中文站投稿或者參與內容翻譯工作,請郵件至editors@cn.infoq.com。也歡迎大家通過新浪微博(@InfoQ,@丁曉昀),微信(微信號: InfoQChina )關注我們。

 

來自:http://www.infoq.com/cn/articles/docker-resource-management-cgroups

 

 本文由用戶 lynnpino 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!