《被kill問題之1:進程物理內存遠大於Xmx的問題分析》
《被kill問題之2:Docker環境下Java應用的JVM設置(容器中的JVM資源該如何被安全的限制)》
問題
Java與Docker的結合,雖然更好的解決了application的封裝問題。但也存在着不兼容,比如Java並不能自動的發現Docker設置的內存限制,CPU限制。
這將導致JVM不能穩定服務業務!容器會殺死你JVM進程,而健康檢查又將拉起你的JVM進程,進而導致你監控你的pod一天重啟次數甚至能達到幾百次。
dmesg -T
[Fri Dec 6 08:02:46 2019] Out of memory: Kill process 10204 (java) score 70 or sacrifice child [Fri Dec 6 08:02:46 2019] Killed process 10204 (java) total-vm:13192148kB, anon-rss:2243220kB, file-rss:0kB, shmem-rss:0kB [Fri Dec 6 08:02:47 2019] docker0: port 13(veth9fbc66c) entered disabled state [Fri Dec 6 08:02:47 2019] docker0: port 13(veth9fbc66c) entered disabled state
理論解釋:
操作系統(operating system)構建在進程(process)的基礎上, 進程由內核作業(kernel jobs)進行調度和維護,其中有一個內核作業稱為 “Out of memory killer(OOM終結者)”,Out of memory killer 在可用內存極低的情況下會殺死某些進程。只要達到觸發條件就會激活,選中某個進程並殺掉。 通常采用啟發式算法,對所有進程計算評分(heuristics scoring),得分最低的進程將被 kill 掉。 Out of memory: Kill process or sacrifice child 既不由JVM觸發,也不由JVM代理,而是系統內核內置的一種安全保護措施。如果可用內存(含swap)不足, 就有可能會影響系統穩定,這時候 Out of memory killer 就會設法找出流氓進程並殺死他,也就是引起 Out of memory: kill process or sacrifice child 錯誤。
為什么設置了-XmX后,jvm被kill了?
docker鏡像的內存上限,不能全部給“-Xmx”。因為JVM消耗的內存不僅僅是Heap,看下圖:
通常情況下, JVM占用的內存不僅僅是-Xmx, -Xms等指定的堆大小, 因為JVM也是一個應用, 它需要額外的空間去完成它的工作, 除了堆外, JVM會分配內存的地方包括以下這些:
Metaspace: 元數據區, 存儲類, 及方法的元數據信息
Threads: 線程, 線程里的棧還是比較耗內存的, 在64位操作系統上, 默認棧的大小為1MB, 當然可以通過-Xss配置。因為一般情況下線程的數量是沒有限制的, 因此可能會占用極其多的內存。
Code Cache: JVM通過JIT把字節碼轉換成機器指令, 然后把這些指令放到一個非堆區域Code Cache。
概況一下,JVM占用的內存為:RSS = Heapsize + MetaSpace + OffHeap size (這里OffHeap由線程堆棧,緩沖區,庫(* .jars)和JVM代碼本身組成。)
根據JDK1.8的特點: JVM運行時的內存=非heap(非heap=元空間+Thread Stack * num of thread+…) + heap + JVM進程運行所需內存+其他數據
所以Xmx的值要小於鏡像上限內存。
當xmx的值與鏡像的上限內存很接近時,xmx的內存最大值很大,fullGC會被推遲,但是這個時候,jvm的總內存已經超過了鏡像上限,而被kill了。這就是為什么容器被kill了,但jvm沒有oom日志了。
怎么做?
我們希望當Java進程運行在容器中時,java能夠自動識別到容器限制,獲取到正確的內存和CPU信息,而不用每次都需要在kubernetes的yaml描述文件中顯示的配置完容器,還需要配置JVM參數。
使用JVM MaxRAM參數或者解鎖實驗特性的JVM參數,升級JDK到10+,我們可以解決這個問題(也許吧~.~)。
首先Docker容器本質是是宿主機上的一個進程,它與宿主機共享一個/proc目錄,也就是說我們在容器內看到的/proc/meminfo
,/proc/cpuinfo
與直接在宿主機上看到的一致,如下。
/ $ [ec2-user@ip-172-29-165-49 ~]$ cat /proc/meminfo MemTotal: 31960240 kB MemFree: 233200 kB MemAvailable: 4412724 kB
docker
[ec2-user@ip-172-29-165-49 ~]$ docker run -it --rm alpine cat /proc/meminfo Unable to find image 'alpine:latest' locally latest: Pulling from library/alpine 89d9c30c1d48: Pull complete Digest: sha256:c19173c5ada610a5989151111163d28a67368362762534d8a8121ce95cf2bd5a Status: Downloaded newer image for alpine:latest MemTotal: 31960240 kB MemFree: 278460 kB MemAvailable: 4351520 kB
那么Java是如何獲取到Host的內存信息的呢?沒錯就是通過/proc/meminfo
來獲取到的。
默認情況下,JVM的Max Heap Size是系統內存的1/4,假如我們系統是8G,那么JVM將的默認Heap≈2G。
Docker通過CGroups完成的是對內存的限制,而/proc目錄是已只讀形式掛載到容器中的,由於默認情況下Java
壓根就看不見CGroups的限制的內存大小,而默認使用/proc/meminfo
中的信息作為內存信息進行啟動,
這種不兼容情況會導致,如果容器分配的內存小於JVM的內存,JVM進程會被理解殺死。
讓 JVM 感知 cgroup 限制
前文提到還有另外一種方法解決 JVM 內存超限的問題,這種方法可以讓 JVM 自動感知 docker 容器的 cgroup 限制,從而動態的調整堆內存大小,感覺挺不錯的。我們也來試一下這種方法,看看效果如何 ; )
回到demo項目代碼的master分支,將 Dockerfile 中啟動命令參數的-Xmx256m替換為-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap,提交為useCGroupMemoryLimitForHeap分支,推送到代碼倉庫里。再次運行流水線進行構建部署。

等 demo 服務部署成功后,再次調用/allocateMemory接口,容器的內存占用情況如上圖所示(最右邊的那一部分連續曲線),內存上升到一定程度后,JVM 拋出了 OOM 錯誤,沒有繼續申請堆內存。看來這種方式也是有效果的。不過,仔細觀察容器的內存占用情況,可以發現容器所使用的內存僅為不到 300M,而我們對於這個容器的內存配額限制為 512M,也就是還有 200M+ 是閑置的,並不會被 JVM 利用。這個利用率,比起上文中直接設置-Xmx256m的內存利用率要低 : ( 。推測是因為 JVM 並不會感知到自己是部署在一個 docker 容器里的,所以它把當前的環境當成一個物理內存只有 512M 的物理機,按照比例來限制自己的最大堆內存,另一部分就被閑置了。
如此看來,如果想要充分利用自己的服務器資源,還是得多花一點功夫,手動調整好-Xmx參數。
來自網絡上的一組經驗值
以下參數配置適用於非計算密集型的大部分應用
分配內存 堆配置推薦
1.5G -Xmx1008M -Xms1008M -Xmn336M -XX:MaxMetaspaceSize=128M -XX:MetaspaceSize=128M
2G -Xmx1344M -Xms1344M -Xmn448M -XX:MaxMetaspaceSize=192M -XX:MetaspaceSize=192M
3G -Xmx2048M -Xms2048M -Xmn768M -XX:MaxMetaspaceSize=256M -XX:MetaspaceSize=256M
4G -Xmx2688M -Xms2688M -Xmn960M -XX:MaxMetaspaceSize=256M -XX:MetaspaceSize=256M
5G -Xmx3392M -Xms3392M -Xmn1216M -XX:MaxMetaspaceSize=512M -XX:MetaspaceSize=512M
6G -Xmx4096M -Xms4096M -Xmn1536M -XX:MaxMetaspaceSize=512M -XX:MetaspaceSize=512M
7G -Xmx4736M -Xms4736M -Xmn1728M -XX:MaxMetaspaceSize=512M -XX:MetaspaceSize=512M
8G -Xmx5440M -Xms5440M -XX:MaxMetaspaceSize=512M -XX:MetaspaceSize=512M
內存>=8G 基礎配置
-server
-XX:+DisableExplicitGC
-XX:+UseG1GC
-XX:MaxGCPauseMillis=100
-XX:+ParallelRefProcEnabled
-XX:+HeapDumpOnOutOfMemoryError
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCDateStamps
-XX:ErrorFile=/var/app/gc/hs_err_pid%p.log
-XX:HeapDumpPath=/var/app/gc
-Xloggc:/var/app/gc/gc%t.log
內存<8G 基礎配置
-server
-XX:+DisableExplicitGC
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=70
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
-XX:+CMSClassUnloadingEnabled
-XX:+ParallelRefProcEnabled
-XX:+CMSScavengeBeforeRemark
-XX:+HeapDumpOnOutOfMemoryError
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCDateStamps
-XX:ErrorFile=/var/app/gc/hs_err_pid%p.log
-XX:HeapDumpPath=/var/app/gc
-Xloggc:/var/app/gc/gc%t.log
----------------------------------------------------------------------------
內存限制
概述
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
- 1
如果容器使用了大於 200M 但小於 500M 內存時,下次系統的內存回收會嘗試將容器的內存鎖緊到 200M 以下。
例如:
$ docker run -it --memory-reservation 1G ubuntu:16.04 /bin/bash
- 1
容器可以使用盡可能多的內存。–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
- 1
是正確的使用方法。
而下面這個容器沒設置內存限制,卻禁用了 OOM killer 是非常危險的:
$ docker run -it --oom-kill-disable ubuntu:16.04 /bin/bash
- 1
容器沒用內存限制,可能或導致系統無內存可用,並嘗試時殺死系統進程來獲取更多可用內存。
一般一個容器只有一個進程,這個唯一進程被殺死,容器也就被殺死了。我們可以通過–oom-score-adj選項來設置在系統內存不夠時,容器被殺死的優先級。負值更教不可能被殺死,而正值更有可能被殺死。
核心內存
核心內存和用戶內存不同的地方在於核心內存不能被交換出。不能交換出去的特性使得容器可以通過消耗太多內存來堵塞一些系統服務。核心內存包括:
1)stack pages(棧頁面)
2)slab pages
3)socket memory pressure
4)tcp memory pressure
可以通過設置核心內存限制來約束這些內存。例如,每個進程都要消耗一些棧頁面,通過限制核心內存,可以在核心內存使用過多時阻止新進程被創建。
核心內存和用戶內存並不是獨立的,必須在用戶內存限制的上下文中限制核心內存。
假設用戶內存的限制值為 U,核心內存的限制值為 K。有三種可能地限制核心內存的方式:
1)U != 0,不限制核心內存。這是默認的標准設置方式
2)K < U,核心內存時用戶內存的子集。這種設置在部署時,每個 cgroup 的內存總量被過度使用。過度使用核心內存限制是絕不推薦的,因為系統還是會用完不能回收的內存。在這種情況下,你可以設置 K,這樣 groups 的總數就不會超過總內存了。然后,根據系統服務的質量自有地設置 U。
3)K > U,因為核心內存的變化也會導致用戶計數器的變化,容器核心內存和用戶內存都會觸發回收行為。這種配置可以讓管理員以一種統一的視圖看待內存。對想跟蹤核心內存使用情況的用戶也是有用的。
例如:
$ docker run -it -m 500M --kernel-memory 50M ubuntu:16.04 /bin/bash
- 1
容器中的進程最多能使用 500M 內存,在這 500M 中,最多只有 50M 核心內存。
$ docker run -it --kernel-memory 50M ubuntu:16.04 /bin/bash
- 1
沒用設置用戶內存限制,所以容器中的進程可以使用盡可能多的內存,但是最多能使用 50M 核心內存。
Swappiness
默認情況下,容器的內核可以交換出一定比例的匿名頁。–memory-swappiness就是用來設置這個比例的。–memory-swappiness可以設置為從 0 到 100。0 表示關閉匿名頁面交換。100 表示所有的匿名頁都可以交換。默認情況下,如果不適用–memory-swappiness,則該值從父進程繼承而來。
例如:
$ docker run -it --memory-swappiness=0 ubuntu:16.04 /bin/bash
- 1
將–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
$ 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、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
$ docker run -it --cpu-shares=100 ubuntu:14.04 /bin/bash
$ cat /sys/fs/cgroup/cpu/docker/<容器的完整長ID>/cpu.shares
表示容器中的進程CPU份額值為100。
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
$ 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 時間是第一個容器的兩倍。
磁盤IO配額控制
相對於CPU和內存的配額控制,docker對磁盤IO的控制相對不成熟,大多數都必須在有宿主機設備的情況下使用。主要包括以下參數:
- –device-read-bps:限制此設備上的讀速度(bytes per second),單位可以是kb、mb或者gb。
- –device-read-iops:通過每秒讀IO次數來限制指定設備的讀速度。
- –device-write-bps :限制此設備上的寫速度(bytes per second),單位可以是kb、mb或者gb。
- –device-write-iops:通過每秒寫IO次數來限制指定設備的寫速度。
- –blkio-weight:容器默認磁盤IO的加權值,有效值范圍為10-100。
- –blkio-weight-device: 針對特定設備的IO加權控制。其格式為DEVICE_NAME:WEIGHT
存儲配額控制的相關參數,可以參考Red Hat文檔中blkio這一章,了解它們的詳細作用。
磁盤IO配額控制示例
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等其他存儲驅動時,沒有這個限制。
----------------------------------------------------------------------------