docker的系統資源限制及驗證
限制容器的資源
預設情況下,容器沒有資源限制,可以使用主機核心排程程式允許的儘可能多的給定資源。
Memory
記憶體風險
不允許容器消耗宿主機太多的記憶體是非常重要的。在 Linux 主機上,如果核心檢測到沒有足夠的記憶體來執行重要的系統功能,它會丟擲 OOME
或 Out of Memory
異常,並開始終止程序以釋放記憶體。任何程序都會被殺死,包括 Docker 和其他重要的應用程式。如果殺錯程序,可能導致整個系統癱瘓。
Docker 通過調整 Docker daemon 上的 OOM 優先順序來降低這些風險,以便它比系統上的其他程序更不可能被殺死。容器上的 OOM 優先順序未調整,這使得單個容器被殺死的可能性比 Docker daemon 或其他系統程序被殺死的可能性更大。你不應試圖通過在 daemon 或容器上手動設定 --oom-score-adj
--oom-kill-disable
來繞過這些安全措施。
有關Linux核心的OOM管理的更多資訊,檢視 Out of Memory Management
可以通過以下方式降低 OOME 導致系統不穩定的風險:
-
在應用程式釋出到生產之前,執行相關測試以便了解應用程式的記憶體要求;
-
確保應用程式僅在具有足夠資源的主機上執行;
-
限制容器可以使用的記憶體,如下所述;
-
在 Docker 主機上配置 Swap 時要小心,Swap 比記憶體更慢且效能更低,但可以提供緩衝以防止系統記憶體耗盡;
-
考慮將 Container 轉換部署為 Service,並使用服務級別約束和節點標籤來確保應用程式僅在具有足夠記憶體的主機上執行。
限制容器記憶體
下述選項中的大多數採用正整數,後跟 b
/ k
/ m
/ g
的字尾,代表單位:位元組 / 千位元組 / 兆位元組 / 千兆位元組。
選項 | 描述 |
---|---|
-m or --memory= |
容器可使用的最大記憶體, 最小值是 4m |
--memory-swap |
允許此容器交換到磁碟的記憶體量 |
--memory-swappiness |
預設情況下,主機核心可以交換容器使用的匿名頁面的百分比,可以設定 --memory-swappiness 的值區間為 0 - 100 |
--memory-reservation |
指定小於 --memory 的軟限制,當 Docker 檢測到主機上的爭用或記憶體不足時會啟用該限制. 如果使用 --memory-reservation ,則必須將其設定為低於 --memory 才能使其優先。因為它是軟限制,所以不保證容器不超過限制。 |
--kernel-memory |
容器可以使用的最大核心記憶體量, 最小值是 4m ,因為核心記憶體無法換出,缺乏核心記憶體的容器可能會阻塞主機資源,這會對主機和其他容器產生副作用 |
--oom-kill-disable |
預設情況, 如果發生記憶體不足(OOM)錯誤,核心會終止容器中的程序。 要改變這種行為,使用 --oom-kill-disable 選項。 僅在已設定 -m / -memory 選項的容器上禁用 OOM killer,如果未設定 -m 標誌,則主機可能會耗盡記憶體,核心可能需要終止主機系統的程序才能釋放記憶體 |
有關 cgroup 和記憶體的更多資訊,檢視 Memory Resource Controller
關於 --memory-swap
--memory-swap
是一個修飾符標誌,只有在設定了 --memory
時才有意義。使用 swap 允許容器在容器耗盡所有可用的 RAM 時,將多餘的記憶體需求寫入磁碟。對於經常將記憶體交換到磁碟的應用程式,效能會受到影響。
它的設定會產生複雜的影響:
-
如果
--memory-swap
設定為正整數,則必須設定--memory
和--memory-swap
。--memory-swap
表示可以使用的 memory 和 swap 總量,--memory
控制 no-swap 的用量。 所以,如果設定--memory="300m"
和--memory-swap="1g"
, 容器可以使用 300m memory 和 700m (1g - 300m
) swap。 -
如果
--memory-swap
設定為0
, 該設定被忽略,該值被視為未設定。 -
如果
--memory-swap
的值等於--memory
的值, 並且--memory
設定為正整數, 則容器無權訪問 swap。這是因為--memory-swap
是可以使用組合的 Memory 和 Swap,而--memory
只是可以使用的 Memory。 -
如果
--memory-swap
不設定, 並且--memory
設定了值, 容器可以使用--memory
兩倍的 Swap(如果主機容器配置了 Swap)。示例: 設定--memory="300m"
並且不設定--memory-swap
,容器可以使用 300m memory 和 600m swap。 -
如果
--memory-swap
設定為-1
,允許容器無限制使用 Swap。 -
在容器內部,像
free
等工具報告的是主機的可用 Swap,而不是容器內可用的。不要依賴於free
或類似工具的輸出來確定是否存在 Swap。
關於 --memory-swappiness
-
值為 0 時,關閉匿名頁交換。
-
值為 100 時,將所有匿名頁設定為可交換。
-
預設情況下,如果不設定
--memory-swappiness
, 該值從主機繼承。
關於 --kernel-memory
核心記憶體限制是就分配給容器的總記憶體而言的,考慮一下方案:
-
無限記憶體,無限核心記憶體:這是預設行為。
-
無限記憶體,有限核心記憶體:當所有 cgroup 所需的記憶體量大於主機上實際存在的記憶體量時,它是合適的。可以將核心記憶體配置為永遠不會超過主機上可用的記憶體,而需求更多記憶體的容器需要等待它。
-
有限記憶體,無限核心記憶體:整體記憶體有限,但核心記憶體不是。
-
有限記憶體,有限核心記憶體:限制使用者和核心記憶體對於除錯與記憶體相關的問題非常有用,如果容器使用意外數量的任意型別的記憶體,則記憶體不足不會影響其他容器或主機。在此設定中,如果核心記憶體限制低於使用者記憶體限制,則核心記憶體不足會導致容器遇到 OOM 錯誤。如果核心記憶體限制高於使用者記憶體限制,則核心限制不會導致容器遇到 OOM。
當你開啟任何核心記憶體限制時,主機會根據每個程序跟蹤 “高水位線” 統計資訊,因此您可以跟蹤哪些程序(在本例中為容器)正在使用多餘的記憶體。通過檢視主機上的 /proc/<PID>/status
,可以在每個程序中看到這一點。
CPU
預設情況下,每個容器對主機 CPU 週期的訪問許可權是不受限制的,您可以設定各種約束來限制給定容器訪問主機的 CPU 週期。大多數使用者使用和配置 預設 CFS 排程程式。在 Docker 1.13 及更高版本中,還可以配置實時排程程式。
配置預設 CFS 排程程式
CFS 是用於普通 Linux 程序的 Linux 核心 CPU 排程程式。通過以下設定,可以控制容器的 CPU 資源訪問量,使用這些設定時,Docker 會修改主機上容器的 cgroup 的設定。
選項 | 描述 |
---|---|
--cpus=<value> | 指定容器可以使用的 CPU 資源量。例如,如果主機有兩個CPU並且,設定 --cpus="1.5" ,則容器最多可以使用 1.5 個 CPU,這相當於設定 --cpu-period="100000" 和 --cpu-quota="150000" 。可在Docker 1.13及更高版本中使用。 |
--cpu-period=<value> | 指定 CPU CFS 排程程式週期,該週期與 --cpu-quota 一起使用,預設為100微秒。大多數使用者不會更改預設設定,如果使用Docker 1.13 或更高版本,請改用 --cpus 。 |
--cpu-quota=<value> | 對容器施加 CPU CFS 配額,在受限制之前容器限制為每個 --cpu-period 的微秒數,作為有效上限。如果使用Docker 1.13 或更高版本,請改用 --cpus 。 |
--cpuset-cpus | 限制容器可以使用的特定 CPU 或核心。如果主機有多個CPU,則容器可以使用的以, 分 隔的列表或 - 分隔的 CPU 範圍。第一個CPU 編號為 0,有效值可能是 0-3(使用第一個、第二個、第三個和第四個CPU)或 1,3(使用第二個和第四個CPU)。 |
--cpu-shares | 將此值設定為大於或小於預設值 1024,以增加或減少容器的權重,並使其可以訪問主機的 CPU 週期的佔較大或較小比例。僅在 CPU 週期受限時才會強制執行此操作。當有足夠的 CPU 週期時,所有容器都會根據需要使用盡可能多的 CPU。這是一個軟限制,--cpu-shares 不會阻止在群集模式下的容器排程。它為可用的 CPU 週期優先考慮容器 CPU 資源。它不保證或保留任何特定的 CPU 訪問許可權。 |
示例:如果你有 1 個 CPU,則以下每個命令都會保證容器每秒最多佔 CPU 的 50%。
Docker 1.13 或更高版本:
docker run -it --cpus=".5" ubuntu /bin/bash
Docker 1.12 或更低版本:
docker run -it --cpu-period=100000 --cpu-quota=50000 ubuntu /bin/bash
配置實時排程程式
In Docker 1.13 and higher, you can configure your container to use the realtime scheduler,
在 Docker 1.13 或更高版本,你可以配置容器使用實時排程程式。在配置 Docker daemon 或配置容器之前,需要確保正確配置主機的核心。
警告:CPU 排程和優先順序是高階核心級功能,大多數使用者不需要從預設值更改這些值,錯誤地設定這些值可能會導致主機系統變得不穩定或無法使用。
配置主機機器的核心
通過執行 zcat /proc/config.gz | grep CONFIG_RT_GROUP_SCHED
驗證是否在 Linux 核心中啟用了 CONFIG_RT_GROUP_SCHED
,或者檢查是否存在檔案 /sys/fs/cgroup/cpu.rt_runtime_us
。有關配置核心實時排程程式的教程,請參閱作業系統的文件。
配置DOCKER DAEMON
要使用實時排程程式執行容器,請執行 Docker daemon,並將 --cpu-rt-runtime
設定為每個執行時間段為實時任務保留的最大微秒數。例如,預設週期為 1000000 微秒(1秒),設定 --cpu-rt-runtime=950000
可確保使用實時排程程式的容器每 1000000 微秒可執行 950000 微秒,並保留至少 50000 微秒用於非實時任務。要在使用 systemd 的系統上永久保留此配置,請參閱 Control and configure Docker with systemd。
配置個別容器
使用 docker run
啟動容器時,可以傳遞多個引數來控制容器的 CPU 優先順序。有關適當值的資訊,請參閱作業系統的文件或 ulimit
命令。
選項 | 描述 |
---|---|
--cap-add=sys_nice | 授予容器 CAP_SYS_NICE 功能,該功能允許容器引發程序良好值,設定實時排程策略,設定 CPU 親和性以及其他操作。 |
--cpu-rt-runtime=<value> | 容器可以在 Docker 守護程式的實時排程程式週期內以實時優先順序執行的最大微秒數,需要設定 --cap-add=sys_nice 。 |
--ulimit rtprio=<value> | 容器允許的最大實時優先順序,需要 --cap-add=sys_nice 標誌。 |
示例:
docker run --it --cpu-rt-runtime=950000 \
--ulimit rtprio=99 \
--cap-add=sys_nice \
debian:jessie
如果未正確配置核心或 Docker Daemon,則會發生錯誤。
演示效果
[[email protected] ~]# lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 1
On-line CPU(s) list: 0
Thread(s) per core: 1
Core(s) per socket: 1
座: 1
NUMA 節點: 1
廠商 ID: GenuineIntel
CPU 系列: 6
型號: 94
型號名稱: Intel(R) Core(TM) i5-6300HQ CPU @ 2.30GHz
步進: 3
CPU MHz: 2304.002
BogoMIPS: 4608.00
超管理器廠商: VMware
虛擬化型別: 完全
L1d 快取: 32K
L1i 快取: 32K
L2 快取: 256K
L3 快取: 6144K
NUMA 節點0 CPU: 0
拖壓測工具:
https://hub.docker.com/r/lorel/docker-stress-ng/
[[email protected] ~]# docker pull lorel/docker-stress-ng
檢視選項:
[[email protected] ~]# docker run --name stress -it --rm lorel/docker-stress-ng stress --help
設定選項
[[email protected] ~]# docker run --name stress -it --rm -m 256m lorel/docker-stress-ng stress --vm 2
//給docker傳選項,該程序最多佔用256M記憶體 --vm 2 則需要512M,記憶體溢位
結果
[[email protected] ~]# docker run --name stress -it --rm -m 256m lorel/docker-stress-ng stress --vm 2
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 2 vm
^[[^Cstress-ng: info: [1] successful run completed in 27.36s
[[email protected] ~]# docker run --name stress -it --rm -m 256m lorel/docker-stress-ng stress --vm 2
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 2 vm
檢視程序
[[email protected] ~]# docker top stress
UID PID PPID C STIME TTY TIME CMD
root 2749 2735 0 21:35 pts/0 00:00:00 /usr/bin/stress-ng stress --vm 2
root 2779 2749 0 21:35 pts/0 00:00:00 /usr/bin/stress-ng stress --vm 2
root 2780 2749 0 21:35 pts/0 00:00:00 /usr/bin/stress-ng stress --vm 2
root 2800 2779 20 21:36 pts/0 00:00:01 /usr/bin/stress-ng stress --vm 2
root 2807 2780 0 21:36 pts/0 00:00:00 /usr/bin/stress-ng stress --vm 2
顯示記憶體變數資訊
[[email protected] ~]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
52fdb41f90fd stress 23.12% 256MiB / 256MiB 100.00% 648B / 0B 91.8MB / 28.1GB 5
演示限制CPU核心數
[[email protected] ~]# docker run --name stress -it --rm --cpus 1 lorel/docker-stress-ng:latest stress --cpu 8
//也可以不加,預設會把CPU核數全吞下去
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 8 cpu
[[email protected] ~]# docker top stress
UID PID PPID C STIME TTY TIME CMD
root 3466 3452 0 21:53 pts/0 00:00:00 /usr/bin/stress-ng stress --cpu 8
root 3496 3466 11 21:53 pts/0 00:00:04 /usr/bin/stress-ng stress --cpu 8
root 3497 3466 11 21:53 pts/0 00:00:04 /usr/bin/stress-ng stress --cpu 8
root 3498 3466 11 21:53 pts/0 00:00:04 /usr/bin/stress-ng stress --cpu 8
root 3499 3466 11 21:53 pts/0 00:00:04 /usr/bin/stress-ng stress --cpu 8
root 3500 3466 11 21:53 pts/0 00:00:04 /usr/bin/stress-ng stress --cpu 8
root 3501 3466 11 21:53 pts/0 00:00:04 /usr/bin/stress-ng stress --cpu 8
root 3502 3466 11 21:53 pts/0 00:00:04 /usr/bin/stress-ng stress --cpu 8
root 3503 3466 11 21:53 pts/0 00:00:04 /usr/bin/stress-ng stress --cpu 8
[[email protected] ~]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
ff567312dc6e stress 90.73% 15.81MiB / 974.6MiB 1.62% 648B / 0B 0B / 0B 9
docker容器如何進行資源控制和驗證?
Docker同LXC一樣,其對資源的隔離和管控是以Linux核心的namespaces和cgroup為基礎。Docker的資源隔離使用了Linux核心 Kernel中的Namespaces功能來實現,隔離的物件包括:主機名與域名、程序編號、網路裝置、檔案系統的掛載點等,namespace中的IPC隔離docker並未使用,docker中使用TCP替代IPC。
在使用Namespaces隔離資源的同時,Docker使用了Linux核心Kernel提供的cgroup來對Container使用的CPU、記憶體、磁碟IO資源進行配額管控。換句話說,在docker的容器啟動引數中,像—cpu*、--memory*和—blkio*的設定,實際上就是設定cgroup的相對應cpu子系統、記憶體子系統、磁碟IO子系統的配額控制檔案,只不過這個修改配額控制檔案的過程是docker例項代替我們做掉罷了。因此,我們完全可以直接修改docker容器所對應的cgroup子系統中的配額控制檔案來達到控制docker容器資源配額的同樣目的,接下來將以IO控制為例,介紹一下具體實現過程。
【物理部署架構圖】
實驗測試環境物理部署圖如圖1
整體的部署思路如下:
1、首先將物理磁碟進行分割槽操作(此步驟可選)
2、將分割槽建立為物理卷(PV)並新增到卷組(VG)中
3、從卷組(VG)中創建出邏輯卷(LV),並將該卷格式化後掛載供docker使用
4、將步驟3中邏輯卷掛載點再掛載到docker映象容器中。
【具體操作實現過程】
1、將磁碟裝置/dev/sdb進行分割槽,分別為sdb1和sdb2
2、建立PV
pvcreate /dev/sdb2 #使用sdb2建立物理卷(PV)
3、建立VG
vgcreate datavg /dev/sdb2 #vg名稱為datavg
4、建立LV,並格式化
lvcreate -n datalv datavg && mkfs.ext4 /dev/mapper/datavg-datalv
5、建立目錄並掛載
mkdir /data && mount /dev/mapper/datavg-datalv /data
【測試過程】
1、 使用docker映象docker.io/learn/tutorial :jiangzt作為測試容器
2、 不加配額引數的情況下啟動容器
啟動容器時不配額引數,並觀察其所在的cgroup子系統的配置情況
docker run -dit -h inner --name test -v /data:/data a36927dbb31f /bin/bash
根據容器ID觀察cgroup下blkio子系統的配置情況,結果是檔案blkio.throttle.write_bps_device中沒有內容
進入容器內,執行dd命令,測試觀察此時預設的寫入IO能力
docker exec -it test /bin/bash
time dd if=/dev/zero of=/data/test.out bs=1M count=1024 oflag=direct
可以得知寫入的平均速度是 2.1GB/s
3、使用引數device-write-bps啟動容器
啟動容器增加配額引數device-write-bps,寫入目標速度1mb/s,並觀察其所在的cgroup子系統的配置情況
docker run -dit -m 100m -h inner --name test --device-write-bps /dev/dm-6:1mb -v/data:/data a36927dbb31f /bin/bash
說明:lv的裝置名稱/dev/dm-6可以通過dmsetup做查詢
容器啟動後根據容器id觀察cgroup子系統中的配置情況
在cgroup的blkio.throttle.write_bps_device檔案中觀察到了253:6 1048576 就是我們在啟動docker時指定的--device-write-bps引數值,裝置為253:6(/dev/dm-6),io能力為1048576byte(1mb)
進入容器內,執行dd命令,測試觀察此時預設的寫入IO能力
可以觀察到此時的寫入速度為1.0MB/s
4、 直接修改cgroup中的引數配置
前面在原理介紹時提過,docker其實也是修改cgroup中的引數來控制資源的使用,那麼我們直接修改blkio.throttle.write_bps_device,然後觀察是否能直接作用於docker容器,還是使用上面的容器id進行操作,將blkio.throttle.write_bps_device中的內容修改為10MB/secho 253:6 10485760 > blkio.throttle.write_bps_device進入容器內,執行dd命令,測試觀察此時預設的寫入IO能力
可以看到通過直接修改cgroup中的blkio.throttle.write_bps_device的速度值起到了作用
5、 對於其他引數的控制分析
細心的你可能已經發現在docker映象時,還有個引數-m 100m,該引數是用來控制容器使用記憶體的配額,我們可以通過docker stats進行驗證
同樣我們也是可以在cgroup的記憶體子系統中看到相應的引數配置。
【總結】
通過以上的分析測試,進一步熟悉瞭解了docker執行時的行為和所依賴的底層的技術原理,當我們希望在不停止docker容器執行的前提下,能夠對cpu、記憶體和磁碟IO等資源進行動態配額調整時,便可以通過直接修改cgroup配置來達到目的,希望能夠為docker的自動化運維開發提供解決思路和辦法。
借鑑:
http://rdc.hundsun.com/portal/article/802.html
https://www.jianshu.com/p/0e03000eb9fc
https://www.kernel.org/doc/gorman/html/understand/understand016.html
https://www.kernel.org/doc/gorman/html/understand/understand001.html