1. 程式人生 > 實用技巧 >Docker 系列(八):限制容器CPU與記憶體

Docker 系列(八):限制容器CPU與記憶體

限制容器記憶體

壓力測試工具 stress

為了測試容器的記憶體使用情況,筆者在 ubuntu 的映象中安裝了壓力測試工作 stress,並新建立了映象 u-stress。本文演示用的所有容器都會通過 u-stress 映象建立(本文執行容器的宿主機為 CentOS7)。下面是建立 u-stress 映象的 Dockerfile:

FROM ubuntu:latest

RUN apt-get update && \
        apt-get install stress

建立映象的命令為:

$ docker build -t u-stress:latest .

限制記憶體使用上限

在進入繁瑣的設定細節之前我們先完成一個簡單的用例:限制容器可以使用的最大記憶體為 300M。
-m(--memory=) 選項可以完成這樣的配置:

$ docker run -it -m 300M --memory-swap -1 --name con1 u-stress /bin/bash

下面的 stress 命令會建立一個程序並通過 malloc 函式分配記憶體:

# stress --vm 1 --vm-bytes 500M

通過 docker stats 命令檢視實際情況:

上面的 docker run 命令中通過 -m 選項限制容器使用的記憶體上限為 300M。同時設定 memory-swap 值為 -1,它表示容器程式使用記憶體的受限,而可以使用的 swap 空間使用不受限制(宿主機有多少 swap 容器就可以使用多少)。

下面我們通過 top 命令來檢視 stress 程序記憶體的實際情況:

上面的截圖中先通過 pgrep 命令查詢 stress 命令相關的程序,程序號比較大的那個是用來消耗記憶體的程序,我們就檢視它的記憶體資訊。VIRT 是程序虛擬記憶體的大小,所以它應該是 500M。RES 為實際分配的實體記憶體數量,我們看到這個值就在 300M 上下浮動。看樣子我們已經成功的限制了容器能夠使用的實體記憶體數量。

限制可用的 swap 大小

強調一下--memory-swap是必須要與--memory一起使用的。

正常情況下, --memory-swap 的值包含容器可用記憶體和可用 swap。所以 --memory="300m" --memory-swap="1g" 的含義為:

容器可以使用 300M 的實體記憶體,並且可以使用 700M(1G -300M) 的 swap。--memory-swap居然是容器可以使用的實體記憶體和可以使用的 swap 之和!

把 --memory-swap 設定為 0 和不設定是一樣的,此時如果設定了 --memory,容器可以使用的 swap 大小為 --memory 值的兩倍。

如果 --memory-swap 的值和 --memory 相同,則容器不能使用 swap。下面的 demo 演示了在沒有 swap 可用的情況下向系統申請大量記憶體的場景:

$ docker run -it --rm -m 300M --memory-swap=300M u-stress /bin/bash
# stress --vm 1 --vm-bytes 500M

demo 中容器的實體記憶體被限制在 300M,但是程序卻希望申請到 500M 的實體記憶體。在沒有 swap 可用的情況下,程序直接被 OOM kill 了。如果有足夠的 swap,程式至少還可以正常的執行。

我們可以通過 --oom-kill-disable 選項強行阻止 OOM kill 的發生,但是筆者認為 OOM kill 是一種健康的行為,為什麼要阻止它呢?

除了限制可用 swap 的大小,還可以設定容器使用 swap 的緊迫程度,這一點和主機的 swappiness 是一樣的。容器預設會繼承主機的 swappiness,如果要顯式的為容器設定 swappiness 值,可以使用 --memory-swappiness 選項。‘

限制容器的 CPU資源

限制可用的 CPU 個數

在 docker 1.13 及更高的版本上,能夠很容易的限制容器可以使用的主機 CPU 個數。只需要通過 --cpus 選項指定容器可以使用的 CPU 個數就可以了,並且還可以指定如 1.5 之類的小數。接下來我們在一臺有四個 CPU 且負載很低的主機上進行 demo 演示:

通過下面的命令建立容器,--cpus=2 表示容器最多可以使用主機上兩個 CPU:

$ docker run -it --rm --cpus=2 u-stress:latest /bin/bash

然後由 stress 命令建立四個繁忙的程序消耗 CPU 資源:

# stress -c 4

我們先來看看 docker stats 命令的輸出:

容器 CPU 的負載為 200%,它的含義為單個 CPU 負載的兩倍。我們也可以把它理解為有兩顆 CPU 在 100% 的為它工作。
再讓我們通過 top 命令看看主機 CPU 的真實負載情況:

哈哈,有點大跌眼鏡!實際的情況並不是兩個 CPU 負載 100%,而另外兩個負載 0%。四個 CPU 的負載都是 50%,加起來容器消耗的 CPU 總量就是兩個 CPU 100% 的負載。

看來對於程序來說是沒有 CPU 個數這一概念的,核心只能通過程序消耗的 CPU 時間片來統計出程序佔用 CPU 的百分比。這也是我們看到的各種工具中都使用百分比來說明 CPU 使用率的原因。
嚴謹起見,我們看看 docker 的官方文件中是如何解釋 --cpus 選項的:
Specify how much of the available CPU resources a container can use.
果然,人家用的是 "how much",不可數的!並且 --cpus 選項支援設為小數也從側面說明了對 CPU 的計量只能是百分比。
看來筆者在本文中寫的 "CPU 個數" 都是不準確的。既然不準確,為什麼還要用?當然是為了容易理解。況且筆者認為在 --cpus 選項的上下文中理解為 "CPU 個數" 並沒有問題(有興趣的同學可以讀讀--cpus 選項的由來,人家的初衷也是要表示 CPU 個數的)。

雖然 --cpus 選項用起來很爽,但它畢竟是 1.13 才開始支援的。對於更早的版本完成同樣的功能我們需要配合使用兩個選項:--cpu-period 和 --cpu-quota(1.13 及之後的版本仍然支援這兩個選項)。下面的命令實現相同的結果:

$ docker run -it --rm --cpu-period=100000 --cpu-quota=200000 u-stress:latest /bin/bash

這樣的配置選項是不是讓人很傻眼呀!100000 是什麼?200000 又是什麼? 它們的單位是微秒,100000 表示 100 毫秒,200000 表示 200 毫秒。它們在這裡的含義是:在每 100 毫秒的時間裡,執行程序使用的 CPU 時間最多為 200 毫秒(需要兩個 CPU 各執行 100 毫秒)。要想徹底搞明白這兩個選項的同學可以參考:CFS BandWith Control。我們要知道這兩個選項才是事實的真相,但是真相往往很殘忍!還好 --cpus 選項成功的解救了我們,其實它就是包裝了 --cpu-period 和 --cpu-quota。

指定固定的 CPU

通過 --cpus 選項我們無法讓容器始終在一個或某幾個 CPU 上執行,但是通過 --cpuset-cpus 選項卻可以做到!這是非常有意義的,因為現在的多核系統中每個核心都有自己的快取,如果頻繁的排程程序在不同的核心上執行勢必會帶來快取失效等開銷。下面我們就演示如何設定容器使用固定的 CPU,下面的命令為容器設定了 --cpuset-cpus 選項,指定執行容器的 CPU 編號為 1:

$ docker run -it --rm --cpuset-cpus="1" u-stress:latest /bin/bash

再啟動壓力測試命令:

# stress -c 4

然後檢視主機 CPU 的負載情況:

這次只有 Cpu1 達到了 100%,其它的 CPU 並未被容器使用。我們還可以反覆的執行 stress -c 4 命令,但是始終都是 Cpu1 在幹活。
再看看容器的 CPU 負載,也是隻有 100%:

--cpuset-cpus 選項還可以一次指定多個 CPU:

$ docker run -it --rm --cpuset-cpus="1,3" u-stress:latest /bin/bash

這次我們指定了 1,3 兩個 CPU,執行 stress -c 4 命令,然後檢查主機的 CPU 負載:

Cpu1 和 Cpu3 的負載都達到了 100%。
容器的 CPU 負載也達到了 200%:

--cpuset-cpus 選項的一個缺點是必須指定 CPU 在作業系統中的編號,這對於動態排程的環境(無法預測容器會在哪些主機上執行,只能通過程式動態的檢測系統中的 CPU 編號,並生成 docker run 命令)會帶來一些不便。

設定使用 CPU 的權重

當 CPU 資源充足時,設定 CPU 的權重是沒有意義的。只有在容器爭用 CPU 資源的情況下, CPU 的權重才能讓不同的容器分到不同的 CPU 用量。--cpu-shares 選項用來設定 CPU 權重,它的預設值為 1024。我們可以把它設定為 2 表示很低的權重,但是設定為 0 表示使用預設值 1024。
下面我們分別執行兩個容器,指定它們都使用 Cpu0,並分別設定 --cpu-shares 為 512 和 1024:

$ docker run -it --rm --cpuset-cpus="0" --cpu-shares=512 u-stress:latest /bin/bash
$ docker run -it --rm --cpuset-cpus="0" --cpu-shares=1024 u-stress:latest /bin/bash

在兩個容器中都執行 stress -c 4 命令。

此時主機 Cpu0 的負載為 100%:

容器中 CPU 的負載為:

兩個容器分享一個 CPU,所以總量應該是 100%。具體每個容器分得的負載則取決於 --cpu-shares 選項的設定!我們的設定分別是 512 和 1024,則它們分得的比例為 1:2。在本例中如果想讓兩個容器各佔 50%,只要把--cpu-shares 選項設為相同的值就可以了。

轉載:https://www.cnblogs.com/ExMan/p/12987562.html