深入理解 Linux Cgroup 系列(三):記憶體
通過上篇文章的學習,我們學會瞭如何檢視當前 cgroup 的資訊,如何通過操作 /sys/fs/cgroup
目錄來動態設定 cgroup,也學會瞭如何設定 CPU shares 和 CPU quota 來控制 slice
內部以及不同 slice
之間的 CPU 使用時間。本文將把重心轉移到記憶體上,通過具體的示例來演示如何通過 cgroup 來限制記憶體的使用。
1. 尋找走失記憶體
上篇文章告訴我們,CPU controller 提供了兩種方法來限制 CPU 使用時間,其中 CPUShares
用來設定相對權重,CPUQuota
50%
,那麼在該 user 的 CPU 使用量達到 50% 之前,可以一直按照 CPUShares 的設定來使用 CPU。
對於記憶體而言,在 CentOS 7 中,systemd 已經幫我們將 memory 繫結到了 /sys/fs/cgroup/memory。systemd
只提供了一個引數 MemoryLimit
來對其進行控制,該引數表示某個 user 或 service 所能使用的實體記憶體總量。拿之前的使用者 tom 舉例, 它的 UID 是 1000,可以通過以下命令來設定:
$ systemctl set-property user-1000.slice MemoryLimit=200M
複製程式碼
現在使用使用者 tom
登入該系統,通過 stress
命令產生 8 個子程式,每個程式分配 256M 記憶體:
$ stress --vm 8 --vm-bytes 256M
複製程式碼
按照預想,stress 程式的記憶體使用量已經超出了限制,此時應該會觸發 oom-killer
,但實際上程式仍在執行,這是為什麼呢?我們來看一下目前佔用的記憶體:
$ cd /sys/fs/cgroup/memory/user.slice/user-1000.slice
$ cat memory.usage_in_bytes
209661952
複製程式碼
奇怪,佔用的記憶體還不到 200M,剩下的記憶體都跑哪去了呢?別慌,你是否還記得 linux 系統中的記憶體使用除了包括實體記憶體,還包括交換分割槽,也就是 swap,我們來看看是不是 swap 搞的鬼。先停止剛剛的 stress 程式,稍等 30 秒,觀察一下 swap 空間的佔用情況:
$ free -h
total used free shared buff/cache available
Mem: 3.7G 180M 3.2G 8.9M 318M 3.3G
Swap: 3.9G 512K 3.9G
複製程式碼
重新執行 stress 程式:
$ stress --vm 8 --vm-bytes 256M
複製程式碼
檢視記憶體使用情況:
$ cat memory.usage_in_bytes
209637376
複製程式碼
發現記憶體佔用剛好在 200M 以內。再看 swap 空間佔用情況:
$ free
total used free shared buff/cache available
Mem: 3880876 407464 3145260 9164 328152 3220164
Swap: 4063228 2031360 2031868
複製程式碼
和剛剛相比,多了 2031360-512=2030848k
,現在基本上可以確定當程式的使用量達到限制時,核心會嘗試將實體記憶體中的資料移動到 swap 空間中,從而讓記憶體分配成功。我們可以精確計算出 tom 使用者使用的實體記憶體+交換空間總量,首先需要分別檢視 tom 使用者的實體記憶體和交換空間使用量:
$ egrep "swap|rss" memory.stat
rss 209637376
rss_huge 0
swap 1938804736
total_rss 209637376
total_rss_huge 0
total_swap 1938804736
複製程式碼
可以看到實體記憶體使用量為 209637376
位元組,swap 空間使用量為 1938804736
位元組,總量為 (209637376+1938804736)/1024/1024=2048
M。而 stress 程式需要的記憶體總量為 256*8=2048
M,兩者相等。
這個時候如果你每隔幾秒就檢視一次 memory.failcnt
檔案,就會發現這個檔案裡面的數值一直在增長:
$ cat memory.failcnt
59390293
複製程式碼
從上面的結果可以看出,當實體記憶體不夠時,就會觸發 memory.failcnt 裡面的數量加 1,但此時程式不一定會被殺死,核心會盡量將實體記憶體中的資料移動到 swap 空間中。
2. 關閉 swap
為了更好地觀察 cgroup 對記憶體的控制,我們可以使用者 tom 不使用 swap 空間,實現方法有以下幾種:
-
將
memory.swappiness
檔案的值修改為 0:$ echo 0 > /sys/fs/cgroup/memory/user.slice/user-1000.slice/memory.swappiness 複製程式碼
這樣設定完成之後,即使系統開啟了交換空間,當前 cgroup 也不會使用交換空間。
-
直接關閉系統的交換空間:
$ swapoff -a 複製程式碼
如果想永久生效,還要註釋掉
/etc/fstab
檔案中的 swap。
如果你既不想關閉系統的交換空間,又想讓 tom 不使用 swap 空間,上面給出的第一個方法是有問題的:
- 你只能在 tom 使用者登入的時候修改
memory.swappiness
檔案的值,因為如果 tom 使用者沒有登入,當前的 cgroup 就會消失。 - 即使你修改了
memory.swappiness
檔案的值,也會在重新登入後失效
如果按照常規思路去解決這個問題,可能會非常棘手,我們可以另闢蹊徑,從 PAM 入手。
Linux PAM(Pluggable Authentication Modules) 是一個系統級使用者認證框架,PAM 將程式開發與認證方式進行分離,程式在執行時呼叫附加的“認證”模組完成自己的工作。本地系統管理員通過配置選擇要使用哪些認證模組,其中 /etc/pam.d/
目錄專門用於存放 PAM 配置,用於為具體的應用程式設定獨立的認證方式。例如,在使用者通過 ssh 登入時,將會載入 /etc/pam.d/sshd
裡面的策略。
從 /etc/pam.d/sshd
入手,我們可以先建立一個 shell 指令碼:
$ cat /usr/local/bin/tom-noswap.sh
#!/bin/bash
if [ $PAM_USER == 'tom' ]
then
echo 0 > /sys/fs/cgroup/memory/user.slice/user-1000.slice/memory.swappiness
fi
複製程式碼
然後在 /etc/pam.d/sshd
中通過 pam_exec 呼叫該指令碼,在 /etc/pam.d/sshd
的末尾新增一行,內容如下:
$ session optional pam_exec.so seteuid /usr/local/bin/tom-noswap.sh
複製程式碼
現在再使用 tom 使用者登入,就會發現 memory.swappiness
的值變成了 0。
這裡需要注意一個前提:至少有一個使用者 tom 的登入會話,且通過
systemctl set-property user-1000.slice MemoryLimit=200M
命令設定了 limit,/sys/fs/cgroup/memory/user.slice/user-1000.slice
目錄才會存在。所以上面的所有操作,一定要保證至少保留一個使用者 tom 的登入會話。
3. 控制記憶體使用
關閉了 swap 之後,我們就可以嚴格控制程式的記憶體使用量了。還是使用開頭提到的例子,使用使用者 tom 登入該系統,先在第一個 shell 視窗執行以下命令:
$ journalctl -f
複製程式碼
開啟第二個 shell 視窗(還是 tom 使用者),通過 stress 命令產生 8 個子程式,每個程式分配 256M 記憶體:
$ stress --vm 8 --vm-bytes 256M
stress: info: [30150] dispatching hogs: 0 cpu,0 io,8 vm,0 hdd
stress: FAIL: [30150] (415) <-- worker 30152 got signal 9
stress: WARN: [30150] (417) stress: FAIL: [30150] (415) <-- worker 30151 got signal 9
stress: WARN: [30150] (417) now reaping child worker processes
stress: FAIL: [30150] (415) <-- worker 30154 got signal 9
stress: WARN: [30150] (417) now reaping child worker processes
stress: FAIL: [30150] (415) <-- worker 30157 got signal 9
stress: WARN: [30150] (417) now reaping child worker processes
stress: FAIL: [30150] (415) <-- worker 30158 got signal 9
stress: WARN: [30150] (417) now reaping child worker processes
stress: FAIL: [30150] (451) failed run completed in 0s
複製程式碼
現在可以看到 stress 程式很快被 kill 掉了,回到第一個 shell 視窗,會輸出以下資訊:
由此可見 cgroup 對記憶體的限制奏效了,stress 程式的記憶體使用量超出了限制,觸發了 oom-killer,進而殺死程式。
4. 更多檔案
加個小插曲,如果你想獲取更多關於 cgroup 的檔案,可以通過 yum 安裝 kernel-doc
包。安裝完成後,你就可以進入 /usr/share/docs
的子目錄,檢視每個 cgroup controller 的詳細檔案。
$ cd /usr/share/doc/kernel-doc-3.10.0/Documentation/cgroups
$ ll
總用量 172
4 -r--r--r-- 1 root root 918 6月 14 02:29 00-INDEX
16 -r--r--r-- 1 root root 16355 6月 14 02:29 blkio-controller.txt
28 -r--r--r-- 1 root root 27027 6月 14 02:29 cgroups.txt
4 -r--r--r-- 1 root root 1972 6月 14 02:29 cpuacct.txt
40 -r--r--r-- 1 root root 37225 6月 14 02:29 cpusets.txt
8 -r--r--r-- 1 root root 4370 6月 14 02:29 devices.txt
8 -r--r--r-- 1 root root 4908 6月 14 02:29 freezer-subsystem.txt
4 -r--r--r-- 1 root root 1714 6月 14 02:29 hugetlb.txt
16 -r--r--r-- 1 root root 14124 6月 14 02:29 memcg_test.txt
36 -r--r--r-- 1 root root 36415 6月 14 02:29 memory.txt
4 -r--r--r-- 1 root root 1267 6月 14 02:29 net_cls.txt
4 -r--r--r-- 1 root root 2513 6月 14 02:29 net_prio.txt
複製程式碼
下一篇文章將會討論如何使用 cgroup 來限制 I/O,敬請期待~