深入剖析 Linux Cgroups 子系统:资源精细管理
本章主要演示以下 cgroups 下各個(gè) subsystem 的作用。
根據(jù)難易程度,依次演示了 pids 、cpu 和 memory 3 個(gè) subsystem 的使用。
注:本文所有操作在 Ubuntu20.04 下進(jìn)行。
如果你對(duì)云原生技術(shù)充滿好奇,想要深入了解更多相關(guān)的文章和資訊,歡迎關(guān)注微信公眾號(hào)。
搜索公眾號(hào)【探索云原生】即可訂閱
1. pids
pids subsystem 功能是限制 cgroup 及其所有子孫 cgroup 里面能創(chuàng)建的總的 task 數(shù)量。
注意:這里的 task 指通過(guò) fork 和 clone 函數(shù)創(chuàng)建的進(jìn)程,由于 clone 函數(shù)也能創(chuàng)建線程(在 Linux 里面,線程是一種特殊的進(jìn)程),所以這里的 task 也包含線程。
本文統(tǒng)一以進(jìn)程來(lái)代表 task,即本文中的進(jìn)程代表了進(jìn)程和線程>
創(chuàng)建子 cgroup
創(chuàng)建子 cgroup,取名為 test
#進(jìn)入目錄/sys/fs/cgroup/pids/并新建一個(gè)目錄,即創(chuàng)建了一個(gè)子cgroup
lixd /home/lixd $ cd /sys/fs/cgroup/pids
lixd /sys/fs/cgroup/pids $ sudo mkdir test
再來(lái)看看 test 目錄下的文件
lixd /sys/fs/cgroup/pids $ cd test
#除了上一篇中介紹的那些文件外,多了兩個(gè)文件
lixd /sys/fs/cgroup/pids/test $ ls
cgroup.clone_children cgroup.procs notify_on_release pids.current pids.events pids.max tasks
下面是這兩個(gè)文件的含義:
- pids.current: 表示當(dāng)前 cgroup 及其所有子孫 cgroup 中現(xiàn)有的總的進(jìn)程數(shù)量
- pids.max: 當(dāng)前 cgroup 及其所有子孫 cgroup 中所允許創(chuàng)建的總的最大進(jìn)程數(shù)量
限制進(jìn)程數(shù)
首先是將當(dāng)前 bash 加入到 cgroup 中,并修改pids.max的值,為了便于測(cè)試,這里就限制為 1:
#--------------------------第一個(gè)shell窗口----------------------
# 將當(dāng)前bash進(jìn)程加入到該cgroup
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/pids/test# echo $$ > cgroup.procs
#將pids.max設(shè)置為1,即當(dāng)前cgroup只允許有一個(gè)進(jìn)程
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/pids/test# echo 1 > pids.max
由于 bash 已經(jīng)占用了一個(gè)進(jìn)程,所以此時(shí) bash 中已經(jīng)無(wú)法創(chuàng)建新的進(jìn)程了:
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/pids/test# ls
bash: fork: retry: Resource temporarily unavailable
bash: fork: retry: Resource temporarily unavailable
bash: fork: retry: Resource temporarily unavailable
創(chuàng)建新進(jìn)程失敗,于是命令運(yùn)行失敗,說(shuō)明限制生效。
打開(kāi)另一個(gè) shell 查看
lixd /mnt/c/Users/意琦行 $ cd /sys/fs/cgroup/pids/test
lixd /sys/fs/cgroup/pids/test $ ls
cgroup.clone_children cgroup.procs notify_on_release pids.current pids.events pids.max tasks
lixd /sys/fs/cgroup/pids/test $ cat pids.current
1
果然,pids.current 為 1,已經(jīng)到 pids.max 的限制了。
當(dāng)前 cgroup 和子 cgroup 之間的關(guān)系
當(dāng)前 cgroup 中的 pids.current 和 pids.max 代表了當(dāng)前 cgroup 及所有子孫 cgroup 的所有進(jìn)程,所以子孫 cgroup 中的 pids.max 大小不能超過(guò)父 cgroup。
如果子 cgroup 中的 pids.max 設(shè)置的大于父 cgroup 里的值,會(huì)怎么樣?
答案是子 cgroup 中的進(jìn)程不光受子 cgroup 限制,還要受其父 cgroup 的限制。
#繼續(xù)使用上面的兩個(gè)窗口
#--------------------------第二個(gè)shell窗口----------------------
#將pids.max設(shè)置成2
dev@dev:/sys/fs/cgroup/pids/test$ echo 2 > pids.max
#在test下面創(chuàng)建一個(gè)子cgroup
dev@dev:/sys/fs/cgroup/pids/test$ mkdir subtest
dev@dev:/sys/fs/cgroup/pids/test$ cd subtest/
#將subtest的pids.max設(shè)置為5
dev@dev:/sys/fs/cgroup/pids/test/subtest$ echo 5 > pids.max
#將當(dāng)前bash進(jìn)程加入到subtest中
dev@dev:/sys/fs/cgroup/pids/test/subtest$ echo $$ > cgroup.procs
#--------------------------第三個(gè)shell窗口----------------------
#重新打開(kāi)一個(gè)bash窗口,看一下test和subtest里面的數(shù)據(jù)
#test里面的數(shù)據(jù)如下:
dev@dev:~$ cd /sys/fs/cgroup/pids/test
dev@dev:/sys/fs/cgroup/pids/test$ cat pids.max
2
#這里為2表示目前test和subtest里面總的進(jìn)程數(shù)為2
dev@dev:/sys/fs/cgroup/pids/test$ cat pids.current
2
dev@dev:/sys/fs/cgroup/pids/test$ cat cgroup.procs
3083
#subtest里面的數(shù)據(jù)如下:
dev@dev:/sys/fs/cgroup/pids/test$ cat subtest/pids.max
5
dev@dev:/sys/fs/cgroup/pids/test$ cat subtest/pids.current
1
dev@dev:/sys/fs/cgroup/pids/test$ cat subtest/cgroup.procs
3185
#--------------------------第一個(gè)shell窗口----------------------
#回到第一個(gè)窗口,隨便運(yùn)行一個(gè)命令,由于test里面的pids.current已經(jīng)等于pids.max了,
#所以創(chuàng)建新進(jìn)程失敗,于是命令運(yùn)行失敗,說(shuō)明限制生效
dev@dev:/sys/fs/cgroup/pids/test$ ls
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: Resource temporarily unavailable
#--------------------------第二個(gè)shell窗口----------------------
#回到第二個(gè)窗口,隨便運(yùn)行一個(gè)命令,雖然subtest里面的pids.max還大于pids.current,
#但由于其父cgroup “test”里面的pids.current已經(jīng)等于pids.max了,
#所以創(chuàng)建新進(jìn)程失敗,于是命令運(yùn)行失敗,說(shuō)明子cgroup中的進(jìn)程數(shù)不僅受自己的pids.max的限制,還受祖先cgroup的限制
dev@dev:/sys/fs/cgroup/pids/test/subtest$ ls
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: Resource temporarily unavailable
pids.current > pids.max 的情況
并不是所有情況下都是 pids.max >= pids.current,在下面兩種情況下,會(huì)出現(xiàn) pids.max < pids.current 的情況:
- 設(shè)置 pids.max 時(shí),將其值設(shè)置的比 pids.current 小
- 將其他進(jìn)程加入到當(dāng)前 cgroup 有可能會(huì)導(dǎo)致 pids.current > pids.max
- 因?yàn)?pids.max 只會(huì)在當(dāng)前 cgroup 中的進(jìn)程 fork、clone 的時(shí)候生效,將其他進(jìn)程加入到當(dāng)前 cgroup 時(shí),不會(huì)檢測(cè) pids.max,所以可能觸發(fā)這種情況
小結(jié)
作用:pids subsystem 用于限制 cgroups 下能夠創(chuàng)建的 task(進(jìn)程和線程)數(shù)。
原理:在調(diào)用 fork 和 clone 時(shí)對(duì)比 subsystem 中配置的 pids.max 和 pids.current 值來(lái)判斷當(dāng)前是否能夠繼續(xù)創(chuàng)建 task。
用法:配置 pids.max 防止容器消耗完 pid。
2. cpu
在 cgroup 里面,跟 CPU 相關(guān)的子系統(tǒng)有 cpusets、cpuacct 和 cpu。
-
其中 cpuset 主要用于設(shè)置 CPU 的親和性,可以限制 cgroup 中的進(jìn)程只能在指定的 CPU 上運(yùn)行,或者不能在指定的 CPU 上運(yùn)行,同時(shí) cpuset 還能設(shè)置內(nèi)存的親和性。設(shè)置親和性一般只在比較特殊的情況才用得著,所以這里不做介紹。
-
cpuacct 包含當(dāng)前 cgroup 所使用的 CPU 的統(tǒng)計(jì)信息,信息量較少,有興趣可以去看看它的文檔,這里不做介紹。
本節(jié)只介紹 cpu 子系統(tǒng),包括怎么限制 cgroup 的 CPU 使用上限及相對(duì)于其它 cgroup 的相對(duì)值。
創(chuàng)建子 cgroup
通用是創(chuàng)建子目錄即可。
#進(jìn)入/sys/fs/cgroup/cpu并創(chuàng)建子cgroup
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/cpu# cd /sys/fs/cgroup/cpu
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/cpu# mkdir test
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/cpu# cd test/
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/cpu/test# ls
cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release
cgroup.procs cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks
看起來(lái)文件比 memory subsystem 還是少一些。
cpu.cfs_period_us & cpu.cfs_quota_us:兩個(gè)文件配合起來(lái)設(shè)置 CPU 的使用上限,兩個(gè)文件的單位都是微秒(us)。
- cfs_period_us:用來(lái)配置時(shí)間周期長(zhǎng)度
- 取值范圍為 1 毫秒(ms)到 1 秒(s)
- cfs_quota_us:用來(lái)配置當(dāng)前 cgroup 在設(shè)置的周期長(zhǎng)度內(nèi)所能使用的 CPU 時(shí)間數(shù)
- 取值大于 1ms 即可
- 默認(rèn)值為 -1,表示不受 cpu 時(shí)間的限制。
cpu.shares 用來(lái)設(shè)置 CPU 的相對(duì)值(比例),并且是針對(duì)所有的 CPU(內(nèi)核),默認(rèn)值是 1024。
假如系統(tǒng)中有兩個(gè) cgroup,分別是 A 和 B,A 的 shares 值是 1024,B 的 shares 值是 512,那么 A 將獲得 1024/(1204+512)=66% 的 CPU 資源,而 B 將獲得 33% 的 CPU 資源。
shares 有兩個(gè)特點(diǎn):
- 如果 A 不忙,沒(méi)有使用到 66% 的 CPU 時(shí)間,那么剩余的 CPU 時(shí)間將會(huì)被系統(tǒng)分配給 B,即 B 的 CPU 使用率可以超過(guò) 33%
- 如果添加了一個(gè)新的 cgroup C,且它的 shares 值是 1024,那么 A 的限額變成了 1024/(1204+512+1024)=40%,B 的變成了 20%
從上面兩個(gè)特點(diǎn)可以看出:
- 在閑的時(shí)候,shares 基本上不起作用,只有在 CPU 忙的時(shí)候起作用,這是一個(gè)優(yōu)點(diǎn)。
- 由于 shares 是一個(gè)絕對(duì)值,需要和其它 cgroup 的值進(jìn)行比較才能得到自己的相對(duì)限額,而在一個(gè)部署很多容器的機(jī)器上,cgroup 的數(shù)量是變化的,所以這個(gè)限額也是變化的,自己設(shè)置了一個(gè)高的值,但別人可能設(shè)置了一個(gè)更高的值,所以這個(gè)功能沒(méi)法精確的控制 CPU 使用率。
cpu.stat 包含了下面三項(xiàng)統(tǒng)計(jì)結(jié)果:
- nr_periods: 表示過(guò)去了多少個(gè) cpu.cfs_period_us 里面配置的時(shí)間周期
- nr_throttled: 在上面的這些周期中,有多少次是受到了限制(即 cgroup 中的進(jìn)程在指定的時(shí)間周期中用光了它的配額)
- throttled_time: cgroup 中的進(jìn)程被限制使用 CPU 持續(xù)了多長(zhǎng)時(shí)間(納秒)
原理
前面配置的參數(shù)都是 cfs_xxx,這里的 cfs 是 Completely Fair Scheduler 的縮寫(xiě)。
CFS 是 Linux 內(nèi)核中的調(diào)度器,它負(fù)責(zé)決定哪個(gè)進(jìn)程在給定時(shí)間片內(nèi)運(yùn)行。CFS 使用 CFS 配額(cpu.cfs_quota_us)和 CFS 周期(cpu.cfs_period_us)來(lái)限制每個(gè) cgroup 中的 CPU 使用。
CFS 的實(shí)現(xiàn)與 cgroups 協(xié)同工作,它負(fù)責(zé)追蹤每個(gè) cgroup 中的進(jìn)程消耗的 CPU 時(shí)間,并在每個(gè)調(diào)度周期結(jié)束時(shí)根據(jù) cgroup 的 CPU 配額調(diào)整進(jìn)程的運(yùn)行時(shí)間。
如果一個(gè) cgroup 中的進(jìn)程在調(diào)度周期內(nèi)超過(guò)了它的 CPU 配額,它將被調(diào)度器限制,從而實(shí)現(xiàn)了 CPU 的使用限制。
即:cgroups 中的 subsystem 負(fù)責(zé)提供配置,cfs 負(fù)責(zé)記錄進(jìn)程使用的 cpu 時(shí)間,達(dá)到閾值后就從調(diào)度層面進(jìn)行限制,避免該進(jìn)程繼續(xù)使用 cpu。
演示
#繼續(xù)使用上面創(chuàng)建的子cgroup: test
#設(shè)置只能使用1個(gè)cpu的20%的時(shí)間
dev@ubuntu:/sys/fs/cgroup/cpu,cpuacct/test$ sudo sh -c "echo 50000 > cpu.cfs_period_us"
dev@ubuntu:/sys/fs/cgroup/cpu,cpuacct/test$ sudo sh -c "echo 10000 > cpu.cfs_quota_us"
#將當(dāng)前bash加入到該cgroup
dev@ubuntu:/sys/fs/cgroup/cpu,cpuacct/test$ echo $$
5456
dev@ubuntu:/sys/fs/cgroup/cpu,cpuacct/test$ sudo sh -c "echo 5456 > cgroup.procs"
#在bash中啟動(dòng)一個(gè)死循環(huán)來(lái)消耗cpu,正常情況下應(yīng)該使用100%的cpu(即消耗一個(gè)內(nèi)核)
dev@ubuntu:/sys/fs/cgroup/cpu,cpuacct/test$ while :; do echo test > /dev/null; done
#--------------------------重新打開(kāi)一個(gè)shell窗口----------------------
#通過(guò)top命令可以看到5456的CPU使用率為20%左右,說(shuō)明被限制住了
#不過(guò)這時(shí)系統(tǒng)的%us+%sy在10%左右,那是因?yàn)槲覝y(cè)試的機(jī)器上cpu是雙核的,
#所以系統(tǒng)整體的cpu使用率為10%左右
dev@ubuntu:~$ top
Tasks: 139 total, 2 running, 137 sleeping, 0 stopped, 0 zombie
%Cpu(s): 5.6 us, 6.2 sy, 0.0 ni, 88.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 499984 total, 15472 free, 81488 used, 403024 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 383332 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
5456 dev 20 0 22640 5472 3524 R 20.3 1.1 0:04.62 bash
#這時(shí)可以看到被限制的統(tǒng)計(jì)結(jié)果
dev@ubuntu:~$ cat /sys/fs/cgroup/cpu,cpuacct/test/cpu.stat
nr_periods 1436
nr_throttled 1304
throttled_time 51542291833
# cfs_period_us 值為 10W
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/cpu/test# cat cpu.cfs_period_us
100000
# 往 cfs_quota_us 寫(xiě)入 20000,即限制只能使用20%cpu
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/cpu/test# echo 20000 > cpu.cfs_quota_us
# 新開(kāi)一個(gè)窗口,運(yùn)行一個(gè)死循環(huán)
$ while : ; do : ; done &
[1] 519
# top 看一下 cpu 占用率,果然是100%了
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
519 lixd 25 5 13444 2912 0 R 100.0 0.0 0:05.66 zsh
# 回到第一個(gè)shell窗口,限制當(dāng)前進(jìn)程的cpu使用率
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/cpu/test# echo 519 >> cgroup.procs
# 再切回第二個(gè)窗口,發(fā)現(xiàn)519進(jìn)程的cpu已經(jīng)降到20%了,說(shuō)明限制生效了
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
519 lixd 25 5 13444 2912 0 R 20.0 0.0 0:31.86 zsh
# 查看被限制的統(tǒng)計(jì)結(jié)果
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/cpu/test# cat cpu.stat
nr_periods 2090
nr_throttled 2088
throttled_time 166752684900
小結(jié)
作用:cpu subsystem 用于限制 cgroups 下進(jìn)程可以使用的 cpu 上限。
原理:cgroups 中的 subsystem 負(fù)責(zé)提供配置,cfs 負(fù)責(zé)記錄進(jìn)程使用的 cpu 時(shí)間,達(dá)到閾值后就從調(diào)度層面進(jìn)行限制,避免該進(jìn)程繼續(xù)使用 cpu。
用法:
- 1)限制為具體值:用 cfs_period_us & cfs_quota_us 兩個(gè)配置可以嚴(yán)格限制進(jìn)程 cpu 使用量。
- 2)按比例分配:用 shares 配置,可以使得多個(gè) cgroups 之間按比例分配所有 cpu。
3. memory
memory subsystem 顧名思義,限制 cgroups 中進(jìn)程的內(nèi)存使用。
為什么需要內(nèi)存控制
- 站在一個(gè)普通開(kāi)發(fā)者的角度,如果能控制一個(gè)或者一組進(jìn)程所能使用的內(nèi)存數(shù),那么就算代碼有 bug,內(nèi)存泄漏也不會(huì)對(duì)系統(tǒng)造成影響,因?yàn)榭梢栽O(shè)置內(nèi)存使用量的上限,當(dāng)?shù)竭_(dá)這個(gè)值之后可以將進(jìn)程重啟。
- 站在一個(gè)系統(tǒng)管理者的角度,如果能限制每組進(jìn)程所能使用的內(nèi)存量,那么不管程序的質(zhì)量如何,都能將它們對(duì)系統(tǒng)的影響降到最低,從而保證整個(gè)系統(tǒng)的穩(wěn)定性。
內(nèi)存控制能控制些什么?
- 限 制 cgroup 中所有進(jìn)程所能使用的物理內(nèi)存總量
- 限制 cgroup 中所有進(jìn)程所能使用的物理內(nèi)存+交換空間總量(CONFIG_MEMCG_SWAP): 一般在 server 上,不太會(huì)用到 swap 空間,所以不在這里介紹這部分內(nèi)容。
- 限制 cgroup 中所有進(jìn)程所能使用的內(nèi)核內(nèi)存總量及其它一些內(nèi)核資源(CONFIG_MEMCG_KMEM): 限制內(nèi)核內(nèi)存有什么用呢?其實(shí)限制內(nèi)核內(nèi)存就是限制當(dāng)前 cgroup 所能使用的內(nèi)核資源,比如進(jìn)程的內(nèi)核棧空間,socket 所占用的內(nèi)存空間等,通過(guò)限制內(nèi)核內(nèi)存,當(dāng)內(nèi)存吃緊時(shí),可以阻止當(dāng)前 cgroup 繼續(xù)創(chuàng)建進(jìn)程以及向內(nèi)核申請(qǐng)分配更多的內(nèi)核資源。由于這塊功能被使用的較少,本篇中也不對(duì)它做介紹。
創(chuàng)建子 cgroup
在 /sys/fs/cgroup/memory 下創(chuàng)建一個(gè)子目錄就算是創(chuàng)建了一個(gè)子 cgroup
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory# cd /sys/fs/cgroup/memory
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory# mkdir test
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory# ls test/
cgroup.clone_children memory.kmem.tcp.max_usage_in_bytes memory.oom_control
cgroup.event_control memory.kmem.tcp.usage_in_bytes memory.pressure_level
cgroup.procs memory.kmem.usage_in_bytes memory.soft_limit_in_bytes
memory.failcnt memory.limit_in_bytes memory.stat
memory.force_empty memory.max_usage_in_bytes memory.swappiness
memory.kmem.failcnt memory.memsw.failcnt memory.usage_in_bytes
memory.kmem.limit_in_bytes memory.memsw.limit_in_bytes memory.use_hierarchy
memory.kmem.max_usage_in_bytes memory.memsw.max_usage_in_bytes notify_on_release
memory.kmem.tcp.failcnt memory.memsw.usage_in_bytes tasks
memory.kmem.tcp.limit_in_bytes memory.move_charge_at_immigrate
從上面 ls 的輸出可以看出,除了每個(gè) cgroup 都有的那幾個(gè)文件外,和 memory 相關(guān)的文件還不少,這里先做個(gè)大概介紹(kernel 相關(guān)的文件除外),后面會(huì)詳細(xì)介紹每個(gè)文件的作用:
cgroup.event_control #用于eventfd的接口
memory.usage_in_bytes #顯示當(dāng)前已用的內(nèi)存
memory.limit_in_bytes #設(shè)置/顯示當(dāng)前限制的內(nèi)存額度
memory.failcnt #顯示內(nèi)存使用量達(dá)到限制值的次數(shù)
memory.max_usage_in_bytes #歷史內(nèi)存最大使用量
memory.soft_limit_in_bytes #設(shè)置/顯示當(dāng)前限制的內(nèi)存軟額度
memory.stat #顯示當(dāng)前cgroup的內(nèi)存使用情況
memory.use_hierarchy #設(shè)置/顯示是否將子cgroup的內(nèi)存使用情況統(tǒng)計(jì)到當(dāng)前cgroup里面
memory.force_empty #觸發(fā)系統(tǒng)立即盡可能的回收當(dāng)前cgroup中可以回收的內(nèi)存
memory.pressure_level #設(shè)置內(nèi)存壓力的通知事件,配合cgroup.event_control一起使用
memory.swappiness #設(shè)置和顯示當(dāng)前的swappiness
memory.move_charge_at_immigrate #設(shè)置當(dāng)進(jìn)程移動(dòng)到其他cgroup中時(shí),它所占用的內(nèi)存是否也隨著移動(dòng)過(guò)去
memory.oom_control #設(shè)置/顯示oom controls相關(guān)的配置
memory.numa_stat #顯示numa相關(guān)的內(nèi)存
添加進(jìn)程
也是往 cgroup 中添加進(jìn)程只要將進(jìn)程號(hào)寫(xiě)入 cgroup.procs 就可以了。
#重新打開(kāi)一個(gè)shell窗口,避免相互影響
root@DESKTOP-9K4GB6E:~# cd /sys/fs/cgroup/memory/test/
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# echo $$ >> cgroup.procs
#運(yùn)行top命令,這樣這個(gè)cgroup消耗的內(nèi)存會(huì)多點(diǎn),便于觀察
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# top
# 后續(xù)操作不再在這個(gè)窗口進(jìn)行,避免在這個(gè)bash中運(yùn)行進(jìn)程影響cgropu里面的進(jìn)程數(shù)及相關(guān)統(tǒng)計(jì)
設(shè)置限額
設(shè)置限額很簡(jiǎn)單,將閾值寫(xiě)入 memory.limit_in_bytes 文件就可以了,例如:
- echo 1M > memory.limit_in_bytes:限制只能用 1M 內(nèi)存
- echo -1 > memory.limit_in_bytes:-1 則是不限制
#回到第一個(gè)shell窗口
#開(kāi)始設(shè)置之前,看看當(dāng)前使用的內(nèi)存數(shù)量,這里的單位是字節(jié)
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# cat memory.usage_in_bytes
2379776
#設(shè)置1M的限額
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# echo 1M > memory.limit_in_bytes
#設(shè)置完之后記得要查看一下這個(gè)文件,因?yàn)閮?nèi)核要考慮頁(yè)對(duì)齊, 所以生效的數(shù)量不一定完全等于設(shè)置的數(shù)量
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# cat memory.usage_in_bytes
950272
#如果不再需要限制這個(gè)cgroup,寫(xiě)-1到文件memory.limit_in_bytes即可
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# echo -1 > memory.limit_in_bytes
#這時(shí)可以看到limit被設(shè)置成了一個(gè)很大的數(shù)字
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# cat memory.limit_in_bytes
9223372036854771712
如果設(shè)置的限額比當(dāng)前已經(jīng)使用的內(nèi)存少呢?
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# free -h
total used free shared buff/cache available
Mem: 7.7Gi 253Mi 7.4Gi 0.0Ki 95Mi 7.3Gi
Swap: 2.0Gi 0.0Ki 2.0Gi
# 此時(shí)用了 1232K
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# cat memory.usage_in_bytes
1232896
# 限制成500K
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# echo 500k > memory.limit_in_bytes
# 再次查看發(fā)現(xiàn)現(xiàn)在只用了401K
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# cat memory.usage_in_bytes
401408
# 發(fā)現(xiàn)swap多了1M,說(shuō)明另外的數(shù)據(jù)被轉(zhuǎn)移到swap上了
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# free -h
total used free shared buff/cache available
Mem: 7.7Gi 254Mi 7.4Gi 0.0Ki 94Mi 7.3Gi
Swap: 2.0Gi 1.0Mi 2.0Gi
#這個(gè)時(shí)候再來(lái)看failcnt,發(fā)現(xiàn)有381次之多(隔幾秒再看這個(gè)文件,發(fā)現(xiàn)次數(shù)在增長(zhǎng))
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# cat memory.failcnt
381
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# cat memory.failcnt
385
#再看看memory.stat(這里只顯示部分內(nèi)容),發(fā)現(xiàn)物理內(nèi)存用了400K,
#但有很多pgmajfault以及pgpgin和pgpgout,說(shuō)明發(fā)生了很多的swap in和swap out
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# cat memory.stat
swap 946176 # 946K 差不多剛好是內(nèi)存中少的量
pgpgin 30492
pgpgout 30443
pgfault 23859
pgmajfault 12507
從上面的結(jié)果可以看出,當(dāng)物理內(nèi)存不夠時(shí),就會(huì)觸發(fā) memory.failcnt 里面的數(shù)量加 1,但進(jìn)程不會(huì)被 kill 掉,那是因?yàn)閮?nèi)核會(huì)嘗試將物理內(nèi)存中的數(shù)據(jù)移動(dòng)到 swap 空間中,從而讓內(nèi)存分配成功。
如果設(shè)置的限額過(guò)小,就算 swap out 部分內(nèi)存后還是不夠會(huì)怎么樣?
#--------------------------第一個(gè)shell窗口----------------------
# 限制到100k
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# echo 100K > memory.limit_in_bytes
#--------------------------第二個(gè)shell窗口----------------------
# 嘗試執(zhí)行 top 發(fā)現(xiàn)剛運(yùn)行就被Kill了
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# top
Killed
從上面的這些測(cè)試可以看出,一旦設(shè)置了內(nèi)存限制,將立即生效,并且當(dāng)物理內(nèi)存使用量達(dá)到 limit 的時(shí)候,memory.failcnt 的內(nèi)容會(huì)加 1,但這時(shí)進(jìn)程不一定就會(huì)
被 kill 掉,內(nèi)核會(huì)盡量將物理內(nèi)存中的數(shù)據(jù)移到 swap 空間上去,如果實(shí)在是沒(méi)辦法移動(dòng)了(設(shè)置的 limit 過(guò)小,或者 swap 空間不足),默認(rèn)情況下,就會(huì) kill 掉 cgroup 里面繼續(xù)申請(qǐng)內(nèi)存的進(jìn)程。
行為控制
通過(guò)修改memory.oom_control文件,可以控制 subsystem 在物理內(nèi)存達(dá)到上限時(shí)的行為。文件中包含以下 3 個(gè)參數(shù):
-
oom_kill_disable:是否啟用 oom kill- 0:關(guān)閉
- 1:開(kāi)啟
-
under_oom:表示當(dāng)前是否已經(jīng)進(jìn)入 oom 狀態(tài),也即是否有進(jìn)程被暫停了。 -
oom_kill:oom 后是否執(zhí)行 kill- 1:?jiǎn)?dòng),oom 后直接 kill 掉對(duì)應(yīng)進(jìn)程
- 2:關(guān)閉:當(dāng)內(nèi)核無(wú)法給進(jìn)程分配足夠的內(nèi)存時(shí),將會(huì)暫停該進(jìn)程直到有空余的內(nèi)存之后再繼續(xù)運(yùn)行。同時(shí)會(huì)更新 under_oom 狀態(tài)
- 注意:root cgroup 的 oom killer 是不能被禁用的
為了演示 OOM-killer 的功能,創(chuàng)建了下面這樣一個(gè)程序,用來(lái)向系統(tǒng)申請(qǐng)內(nèi)存,它會(huì)每秒消耗 1M 的內(nèi)存。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MB (1024 * 1024)
int main(int argc, char *argv[])
{
char *p;
int i = 0;
while(1) {
p = (char *)malloc(MB);
memset(p, 0, MB);
printf("%dM memory allocated\n", ++i);
sleep(1);
}
return 0;
}
保存上面的程序到文件~/mem-allocate.c,然后編譯并測(cè)試
#--------------------------第一個(gè)shell窗口----------------------
#編譯上面的文件
dev@dev:/sys/fs/cgroup/memory/test$ gcc ~/mem-allocate.c -o ~/mem-allocate
#設(shè)置內(nèi)存限額為5M
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 5M > memory.limit_in_bytes"
#將當(dāng)前bash加入到test中,這樣這個(gè)bash創(chuàng)建的所有進(jìn)程都會(huì)自動(dòng)加入到test中
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo $$ >> cgroup.procs"
#默認(rèn)情況下,memory.oom_control的值為0,即默認(rèn)啟用oom killer
dev@dev:/sys/fs/cgroup/memory/test$ cat memory.oom_control
oom_kill_disable 0
under_oom 0
#為了避免受swap空間的影響,設(shè)置swappiness為0來(lái)禁止當(dāng)前cgroup使用swap
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 0 > memory.swappiness"
#當(dāng)分配第5M內(nèi)存時(shí),由于總內(nèi)存量超過(guò)了5M,所以進(jìn)程被kill了
dev@dev:/sys/fs/cgroup/memory/test$ ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
Killed
#設(shè)置oom_control為1,這樣內(nèi)存達(dá)到限額的時(shí)候會(huì)暫停
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 1 >> memory.oom_control"
#跟預(yù)期的一樣,程序被暫停了
dev@dev:/sys/fs/cgroup/memory/test$ ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
#--------------------------第二個(gè)shell窗口----------------------
#再打開(kāi)一個(gè)窗口
dev@dev:~$ cd /sys/fs/cgroup/memory/test/
#這時(shí)候可以看到memory.oom_control里面under_oom的值為1,表示當(dāng)前已經(jīng)oom了
dev@dev:/sys/fs/cgroup/memory/test$ cat memory.oom_control
oom_kill_disable 1
under_oom 1
#修改test的額度為7M
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 7M > memory.limit_in_bytes"
#--------------------------第一個(gè)shell窗口----------------------
#再回到第一個(gè)窗口,會(huì)發(fā)現(xiàn)進(jìn)程mem-allocate繼續(xù)執(zhí)行了兩步,然后暫停在6M那里了
dev@dev:/sys/fs/cgroup/memory/test$ ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
5M memory allocated
6M memory allocated
# 創(chuàng)建上面的文件并編譯
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# vim ~/mem-allocate.c
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# gcc ~/mem-allocate.c -o ~/mem-allocate
# 限制5M的上限
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# echo 5M > memory.limit_in_bytes
#將當(dāng)前bash加入到test中,這樣這個(gè)bash創(chuàng)建的所有進(jìn)程都會(huì)自動(dòng)加入到test中
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# echo $$ >> cgroup.procs
#默認(rèn)情況下,會(huì)啟用oom killer
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# cat memory.oom_control
oom_kill_disable 0
under_oom 0
oom_kill 1
#為了避免受swap空間的影響,設(shè)置swappiness為0來(lái)禁止當(dāng)前cgroup使用swap
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# echo 0 > memory.swappiness
#當(dāng)分配第5M內(nèi)存時(shí),由于總內(nèi)存量超過(guò)了5M,所以進(jìn)程被kill了
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
Killed
#設(shè)置oom_control為1,這樣內(nèi)存達(dá)到限額的時(shí)候會(huì)暫停
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# echo 1 >> memory.oom_control
#跟預(yù)期的一樣,程序被暫停了
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
#--------------------------第二個(gè)shell窗口----------------------
#再打開(kāi)一個(gè)窗口
dev@dev:~$ cd /sys/fs/cgroup/memory/test/
#這時(shí)候可以看到memory.oom_control里面under_oom的值為1,表示當(dāng)前已經(jīng)oom了
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# cat memory.oom_control
oom_kill_disable 1
under_oom 1
oom_kill 2
#修改test的額度為7M
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# echo 7M > memory.limit_in_bytes
# 切換會(huì)第一個(gè)窗口,發(fā)送程序又跑了兩步,停在了6M
root@DESKTOP-9K4GB6E:/sys/fs/cgroup/memory/test# ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
5M memory allocated
6M memory allocated
其他
進(jìn)程遷移(migration)
當(dāng)一個(gè)進(jìn)程從一個(gè) cgroup 移動(dòng)到另一個(gè) cgroup 時(shí),默認(rèn)情況下,該進(jìn)程已經(jīng)占用的內(nèi)存還是統(tǒng)計(jì)在原來(lái)的 cgroup 里面,不會(huì)占用新 cgroup 的配額,但新分配的內(nèi)存會(huì)統(tǒng)計(jì)到新的 cgroup 中(包括 swap out 到交換空間后再 swap in 到物理內(nèi)存中的部分)。
我們可以通過(guò)設(shè)置 memory.move_charge_at_immigrate 讓進(jìn)程所占用的內(nèi)存隨著進(jìn)程的遷移一起遷移到新的 cgroup 中。
enable: echo 1 > memory.move_charge_at_immigrate
disable:echo 0 > memory.move_charge_at_immigrate
注意: 就算設(shè)置為 1,但如果不是 thread group 的 leader,這個(gè) task 占用的內(nèi)存也不能被遷移過(guò)去。
換句話說(shuō),如果以線程為單位進(jìn)行遷移,必須是進(jìn)程的第一個(gè)線程,如果以進(jìn)程為單位進(jìn)行遷移,就沒(méi)有這個(gè)問(wèn)題。
當(dāng) memory.move_charge_at_immigrate 被設(shè)置成 1 之后,進(jìn)程占用的內(nèi)存將會(huì)被統(tǒng)計(jì)到目的 cgroup 中,如果目的 cgroup 沒(méi)有足夠的內(nèi)存,系統(tǒng)將嘗試回收目的 cgroup 的部分內(nèi)存(和系統(tǒng)內(nèi)存緊張時(shí)的機(jī)制一樣,刪除不常用的 file backed 的內(nèi)存或者 swap out 到交換空間上,請(qǐng)參考Linux 內(nèi)存管理),如果回收不成功,那么進(jìn)程遷移將失敗。
注意:遷移內(nèi)存占用數(shù)據(jù)是比較耗時(shí)的操作。
移除 cgroup
當(dāng) memory.move_charge_at_immigrate 為 0 時(shí),就算當(dāng)前 cgroup 中里面的進(jìn)程都已經(jīng)移動(dòng)到其它 cgropu 中去了,由于進(jìn)程已經(jīng)占用的內(nèi)存沒(méi)有被統(tǒng)計(jì)過(guò)去,當(dāng)前 cgroup 有可能還占用很多內(nèi)存,當(dāng)移除該 cgroup 時(shí),占用的內(nèi)存需要統(tǒng)計(jì)到誰(shuí)頭上呢?
答案是依賴 memory.use_hierarchy 的值,
- 如果該值為 0,將會(huì)統(tǒng)計(jì)到 root cgroup 里;
- 如果值為 1,將統(tǒng)計(jì)到它的父 cgroup 里面。
force_empty
當(dāng)向 memory.force_empty 文件寫(xiě)入 0 時(shí)(echo 0 > memory.force_empty),將會(huì)立即觸發(fā)系統(tǒng)盡可能的回收該 cgroup 占用的內(nèi)存。該功能主要使用場(chǎng)景是移除 cgroup 前(cgroup 中沒(méi)有進(jìn)程),先執(zhí)行該命令,可以盡可能的回收該 cgropu 占用的內(nèi)存,這樣遷移內(nèi)存的占用數(shù)據(jù)到父 cgroup 或者 root cgroup 時(shí)會(huì)快些。
memory.swappiness
該文件的值默認(rèn)和全局的 swappiness(/proc/sys/vm/swappiness)一樣,修改該文件只對(duì)當(dāng)前 cgroup 生效,其功能和全局的 swappiness 一樣,請(qǐng)參考Linux 交換空間中關(guān)于 swappiness 的介紹。
注意:有一點(diǎn)和全局的 swappiness 不同,那就是如果這個(gè)文件被設(shè)置成 0,就算系統(tǒng)配置的有交換空間,當(dāng)前 cgroup 也不會(huì)使用交換空間。
memory.use_hierarchy
該文件內(nèi)容為 0 時(shí),表示不使用繼承,即父子 cgroup 之間沒(méi)有關(guān)系;當(dāng)該文件內(nèi)容為 1 時(shí),子 cgroup 所占用的內(nèi)存會(huì)統(tǒng)計(jì)到所有祖先 cgroup 中。
如果該文件內(nèi)容為 1,當(dāng)一個(gè) cgroup 內(nèi)存吃緊時(shí),會(huì)觸發(fā)系統(tǒng)回收它以及它所有子孫 cgroup 的內(nèi)存。
注意: 當(dāng)該 cgroup 下面有子 cgroup 或者父 cgroup 已經(jīng)將該文件設(shè)置成了 1,那么當(dāng)前 cgroup 中的該文件就不能被修改。
#當(dāng)前cgroup和父cgroup里都是1
dev@dev:/sys/fs/cgroup/memory/test$ cat memory.use_hierarchy
1
dev@dev:/sys/fs/cgroup/memory/test$ cat ../memory.use_hierarchy
1
#由于父cgroup里面的值為1,所以修改當(dāng)前cgroup的值失敗
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 0 > ./memory.use_hierarchy"
sh: echo: I/O error
#由于父cgroup里面有子cgroup(至少有當(dāng)前cgroup這么一個(gè)子cgroup),
#修改父cgroup里面的值也失敗
dev@dev:/sys/fs/cgroup/memory/test$ sudo sh -c "echo 0 > ../memory.use_hierarchy"
sh: echo: I/O error
memory.soft_limit_in_bytes
有了 hard limit(memory.limit_in_bytes),為什么還要 soft limit 呢?hard limit 是一個(gè)硬性標(biāo)準(zhǔn),絕對(duì)不能超過(guò)這個(gè)值。
而 soft limit 可以被超越,既然能被超越,要這個(gè)配置還有啥用?先看看它的特點(diǎn)
- 1)當(dāng)系統(tǒng)內(nèi)存充裕時(shí),soft limit 不起任何作用
- 2)當(dāng)系統(tǒng)內(nèi)存吃緊時(shí),系統(tǒng)會(huì)盡量的將 cgroup 的內(nèi)存限制在 soft limit 值之下(內(nèi)核會(huì)盡量,但不 100% 保證)
從它的特點(diǎn)可以看出,它的作用主要發(fā)生在系統(tǒng)內(nèi)存吃緊時(shí),如果沒(méi)有 soft limit,那么所有的 cgroup 一起競(jìng)爭(zhēng)內(nèi)存資源,占用內(nèi)存多的 cgroup 不會(huì)讓著內(nèi)存占用少的 cgroup,這樣就會(huì)出現(xiàn)某些 cgroup 內(nèi)存饑餓的情況。如果配置了 soft limit,那么當(dāng)系統(tǒng)內(nèi)存吃緊時(shí),系統(tǒng)會(huì)讓超過(guò) soft limit 的 cgroup 釋放出超過(guò) soft limit 的那部分內(nèi)存(有可能更多),這樣其它 cgroup 就有了更多的機(jī)會(huì)分配到內(nèi)存。
從上面的分析看出,這其實(shí)是系統(tǒng)內(nèi)存不足時(shí)的一種妥協(xié)機(jī)制,給次等重要的進(jìn)程設(shè)置 soft limit,當(dāng)系統(tǒng)內(nèi)存吃緊時(shí),把機(jī)會(huì)讓給其它重要的進(jìn)程。
注意: 當(dāng)系統(tǒng)內(nèi)存吃緊且 cgroup 達(dá)到 soft limit 時(shí),系統(tǒng)為了把當(dāng)前 cgroup 的內(nèi)存使用量控制在 soft limit 下,在收到當(dāng)前 cgroup 新的內(nèi)存分配請(qǐng)求時(shí),就會(huì)觸發(fā)回收內(nèi)存操作,所以一旦到達(dá)這個(gè)狀態(tài),就會(huì)頻繁的觸發(fā)對(duì)當(dāng)前 cgroup 的內(nèi)存回收操作,會(huì)嚴(yán)重影響當(dāng)前 cgroup 的性能。
memory.pressure_level
這個(gè)文件主要用來(lái)監(jiān)控當(dāng)前 cgroup 的內(nèi)存壓力,當(dāng)內(nèi)存壓力大時(shí)(即已使用內(nèi)存快達(dá)到設(shè)置的限額),在分配內(nèi)存之前需要先回收部分內(nèi)存,從而影響內(nèi)存分配速度,影響性能,而通過(guò)監(jiān)控當(dāng)前 cgroup 的內(nèi)存壓力,可以在有壓力的時(shí)候采取一定的行動(dòng)來(lái)改善當(dāng)前 cgroup 的性能,比如關(guān)閉當(dāng)前 cgroup 中不重要的服務(wù)等。目前有三種壓力水平:
-
low
- 意味著系統(tǒng)在開(kāi)始為當(dāng)前 cgroup 分配內(nèi)存之前,需要先回收內(nèi)存中的數(shù)據(jù)了,這時(shí)候回收的是在磁盤(pán)上有對(duì)應(yīng)文件的內(nèi)存數(shù)據(jù)。
-
medium
- 意味著系統(tǒng)已經(jīng)開(kāi)始頻繁為當(dāng)前 cgroup 使用交換空間了。
-
critical
- 快撐不住了,系統(tǒng)隨時(shí)有可能 kill 掉 cgroup 中的進(jìn)程。
如何配置相關(guān)的監(jiān)聽(tīng)事件呢?和 memory.oom_control 類似,大概步驟如下:
- 利用函數(shù) eventfd(2) 創(chuàng)建一個(gè) event_fd
- 打開(kāi)文件 memory.pressure_level,得到 pressure_level_fd
- 往 cgroup.event_control 中寫(xiě)入這么一串:
<event_fd> <pressure_level_fd> <level> - 然后通過(guò)讀 event_fd 得到通知
注意: 多個(gè) level 可能要?jiǎng)?chuàng)建多個(gè) event_fd,好像沒(méi)有辦法共用一個(gè)
Memory thresholds
我們可以通過(guò) cgroup 的事件通知機(jī)制來(lái)實(shí)現(xiàn)對(duì)內(nèi)存的監(jiān)控,當(dāng)內(nèi)存使用量穿過(guò)(變得高于或者低于)我們?cè)O(shè)置的值時(shí),就會(huì)收到通知。使用方法和 memory.oom_control 類似,大概步驟如下:
- 利用函數(shù) eventfd(2) 創(chuàng)建一個(gè) event_fd
- 打開(kāi)文件 memory.usage_in_bytes,得到 usage_in_bytes_fd
- 往 cgroup.event_control 中寫(xiě)入這么一串:
<event_fd> <usage_in_bytes_fd> <threshold> - 然后通過(guò)讀 event_fd 得到通知
stat file
這個(gè)文件包含的統(tǒng)計(jì)項(xiàng)比較細(xì),需要一些內(nèi)核的內(nèi)存管理知識(shí)才能看懂,這里就不介紹了(怕說(shuō)錯(cuò))。詳細(xì)信息可以參考 Memory Resource Controller中的“5.2 stat file”。這里有幾個(gè)需要注意的地方:
- 里面 total 開(kāi)頭的統(tǒng)計(jì)項(xiàng)包含了子 cgroup 的數(shù)據(jù)(前提條件是 memory.use_hierarchy 等于 1)。
- 里面的 'rss + file_mapped" 才約等于是我們常說(shuō)的 RSS(ps aux 命令看到的 RSS)
- 文件(動(dòng)態(tài)庫(kù)和可執(zhí)行文件)及共享內(nèi)存可以在多個(gè)進(jìn)程之間共享,不過(guò)它們只會(huì)統(tǒng)計(jì)到他們的 owner cgroup 中的 file_mapped 去。(不確定是怎么定義 owner 的,但如果看到當(dāng)前 cgroup 的 file_mapped 值很小,說(shuō)明共享的數(shù)據(jù)沒(méi)有算到它頭上,而是其它的 cgroup)
小結(jié)
作用:限制 cgroups 中的進(jìn)程占用的內(nèi)存上限
用法:
- 1)
memory.limit_in_bytes配置進(jìn)程可以使用的內(nèi)存上限(hard limit),當(dāng)超過(guò)該閾值時(shí),一般是嘗試使用 swap,如果不行則直接 kill 掉。 - 2)
memory.soft_limit_in_bytes配置進(jìn)程可以使用的內(nèi)存上行(soft limit),當(dāng)系統(tǒng)內(nèi)存不足時(shí),cgroups 會(huì)優(yōu)先將使用量超過(guò) soft limit 的進(jìn)程進(jìn)行內(nèi)存回收,騰出內(nèi)存。 - 3)
memory.oom_control參數(shù)配置內(nèi)存使用量到達(dá)閾值時(shí)內(nèi)核的處理行為,默認(rèn)為 oom_kill。
原理:當(dāng)進(jìn)程使用內(nèi)存超過(guò)memory.limit_in_bytes 之后,系統(tǒng)會(huì)根據(jù) memory.oom_control 配置的行為進(jìn)行處理,一般是嘗試使用 swap,如果不行則直接 kill 掉。
本節(jié)沒(méi)有介紹 swap 和 kernel 相關(guān)的內(nèi)容,不過(guò)在實(shí)際使用過(guò)程中一定要留意 swap 空間,如果系統(tǒng)使用了交換空間,那么設(shè)置限額時(shí)一定要注意一點(diǎn),那就是當(dāng) cgroup 的物理空間不夠時(shí),內(nèi)核會(huì)將不常用的內(nèi)存 swap out 到交換空間上,從而導(dǎo)致一直不觸發(fā) oom killer,而是不停的 swap out/in,導(dǎo)致 cgroup 中的進(jìn)程運(yùn)行速度很慢。
如果一定要用交換空間,最好的辦法是限制 swap+物理內(nèi)存 的額度,雖然我們?cè)谶@篇中沒(méi)有介紹這部分內(nèi)容,但其使用方法和限制物理內(nèi)存是一樣的,只是換做寫(xiě)文件 memory.memsw.limit_in_bytes 罷了。
4. 小結(jié)
本文主要簡(jiǎn)單介紹了 pid、cpu、memory 這三個(gè) subsystem 的作用和基本使用,具體如下:
| subsystem | 功能 | 用法 | 原理 | 備注 |
|---|---|---|---|---|
| pid | 限制 cgroups 中進(jìn)程使用的 pid 數(shù) | 配置 subsystem 中的 pids.max 即可 | 當(dāng) cgroups 中的進(jìn)程調(diào)用 fork 或者 clone 系統(tǒng)調(diào)用時(shí)會(huì)判斷,subsystem 中配置的 pids.max 和當(dāng)前 pids.current 的值,來(lái)確定是否能夠創(chuàng)建新的進(jìn)程(或線程) | linux 中的 pid 是有限的,通過(guò)該 subsystem 可以有效防止 fork 炸彈之類的惡意進(jìn)程 |
| cpu | 限制 cgroups 中進(jìn)程使用的 cpu 上限 | 1)限制為具體值:用 cfs_period_us & cfs_quota_us 兩個(gè)配置可以嚴(yán)格限制進(jìn)程 cpu 使用量。 2)按比例分配:用 shares 配置,可以使得多個(gè) cgroups 之間按比例分配所有 cpu。 | subsystem 負(fù)責(zé)提供配置,cfs 負(fù)責(zé)記錄進(jìn)程使用的 cpu 時(shí)間,達(dá)到閾值后就從調(diào)度層面進(jìn)行限制,避免該進(jìn)程繼續(xù)使用 cpu。 | 一般使用 cfs_period_us & cfs_quota_us 方式限制具體值用得比較多。 |
| memory | 限制 cgroups 中進(jìn)程使用的 memory 上限 | 1)memory.limit_in_bytes 配置進(jìn)程可以使用的內(nèi)存上限(hard limit),當(dāng)超過(guò)該閾值時(shí),一般是嘗試使用 swap,如果不行則直接 kill 掉。 2)memory.soft_limit_in_bytes 配置進(jìn)程可以使用的內(nèi)存上行(soft limit),當(dāng)系統(tǒng)內(nèi)存不足時(shí),cgroups 會(huì)優(yōu)先將使用量超過(guò) soft limit 的進(jìn)程進(jìn)行內(nèi)存回收,騰出內(nèi)存。 3)memory.oom_control 參數(shù)配置內(nèi)存使用量到達(dá)閾值時(shí)內(nèi)核的處理行為,默認(rèn)為 oom_kill。 |
當(dāng)進(jìn)程使用內(nèi)存超過(guò)memory.limit_in_bytes 之后,系統(tǒng)會(huì)根據(jù) memory.oom_control 配置的行為進(jìn)行處理,一般是嘗試使用 swap,如果不行則直接 kill 掉。 |
如果系統(tǒng)使用了交換空間,那么設(shè)置限額時(shí)一定要注意一點(diǎn),那就是當(dāng) cgroup 的物理空間不夠時(shí),內(nèi)核會(huì)將不常用的內(nèi)存 swap out 到交換空間上,從而導(dǎo)致一直不觸發(fā) oom killer,而是不停的 swap out/in,導(dǎo)致 cgroup 中的進(jìn)程運(yùn)行速度很慢。 |
如果你對(duì)云原生技術(shù)充滿好奇,想要深入了解更多相關(guān)的文章和資訊,歡迎關(guān)注微信公眾號(hào)。
搜索公眾號(hào)【探索云原生】即可訂閱
5. 參考
cgroups(7) — Linux manual page
美團(tuán)技術(shù)團(tuán)隊(duì)---Linux 資源管理之 cgroups 簡(jiǎn)介
Red Hat---資源管理指南
總結(jié)
以上是生活随笔為你收集整理的深入剖析 Linux Cgroups 子系统:资源精细管理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 新一代通信协议 - Socket.D
- 下一篇: 通过腾讯网页快捷登录协议截取 QQ邮箱