深入理解 Linux Cgroup 系列(三):內存

通過上篇文章的學習,我們學會了如何查看當前 cgroup 的信息,如何通過操作 /sys/fs/cgroup 目錄來動態設置 cgroup,也學會了如何設置 CPU shares 和 CPU quota 來控制 slice 內部以及不同 slice 之間的 CPU 使用時間。本文將把重心轉移到內存上,通過具體的示例來演示如何通過 cgroup 來限制內存的使用。

1. 尋找走失內存

上篇文章告訴我們,CPU controller 提供了兩種方法來限制 CPU 使用時間,其中 CPUShares 用來設置相對權重,CPUQuota 用來限制 user、service 或 VM 的 CPU 使用時間百分比。例如:如果一個 user 同時設置了 CPUShares 和 CPUQuota,假設 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 空間,實現方法有以下幾種:

  1. 將 memory.swappiness 文件的值修改為 0:
 $ echo 0 > /sys/fs/cgroup/memory/user.slice/user-1000.slice/memory.swappiness
這樣設置完成之後,即使系統開啟了交換空間,當前 cgroup 也不會使用交換空間。
2. 直接關閉系統的交換空間:
$ swapoff -a
如果想永久生效,還要註釋掉 `/etc/fstab` 文件中的 swap。

如果你既不想關閉系統的交換空間,又想讓 tom 不使用 swap 空間,上面給出的第一個方法是有問題的:

  • 你只能在 tom 用戶登錄的時候修改 memory.swappiness 文件的值,因為如果 tom 用戶沒有登錄,當前的 cgroup 就會消失。
  • 即使你修改了 memory.swappiness 文件的值,也會在重新登錄後失效

如果按照常規思路去解決這個問題,可能會非常棘手,我們可以另闢蹊徑,從 PAM 入手。

Linux PAM(http://www.linux-pam.org/) 是一個系統級用戶認證框架,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) stress: WARN: [30150] (417) stress: FAIL: [30150] (415) stress: WARN: [30150] (417) now reaping child worker processes
stress: FAIL: [30150] (415) stress: WARN: [30150] (417) now reaping child worker processes
stress: FAIL: [30150] (415) stress: WARN: [30150] (417) now reaping child worker processes
stress: FAIL: [30150] (415) stress: WARN: [30150] (417) now reaping child worker processes
stress: FAIL: [30150] (451) failed run completed in 0s

現在可以看到 stress 進程很快被 kill 掉了,回到第一個 shell 窗口,會輸出以下信息:

深入理解 Linux Cgroup 系列(三):內存

由此可見 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,敬請期待~


分享到:


相關文章: