在使用 docker 運行容器時,默認的狀況下,docker沒有對容器進行硬件資源的限制,當一臺主機上運行幾百個容器,這些容器雖然互相隔離,可是底層卻使用着相同的 CPU、內存和磁盤資源。若是不對容器使用的資源進行限制,那麼容器之間會互相影響,小的來講會致使容器資源使用不公平;大的來講,可能會致使主機和集羣資源耗盡,服務徹底不可用。html
docker 做爲容器的管理者,天然提供了控制容器資源的功能。正如使用內核的 namespace 來作容器之間的隔離,docker 也是經過內核的 cgroups 來作容器的資源限制;包括CPU、內存、磁盤三大方面,基本覆蓋了常見的資源配額和使用量控制。git
Docker內存控制OOME在linxu系統上,若是內核探測到當前宿主機已經沒有可用內存使用,那麼會拋出一個OOME(Out Of Memory Exception:內存異常 ),而且會開啓killing去殺掉一些進程。github
一旦發生OOME,任何進程都有可能被殺死,包括docker daemon在內,爲此,docker特意調整了docker daemon的OOM_Odj優先級,以避免他被殺掉,但容器的優先級並未被調整。通過系統內部複製的計算後,每一個系統進程都會有一個OOM_Score得分,OOM_Odj越高,得分越高,(在docker run的時候能夠調整OOM_Odj)得分最高的優先被kill掉,固然,也能夠指定一些特定的重要的容器禁止被OMM殺掉,在啓動容器時使用 –oom-kill-disable=true指定。算法
參考:Docker監控容器資源的佔用狀況docker
cgroup是Control Groups的縮寫,是Linux 內核提供的一種能夠限制、記錄、隔離進程組所使用的物理資源(如 cpu、memory、磁盤IO等等) 的機制,被LXC、docker等不少項目用於實現進程資源控制。cgroup將任意進程進行分組化管理的 Linux 內核功能。cgroup自己是提供將進程進行分組化管理的功能和接口的基礎結構,I/O 或內存的分配控制等具體的資源管理功能是經過這個功能來實現的。這些具體的資源管理功能稱爲cgroup子系統,有如下幾大子系統實現:shell
目前docker只是用了其中一部分子系統,實現對資源配額和使用的控制。ubuntu
可使用stress工具來測試CPU和內存。使用下面的Dockerfile來建立一個基於Ubuntu的stress工具鏡像。bash
FROM ubuntu:14.04
RUN apt-get update &&apt-get install stress網絡
資源監控的關鍵目錄:cat讀出
已使用內存: /sys/fs/cgroup/memory/docker/應用ID/memory.usage_in_bytes
app
分配的總內存: /sys/fs/cgroup/memory/docker/應用ID/memory.limit_in_bytes
已使用的cpu:單位納秒 /sys/fs/cgroup/cpuacct/docker/應用ID/cpuacct.usage
系統當前cpu:
$ cat /proc/stat | grep 'cpu '(週期/時間片/jiffies) #獲得的數字相加/HZ(cat /boot/config-`uname -r` | grep '^CONFIG_HZ=' ubuntu 14.04爲250)就是系統時間(秒) #再乘以10*9就是系統時間(納秒)
例子
[~]$ cat /proc/stat cpu 432661 13295 86656 422145968 171474 233 5346 cpu0 123075 2462 23494 105543694 16586 0 4615 cpu1 111917 4124 23858 105503820 69697 123 371 cpu2 103164 3554 21530 105521167 64032 106 334 cpu3 94504 3153 17772 105577285 21158 4 24 intr 1065711094 1057275779 92 0 6 6 0 4 0 3527 0 0 0 70 0 20 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ctxt 19067887 btime 1139187531 processes 270014 procs_running 1 procs_blocked 0 輸出解釋 CPU 以及CPU0、CPU一、CPU二、CPU3每行的每一個參數意思(以第一行爲例)爲: 參數 解釋 user (432661) 從系統啓動開始累計到當前時刻,用戶態的CPU時間(單位:jiffies) ,不包含 nice值爲負進程。 nice (13295) 從系統啓動開始累計到當前時刻,nice值爲負的進程所佔用的CPU時間(單位:jiffies) system (86656) 從系統啓動開始累計到當前時刻,核心時間(單位:jiffies) idle (422145968) 從系統啓動開始累計到當前時刻,除硬盤IO等待時間之外其它等待時間(單位:jiffies) iowait (171474) 從系統啓動開始累計到當前時刻,硬盤IO等待時間(單位:jiffies) , irq (233) 從系統啓動開始累計到當前時刻,硬中斷時間(單位:jiffies) softirq (5346) 從系統啓動開始累計到當前時刻,軟中斷時間(單位:jiffies)
cpu使用率:
(已使用2-已使用1)/(系統當前2-系統當前1)*100%
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: 重啓系統。
若是不設置-m,--memory
和--memory-swap
,容器默承認以用完宿舍機的全部內存和 swap 分區。不過注意,若是容器佔用宿主機的全部內存和 swap 分區超過一段時間後,會被宿主機系統殺死(若是沒有設置--00m-kill-disable=true
的話)。
-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。
-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。
-m,--memory=a
,--memory-swap=-1
給-m
參數設置一個正常值,而給--memory-swap
設置成 -1。這種狀況表示限制容器能使用的內存大小爲 a,而不限制容器能使用的 swap 分區大小。
這時候,容器內進程能申請到的內存大小爲 a + 宿主機的 swap 大小。
這種 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
確保容器不會長時間佔用太多內存。
默認狀況下,在出現 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
選項來設置在系統內存不夠時,容器被殺死的優先級。負值更教不可能被殺死,而正值更有可能被殺死。
核心內存和用戶內存不一樣的地方在於核心內存不能被交換出。不能交換出去的特性使得容器能夠經過消耗太多內存來堵塞一些系統服務。核心內存包括:
能夠經過設置核心內存限制來約束這些內存。例如,每一個進程都要消耗一些棧頁面,經過限制核心內存,能夠在覈心內存使用過多時阻止新進程被建立。
核心內存和用戶內存並非獨立的,必須在用戶內存限制的上下文中限制核心內存。
假設用戶內存的限制值爲 U,核心內存的限制值爲 K。有三種可能地限制核心內存的方式:
例如:
$ 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 核心內存。
默認狀況下,容器的內核能夠交換出必定比例的匿名頁。--memory-swappiness
就是用來設置這個比例的。--memory-swappiness
能夠設置爲從 0 到 100。0 表示關閉匿名頁面交換。100 表示全部的匿名頁均可以交換。默認狀況下,若是不適用--memory-swappiness
,則該值從父進程繼承而來。
例如:
$ docker run -it --memory-swappiness=0 ubuntu:16.04 /bin/bash
將--memory-swappiness
設置爲 0 能夠保持容器的工做集,避免交換代理的性能損失。
$ docker run -tid —name mem1 —memory 128m ubuntu:16.04 /bin/bash
$ cat /sys/fs/cgroup/memory/docker/<容器的完整ID>/memory.limit_in_bytes
$ cat /sys/fs/cgroup/memory/docker/<容器的完整ID>/memory.memsw.limit_in_bytes
Docker 的資源限制和隔離徹底基於 Linux cgroups。對 CPU 資源的限制方式也和 cgroups 相同。Docker 提供的 CPU 資源限制選項能夠在多核系統上限制容器能利用哪些 vCPU。而對容器最多能使用的 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 核上運行。
例如:
$ 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
$ cat /sys/fs/cgroup/cpuset/docker/<容器的完整長ID>/cpuset.cpus
表示容器中的進程能夠在 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、一、2 上的內存。
默認狀況下,全部的容器獲得同等比例的 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
$ docker run -it --cpu-shares=100 ubuntu:14.04 /bin/bash
$ cat /sys/fs/cgroup/cpu/docker/<容器的完整長ID>/cpu.shares
表示容器中的進程CPU份額值爲100。
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
$ cat /sys/fs/cgroup/cpu/docker/<容器的完整長ID>/cpu.cfs_period_us
$ cat /sys/fs/cgroup/cpu/docker/<容器的完整長ID>/cpu.cfs_quota_us
將容器的 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 時間是第一個容器的兩倍。
相對於CPU和內存的配額控制,docker對磁盤IO的控制相對不成熟,大多數都必須在有宿主機設備的狀況下使用。主要包括如下參數:
存儲配額控制的相關參數,能夠參考Red Hat文檔中blkio這一章,瞭解它們的詳細做用。
blkio-weight
要使–blkio-weight生效,須要保證IO的調度算法爲CFQ。可使用下面的方式查看:
root@ubuntu:~# cat /sys/block/sda/queue/scheduler
noop [deadline] cfq
使用下面的命令建立兩個–blkio-weight值不一樣的容器:
docker run -ti –rm –blkio-weight 100 ubuntu:stress
docker run -ti –rm –blkio-weight 1000 ubuntu:stress
在容器中同時執行下面的dd命令,進行測試:
time dd if=/dev/zero of=test.out bs=1M count=1024 oflag=direct
最終輸出以下圖所示:
在個人測試環境上沒有達到理想的測試效果,經過docker官方的blkio-weight doesn’t take effect in docker Docker version 1.8.1 #16173,能夠發現這個問題在一些環境上存在,但docker官方也沒有給出解決辦法。
device-write-bps
使用下面的命令建立容器,並執行命令驗證寫速度的限制。
docker run -tid –name disk1 –device-write-bps /dev/sda:1mb ubuntu:stress
經過dd來驗證寫速度,輸出以下圖示:
能夠看到容器的寫磁盤速度被成功地限制到了1MB/s。device-read-bps等其餘磁盤IO限制參數可使用相似的方式進行驗證。
在docker使用devicemapper做爲存儲驅動時,默認每一個容器和鏡像的最大大小爲10G。若是須要調整,能夠在daemon啓動參數中,使用dm.basesize來指定,但須要注意的是,修改這個值,不只僅須要重啓docker daemon服務,還會致使宿主機上的全部本地鏡像和容器都被清理掉。
使用aufs或者overlay等其餘存儲驅動時,沒有這個限制。