目錄
- 前言
- 一、使用內(nèi)存限制
- 1. 不設(shè)置
- 2. 設(shè)置-m,–memory,不設(shè)置–memory-swap
- 3. 設(shè)置-m,–memory=a,–memory-swap=b,且b > a
- 4. 設(shè)置-m,–memory=a,–memory-swap=-1
- 二、Memory reservation
- 三、OOM killer
- 四、kernel-memory核心內(nèi)存限制
- 五、驗證
- 總結(jié)
前言
本文介紹如何通過docker運(yùn)行參數(shù)配置限制docker容器可以使用的內(nèi)存上限。docker容器默認(rèn)可以使用全部宿主機(jī)的所有內(nèi)存和 swap 分區(qū),比如宿主機(jī)的內(nèi)存是32G,則運(yùn)行一個docker容器最多可以分配到32G內(nèi)存,如果啟用了多個docker容器,則很快宿主機(jī)的內(nèi)存就耗盡了。
內(nèi)存限制相關(guān)參數(shù)如下:
選項 |
描述 |
-m,–memory |
內(nèi)存限制,格式是數(shù)字加單位,單位可以為 b,k,m,g。最小為 4M |
–memory-swap |
內(nèi)存+交換分區(qū)大小總限制。格式同上。必須必-m設(shè)置的大 |
–memory-reservation |
內(nèi)存的軟性限制。格式同上 |
–oom-kill-disable |
是否阻止 OOM killer 殺死容器,默認(rèn)沒設(shè)置 |
–oom-score-adj |
容器被 OOM killer 殺死的優(yōu)先級,范圍是[-1000, 1000],默認(rèn)為 0 |
–memory-swappiness |
用于設(shè)置容器的虛擬內(nèi)存控制行為。值為 0~100 之間的整數(shù) |
–kernel-memory |
核心內(nèi)存限制。格式同上,最小為 4M |
一、使用內(nèi)存限制
用戶內(nèi)存限制就是對容器能使用的內(nèi)存和交換分區(qū)的大小作出限制。使用時要遵循兩條直觀的規(guī)則:-m,–memory選項的參數(shù)最小為 4 M。–memory-swap不是交換分區(qū),而是內(nèi)存加交換分區(qū)的總大小,所以–memory-swap必須比-m,–memory大。在這兩條規(guī)則下,一般有四種設(shè)置方式。
你可能在進(jìn)行內(nèi)存限制的實驗時發(fā)現(xiàn) docker run命令報錯:WARNING: Your kernel does not support swap limit capabilities, memory limited without swap.
這是因為宿主機(jī)內(nèi)核的相關(guān)功能沒有打開。按照下面的設(shè)置就行。
step 1:編輯 /etc/default/grub文件,將 GRUB_CMDLINE_LINUX一行改為 GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"
step 2:更新 GRUB,即執(zhí)行 $ sudo update-grub
step 3: 重啟系統(tǒng)。
1. 不設(shè)置
如果不設(shè)置-m,–memory和–memory-swap,容器默認(rèn)可以用完宿舍機(jī)的所有內(nèi)存和 swap 分區(qū)。不過注意,如果容器占用宿主機(jī)的所有內(nèi)存和 swap 分區(qū)超過一段時間后,會被宿主機(jī)系統(tǒng)殺死(如果沒有設(shè)置–00m-kill-disable=true的話)。
2. 設(shè)置-m,–memory,不設(shè)置–memory-swap
給-m或–memory設(shè)置一個不小于 4M 的值,假設(shè)為 a,不設(shè)置–memory-swap,或?qū)?#8211;memory-swap設(shè)置為 0。這種情況下,容器能使用的內(nèi)存大小為 a,能使用的交換分區(qū)大小也為 a。因為 Docker 默認(rèn)容器交換分區(qū)的大小和內(nèi)存相同。
如果在容器中運(yùn)行一個一直不停申請內(nèi)存的程序,你會觀察到該程序最終能占用的內(nèi)存大小為 2a。
比如$ docker run -m 1G ubuntu:16.04,該容器能使用的內(nèi)存大小為 1G,能使用的 swap 分區(qū)大小也為 1G。容器內(nèi)的進(jìn)程能申請到的總內(nèi)存大小為 2G。
3. 設(shè)置-m,–memory=a,–memory-swap=b,且b > a
給-m設(shè)置一個參數(shù) a,給–memory-swap設(shè)置一個參數(shù) b。a 時容器能使用的內(nèi)存大小,b是容器能使用的 內(nèi)存大小 + swap 分區(qū)大小。所以 b 必須大于 a。b -a 即為容器能使用的 swap 分區(qū)大小。
比如$ docker run -m 1G –memory-swap 3G ubuntu:16.04,該容器能使用的內(nèi)存大小為 1G,能使用的 swap 分區(qū)大小為 2G。容器內(nèi)的進(jìn)程能申請到的總內(nèi)存大小為 3G。
4. 設(shè)置-m,–memory=a,–memory-swap=-1
給-m參數(shù)設(shè)置一個正常值,而給–memory-swap設(shè)置成 -1。這種情況表示限制容器能使用的內(nèi)存大小為 a,而不限制容器能使用的 swap 分區(qū)大小。
這時候,容器內(nèi)進(jìn)程能申請到的內(nèi)存大小為 a + 宿主機(jī)的 swap 大小。
二、Memory reservation
這種 memory reservation 機(jī)制不知道怎么翻譯比較形象。Memory reservation 是一種軟性限制,用于節(jié)制容器內(nèi)存使用。給–memory-reservation設(shè)置一個比-m小的值后,雖然容器最多可以使用-m使用的內(nèi)存大小,但在宿主機(jī)內(nèi)存資源緊張時,在系統(tǒng)的下次內(nèi)存回收時,系統(tǒng)會回收容器的部分內(nèi)存頁,強(qiáng)迫容器的內(nèi)存占用回到–memory-reservation設(shè)置的值大小。
沒有設(shè)置時(默認(rèn)情況下)–memory-reservation的值和-m的限定的值相同。將它設(shè)置為 0 會設(shè)置的比-m的參數(shù)大 等同于沒有設(shè)置。
Memory reservation 是一種軟性機(jī)制,它不保證任何時刻容器使用的內(nèi)存不會超過–memory-reservation限定的值,它只是確保容器不會長時間占用超過–memory-reservation限制的內(nèi)存大小。
例如:
$ docker run -it -m 500M --memory-reservation 200M ubuntu:16.04 /bin/bash1
如果容器使用了大于 200M 但小于 500M 內(nèi)存時,下次系統(tǒng)的內(nèi)存回收會嘗試將容器的內(nèi)存鎖緊到 200M 以下。
例如:
$ docker run -it--memory-reservation 1G ubuntu:16.04 /bin/bash1
容器可以使用盡可能多的內(nèi)存。–memory-reservation確保容器不會長時間占用太多內(nèi)存。
三、OOM killer
默認(rèn)情況下,在出現(xiàn) out-of-memory(OOM) 錯誤時,系統(tǒng)會殺死容器內(nèi)的進(jìn)程來獲取更多空閑內(nèi)存。這個殺死進(jìn)程來節(jié)省內(nèi)存的進(jìn)程,我們姑且叫它 OOM killer。我們可以通過設(shè)置–oom-kill-disable選項來禁止 OOM killer 殺死容器內(nèi)進(jìn)程。但請確保只有在使用了-m/–memory選項時才使用–oom-kill-disable禁用 OOM killer。如果沒有設(shè)置-m選項,卻禁用了 OOM-killer,可能會造成出現(xiàn) out-of-memory 錯誤時,系統(tǒng)通過殺死宿主機(jī)進(jìn)程或獲取更改內(nèi)存。
下面的例子限制了容器的內(nèi)存為 100M 并禁止了 OOM killer:
$ docker run -it-m100M --oom-kill-disable ubuntu:16.04 /bin/bash1
是正確的使用方法。
而下面這個容器沒設(shè)置內(nèi)存限制,卻禁用了 OOM killer 是非常危險的:
$ docker run -it--oom-kill-disable ubuntu:16.04 /bin/bash1
容器沒用內(nèi)存限制,可能或?qū)е孪到y(tǒng)無內(nèi)存可用,并嘗試時殺死系統(tǒng)進(jìn)程來獲取更多可用內(nèi)存。
一般一個容器只有一個進(jìn)程,這個唯一進(jìn)程被殺死,容器也就被殺死了。我們可以通過–oom-score-adj選項來設(shè)置在系統(tǒng)內(nèi)存不夠時,容器被殺死的優(yōu)先級。負(fù)值更教不可能被殺死,而正值更有可能被殺死。
四、kernel-memory核心內(nèi)存限制
核心內(nèi)存和用戶內(nèi)存不同的地方在于核心內(nèi)存不能被交換出。不能交換出去的特性使得容器可以通過消耗太多內(nèi)存來堵塞一些系統(tǒng)服務(wù)。核心內(nèi)存包括:
- stack pages(棧頁面)
- slab pages
- socket memory pressure
- tcp memory pressure
可以通過設(shè)置核心內(nèi)存限制來約束這些內(nèi)存。例如,每個進(jìn)程都要消耗一些棧頁面,通過限制核心內(nèi)存,可以在核心內(nèi)存使用過多時阻止新進(jìn)程被創(chuàng)建。
核心內(nèi)存和用戶內(nèi)存并不是獨(dú)立的,必須在用戶內(nèi)存限制的上下文中限制核心內(nèi)存。
假設(shè)用戶內(nèi)存的限制值為 U,核心內(nèi)存的限制值為 K。有三種可能地限制核心內(nèi)存的方式:
- U != 0,不限制核心內(nèi)存。這是默認(rèn)的標(biāo)準(zhǔn)設(shè)置方式
- K < U,核心內(nèi)存時用戶內(nèi)存的子集。這種設(shè)置在部署時,每個 cgroup 的內(nèi)存總量被過度使用。過度使用核心內(nèi)存限制是絕不推薦的,因為系統(tǒng)還是會用完不能回收的內(nèi)存。在這種情況下,你可以設(shè)置 K,這樣 groups 的總數(shù)就不會超過總內(nèi)存了。然后,根據(jù)系統(tǒng)服務(wù)的質(zhì)量自有地設(shè)置 U。
- K > U,因為核心內(nèi)存的變化也會導(dǎo)致用戶計數(shù)器的變化,容器核心內(nèi)存和用戶內(nèi)存都會觸發(fā)回收行為。這種配置可以讓管理員以一種統(tǒng)一的視圖看待內(nèi)存。對想跟蹤核心內(nèi)存使用情況的用戶也是有用的。
例如:
$ docker run -it -m 500M --kernel-memory 50M ubuntu:16.04 /bin/bash1
容器中的進(jìn)程最多能使用 500M 內(nèi)存,在這 500M 中,最多只有 50M 核心內(nèi)存。
$ docker run -it--kernel-memory 50M ubuntu:16.04 /bin/bash1
沒用設(shè)置用戶內(nèi)存限制,所以容器中的進(jìn)程可以使用盡可能多的內(nèi)存,但是最多能使用 50M 核心內(nèi)存。
五、驗證
可以通過docker stats命令查看docker容器的內(nèi)存使用情況,來驗證內(nèi)存設(shè)置是否生效。可以看到容器默認(rèn)可使用的最大內(nèi)存上限與宿主機(jī)內(nèi)存一致,設(shè)置–memory 1G參數(shù)后,最大可以使用的內(nèi)存被限制為1G。
docker stats