1. 程式人生 > 實用技巧 >Docker執行時資源限制

Docker執行時資源限制

Docker 執行時資源限制
Docker 基於 Linux 核心提供的 cgroups 功能,可以限制容器在執行時使用到的資源,比如記憶體、CPU、塊 I/O、網路等。

記憶體限制
概述
Docker 提供的記憶體限制功能有以下幾點:

容器能使用的記憶體和交換分割槽大小。
容器的核心記憶體大小。
容器虛擬記憶體的交換行為。
容器記憶體的軟性限制。
是否殺死佔用過多記憶體的容器。
容器被殺死的優先順序
一般情況下,達到記憶體限制的容器過段時間後就會被系統殺死。

記憶體限制相關的引數
執行docker run命令時能使用的和記憶體限制相關的所有選項如下。

選項 描述
-m,--memory 記憶體限制,格式是數字加單位,單位可以為 b,k,m,g。最小為 4M
--memory-swap 記憶體+交換分割槽大小總限制。格式同上。必須必-m設定的大
--memory-reservation 記憶體的軟性限制。格式同上
--oom-kill-disable 是否阻止 OOM killer 殺死容器,預設沒設定
--oom-score-adj 容器被 OOM killer 殺死的優先順序,範圍是[-1000, 1000],預設為 0
--memory-swappiness 用於設定容器的虛擬記憶體控制行為。值為 0~100 之間的整數
--kernel-memory 核心記憶體限制。格式同上,最小為 4M
使用者記憶體限制
使用者記憶體限制就是對容器能使用的記憶體和交換分割槽的大小作出限制。使用時要遵循兩條直觀的規則:-m,--memory選項的引數最小為 4 M。--memory-swap不是交換分割槽,而是記憶體加交換分割槽的總大小,所以--memory-swap必須比-m,--memory大。在這兩條規則下,一般有四種設定方式。

你可能在進行記憶體限制的實驗時發現docker run命令報錯:WARNING: Your kernel does not support swap limit capabilities, memory limited without swap.

這是因為宿主機核心的相關功能沒有開啟。按照下面的設定就行。

step 1:編輯/etc/default/grub檔案,將GRUB_CMDLINE_LINUX一行改為GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"

step 2:更新 GRUB,即執行$ sudo update-grub

step 3:重啟系統。

1. 不設定
如果不設定-m,--memory和--memory-swap,容器預設可以用完宿舍機的所有記憶體和 swap 分割槽。不過注意,如果容器佔用宿主機的所有記憶體和 swap 分割槽超過一段時間後,會被宿主機系統殺死(如果沒有設定--00m-kill-disable=true的話)。

2. 設定-m,--memory,不設定--memory-swap
給-m或--memory設定一個不小於 4M 的值,假設為 a,不設定--memory-swap,或將--memory-swap設定為 0。這種情況下,容器能使用的記憶體大小為 a,能使用的交換分割槽大小也為 a。因為 Docker 預設容器交換分割槽的大小和記憶體相同。

如果在容器中執行一個一直不停申請記憶體的程式,你會觀察到該程式最終能佔用的記憶體大小為 2a。

比如$ docker run -m 1G ubuntu:16.04,該容器能使用的記憶體大小為 1G,能使用的 swap 分割槽大小也為 1G。容器內的程序能申請到的總記憶體大小為 2G。

3. 設定-m,--memory=a,--memory-swap=b,且b > a
給-m設定一個引數 a,給--memory-swap設定一個引數 b。a 時容器能使用的記憶體大小,b是容器能使用的 記憶體大小 + swap 分割槽大小。所以 b 必須大於 a。b -a 即為容器能使用的 swap 分割槽大小。

比如$ docker run -m 1G --memory-swap 3G ubuntu:16.04,該容器能使用的記憶體大小為 1G,能使用的 swap 分割槽大小為 2G。容器內的程序能申請到的總記憶體大小為 3G。

4. 設定-m,--memory=a,--memory-swap=-1
給-m引數設定一個正常值,而給--memory-swap設定成 -1。這種情況表示限制容器能使用的記憶體大小為 a,而不限制容器能使用的 swap 分割槽大小。

這時候,容器內程序能申請到的記憶體大小為 a + 宿主機的 swap 大小。

Memory reservation
這種 memory reservation 機制不知道怎麼翻譯比較形象。Memory reservation 是一種軟性限制,用於節制容器記憶體使用。給--memory-reservation設定一個比-m小的值後,雖然容器最多可以使用-m使用的記憶體大小,但在宿主機記憶體資源緊張時,在系統的下次記憶體回收時,系統會回收容器的部分記憶體頁,強迫容器的記憶體佔用回到--memory-reservation設定的值大小。

沒有設定時(預設情況下)--memory-reservation的值和-m的限定的值相同。將它設定為 0 會設定的比-m的引數大 等同於沒有設定。

Memory reservation 是一種軟性機制,它不保證任何時刻容器使用的記憶體不會超過--memory-reservation限定的值,它只是確保容器不會長時間佔用超過--memory-reservation限制的記憶體大小。

例如:

$ docker run -it -m 500M --memory-reservation 200M ubuntu:16.04 /bin/bash

如果容器使用了大於 200M 但小於 500M 記憶體時,下次系統的記憶體回收會嘗試將容器的記憶體鎖緊到 200M 以下。

例如:

$ docker run -it --memory-reservation 1G ubuntu:16.04 /bin/bash

容器可以使用盡可能多的記憶體。--memory-reservation確保容器不會長時間佔用太多記憶體。

OOM killer
預設情況下,在出現 out-of-memory(OOM) 錯誤時,系統會殺死容器內的程序來獲取更多空閒記憶體。這個殺死程序來節省記憶體的程序,我們姑且叫它 OOM killer。我們可以通過設定--oom-kill-disable選項來禁止 OOM killer 殺死容器內程序。但請確保只有在使用了-m/--memory選項時才使用--oom-kill-disable禁用 OOM killer。如果沒有設定-m選項,卻禁用了 OOM-killer,可能會造成出現 out-of-memory 錯誤時,系統通過殺死宿主機程序或獲取更改記憶體。

下面的例子限制了容器的記憶體為 100M 並禁止了 OOM killer:

$ docker run -it -m 100M --oom-kill-disable ubuntu:16.04 /bin/bash

是正確的使用方法。

而下面這個容器沒設定記憶體限制,卻禁用了 OOM killer 是非常危險的:

$ docker run -it --oom-kill-disable ubuntu:16.04 /bin/bash

容器沒用記憶體限制,可能或導致系統無記憶體可用,並嘗試時殺死系統程序來獲取更多可用記憶體。

一般一個容器只有一個程序,這個唯一程序被殺死,容器也就被殺死了。我們可以通過--oom-score-adj選項來設定在系統記憶體不夠時,容器被殺死的優先順序。負值更教不可能被殺死,而正值更有可能被殺死。

核心記憶體
核心記憶體和使用者記憶體不同的地方在於核心記憶體不能被交換出。不能交換出去的特性使得容器可以通過消耗太多記憶體來堵塞一些系統服務。核心記憶體包括:

stack pages(棧頁面)
slab pages
socket memory pressure
tcp memory pressure
可以通過設定核心記憶體限制來約束這些記憶體。例如,每個程序都要消耗一些棧頁面,通過限制核心記憶體,可以在核心記憶體使用過多時阻止新程序被建立。

核心記憶體和使用者記憶體並不是獨立的,必須在使用者記憶體限制的上下文中限制核心記憶體。

假設使用者記憶體的限制值為 U,核心記憶體的限制值為 K。有三種可能地限制核心記憶體的方式:

U != 0,不限制核心記憶體。這是預設的標準設定方式
K < U,核心記憶體時使用者記憶體的子集。這種設定在部署時,每個 cgroup 的記憶體總量被過度使用。過度使用核心記憶體限制是絕不推薦的,因為系統還是會用完不能回收的記憶體。在這種情況下,你可以設定 K,這樣 groups 的總數就不會超過總記憶體了。然後,根據系統服務的質量自有地設定 U。
K > U,因為核心記憶體的變化也會導致使用者計數器的變化,容器核心記憶體和使用者記憶體都會觸發回收行為。這種配置可以讓管理員以一種統一的檢視看待記憶體。對想跟蹤核心記憶體使用情況的使用者也是有用的。
例如:

$ docker run -it -m 500M --kernel-memory 50M ubuntu:16.04 /bin/bash

容器中的程序最多能使用 500M 記憶體,在這 500M 中,最多隻有 50M 核心記憶體。

$ docker run -it --kernel-memory 50M ubuntu:16.04 /bin/bash

沒用設定使用者記憶體限制,所以容器中的程序可以使用盡可能多的記憶體,但是最多能使用 50M 核心記憶體。

Swappiness
預設情況下,容器的核心可以交換出一定比例的匿名頁。--memory-swappiness就是用來設定這個比例的。--memory-swappiness可以設定為從 0 到 100。0 表示關閉匿名頁面交換。100 表示所有的匿名頁都可以交換。預設情況下,如果不適用--memory-swappiness,則該值從父程序繼承而來。

例如:

$ docker run -it --memory-swappiness=0 ubuntu:16.04 /bin/bash

將--memory-swappiness設定為 0 可以保持容器的工作集,避免交換代理的效能損失。

CPU 限制
概述
Docker 的資源限制和隔離完全基於 Linux cgroups。對 CPU 資源的限制方式也和 cgroups 相同。Docker 提供的 CPU 資源限制選項可以在多核系統上限制容器能利用哪些 vCPU。而對容器最多能使用的 CPU 時間有兩種限制方式:一是有多個 CPU 密集型的容器競爭 CPU 時,設定各個容器能使用的 CPU 時間相對比例。二是以絕對的方式設定容器在每個排程週期內最多能使用的 CPU 時間。

CPU 限制相關引數
docker run命令和 CPU 限制相關的所有選項如下:

選項 描述
--cpuset-cpus="" 允許使用的 CPU 集,值可以為 0-3,0,1
-c,--cpu-shares=0 CPU 共享權值(相對權重)
cpu-period=0 限制 CPU CFS 的週期,範圍從 100ms~1s,即[1000, 1000000]
--cpu-quota=0 限制 CPU CFS 配額,必須不小於1ms,即 >= 1000
--cpuset-mems="" 允許在上執行的記憶體節點(MEMs),只對 NUMA 系統有效
其中--cpuset-cpus用於設定容器可以使用的 vCPU 核。-c,--cpu-shares用於設定多個容器競爭 CPU 時,各個容器相對能分配到的 CPU 時間比例。--cpu-period和--cpu-quata用於絕對設定容器能使用 CPU 時間。

--cpuset-mems暫用不上,這裡不談。

CPU 集
我們可以設定容器可以在哪些 CPU 核上執行。

例如:

$ docker run -it --cpuset-cpus="1,3" ubuntu:14.04 /bin/bash

表示容器中的程序可以在 cpu 1 和 cpu 3 上執行。

$ docker run -it --cpuset-cpus="0-2" ubuntu:14.04 /bin/bash

表示容器中的程序可以在 cpu 0、cpu 1 及 cpu 3 上執行。

在 NUMA 系統上,我們可以設定容器可以使用的記憶體節點。

例如:

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

表示容器中的程序只能使用記憶體節點 1 和 3 上的記憶體。

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

表示容器中的程序只能使用記憶體節點 0、1、2 上的記憶體。

CPU 資源的相對限制
預設情況下,所有的容器得到同等比例的 CPU 週期。在有多個容器競爭 CPU 時我們可以設定每個容器能使用的 CPU 時間比例。這個比例叫作共享權值,通過-c或--cpu-shares設定。Docker 預設每個容器的權值為 1024。不設定或將其設定為 0,都將使用這個預設值。系統會根據每個容器的共享權值和所有容器共享權值和比例來給容器分配 CPU 時間。

假設有三個正在執行的容器,這三個容器中的任務都是 CPU 密集型的。第一個容器的 cpu 共享權值是 1024,其它兩個容器的 cpu 共享權值是 512。第一個容器將得到 50% 的 CPU 時間,而其它兩個容器就只能各得到 25% 的 CPU 時間了。如果再新增第四個 cpu 共享值為 1024 的容器,每個容器得到的 CPU 時間將重新計算。第一個容器的CPU 時間變為 33%,其它容器分得的 CPU 時間分別為 16.5%、16.5%、33%。

必須注意的是,這個比例只有在 CPU 密集型的任務執行時才有用。在四核的系統上,假設有四個單程序的容器,它們都能各自使用一個核的 100% CPU 時間,不管它們的 cpu 共享權值是多少。

在多核系統上,CPU 時間權值是在所有 CPU 核上計算的。即使某個容器的 CPU 時間限制少於 100%,它也能使用各個 CPU 核的 100% 時間。

例如,假設有一個不止三核的系統。用-c=512的選項啟動容器{C0},並且該容器只有一個程序,用-c=1024的啟動選項為啟動容器C2,並且該容器有兩個程序。CPU 權值的分佈可能是這樣的:

PID container CPU CPU share
100 {C0} 0 100% of CPU0
101 {C1} 1 100% of CPU1
102 {C1} 2 100% of CPU2

CPU 資源的絕對限制
Linux 通過 CFS(Completely Fair Scheduler,完全公平排程器)來排程各個程序對 CPU 的使用。CFS 預設的排程週期是 100ms。

關於 CFS 的更多資訊,參考CFS documentation on bandwidth limiting。

我們可以設定每個容器程序的排程週期,以及在這個週期內各個容器最多能使用多少 CPU 時間。使用--cpu-period即可設定排程週期,使用--cpu-quota即可設定在每個週期內容器能使用的 CPU 時間。兩者一般配合使用。

例如:

$ docker run -it --cpu-period=50000 --cpu-quota=25000 ubuntu:16.04 /bin/bash

將 CFS 排程的週期設為 50000,將容器在每個週期內的 CPU 配額設定為 25000,表示該容器每 50ms 可以得到 50% 的 CPU 執行時間。

$ docker run -it --cpu-period=10000 --cpu-quota=20000 ubuntu:16.04 /bin/bash

將容器的 CPU 配額設定為 CFS 週期的兩倍,CPU 使用時間怎麼會比周期大呢?其實很好解釋,給容器分配兩個 vCPU 就可以了。該配置表示容器可以在每個週期內使用兩個 vCPU 的 100% 時間。

CFS 週期的有效範圍是 1ms~1s,對應的--cpu-period的數值範圍是 1000~1000000。而容器的 CPU 配額必須不小於 1ms,即--cpu-quota的值必須 >= 1000。可以看出這兩個選項的單位都是 us。

正確的理解“絕對”
注意前面我們用--cpu-quota設定容器在一個排程週期內能使用的 CPU 時間時實際上設定的是一個上限。並不是說容器一定會使用這麼長的 CPU 時間。比如,我們先啟動一個容器,將其繫結到 cpu 1 上執行。給其--cpu-quota和--cpu-period都設定為 50000。

$ docker run --rm --name test01 --cpu-cpus 1 --cpu-quota=50000 --cpu-period=50000 deadloop:busybox-1.25.1-glibc

排程週期為 50000,容器在每個週期內最多能使用 50000 cpu 時間。

再用docker stats test01可以觀察到該容器對 CPU 的使用率在100%左右。然後,我們再以同樣的引數啟動另一個容器。

$ docker run --rm --name test02 --cpu-cpus 1 --cpu-quota=50000 --cpu-period=50000 deadloop:busybox-1.25.1-glibc

再用docker stats test01 test02可以觀察到這兩個容器,每個容器對 cpu 的使用率在 50% 左右。說明容器並沒有在每個週期內使用 50000 的 cpu 時間。

使用docker stop test02命令結束第二個容器,再加一個引數-c 2048啟動它:

$ docker run --rm --name test02 --cpu-cpus 1 --cpu-quota=50000 --cpu-period=50000 -c 2048 deadloop:busybox-1.25.1-glibc

再用docker stats test01命令可以觀察到第一個容器的 CPU 使用率在 33% 左右,第二個容器的 CPU 使用率在 66% 左右。因為第二個容器的共享值是 2048,第一個容器的預設共享值是 1024,所以第二個容器在每個週期內能使用的 CPU 時間是第一個容器的兩倍。

轉載自:https://blog.csdn.net/candcplusplus/article/details/53728507