控制组
控制組 (或者通常被簡寫為 cgroups) 是一項 Linux 內核提供的特性, 用於管理,限制和審核一組進程。Cgroups 可以操作一個進程的集合或者子集(例如,集合中由不同用戶啟動的進程),這使得 cgroups 和其它類似工具,如 nice(1) 命令和 /etc/security/limits.conf 相比更為靈活。
可以使用以下方式使用控制組:
- 在 systemd 單元文件中使用指令來制定服務和切片的限制;
- 通過直接訪問
cgroup文件系統; - 通過
cgcreate,cgexec和cgclassify(libcgroupAUR 和 libcgroup-gitAUR 包的一部分) 等工具; - 使用「規則應用守護程序」來自動移動特定的用戶/組/命令到另一個組中(
/etc/cgrules.conf和cgconfig.service)(libcgroupAUR 和 libcgroup-gitAUR 包的一部分);以及 - 通過其它軟體例如 Linux 容器 (LXC) 虛擬化。
對於 Arch Linux 來說,systemd 是首選的也是最簡單的調用和配置 cgroups 的方法,因為它是默認安裝的一部分。
確保你已經安裝了這些用於自動處理 cgroups 的包中的至少一個:
- systemd包 —— 用於控制 systemd 服務的資源使用。
-
libcgroupAUR,libcgroup-gitAUR —— 一系列獨立的工具(
cgcreate,cgclassify,通過cgconfig.conf實現可持久化配置)。
現有的 cgroup 層級可以通過 systemctl status 或者 systemd-cgls 命令查看。
$ systemctl status
● myarchlinux
State: running
Jobs: 0 queued
Failed: 0 units
Since: Wed 2019-12-04 22:16:28 UTC; 1 day 4h ago
CGroup: /
├─user.slice
│ └─user-1000.slice
│ ├─user@1000.service
│ │ ├─gnome-shell-wayland.service
│ │ │ ├─ 1129 /usr/bin/gnome-shell
│ │ ├─gnome-terminal-server.service
│ │ │ ├─33519 /usr/lib/gnome-terminal-server
│ │ │ ├─37298 fish
│ │ │ └─39239 systemctl status
│ │ ├─init.scope
│ │ │ ├─1066 /usr/lib/systemd/systemd --user
│ │ │ └─1067 (sd-pam)
│ └─session-2.scope
│ ├─1053 gdm-session-worker [pam/gdm-password]
│ ├─1078 /usr/bin/gnome-keyring-daemon --daemonize --login
│ ├─1082 /usr/lib/gdm-wayland-session /usr/bin/gnome-session
│ ├─1086 /usr/lib/gnome-session-binary
│ └─3514 /usr/bin/ssh-agent -D -a /run/user/1000/keyring/.ssh
├─init.scope
│ └─1 /sbin/init
└─system.slice
├─systemd-udevd.service
│ └─285 /usr/lib/systemd/systemd-udevd
├─systemd-journald.service
│ └─272 /usr/lib/systemd/systemd-journald
├─NetworkManager.service
│ └─656 /usr/bin/NetworkManager --no-daemon
├─gdm.service
│ └─668 /usr/bin/gdm
└─systemd-logind.service
└─654 /usr/lib/systemd/systemd-logind
一個進程所屬的 cgroup 組可以在 /proc/PID/cgroup 找到。
例如,找到 shell 進程的 cgroup:
$ cat /proc/self/cgroup
0::/user.slice/user-1000.slice/session-3.scope
systemd-cgtop 命令可以用於查看控制組的資源使用情況:
$ systemd-cgtop
Control Group Tasks %CPU Memory Input/s Output/s user.slice 540 152,8 3.3G - - user.slice/user-1000.slice 540 152,8 3.3G - - user.slice/u…000.slice/session-1.scope 425 149,5 3.1G - - system.slice 37 - 215.6M - -
systemd.slice(5) systemd 單元文件可以用於自定義一個 cgroup 配置。單元文件必須放在 systemd 目錄下,例如 /etc/systemd/system/。可以指定的資源控制選項文檔可以在 systemd.resource-control(5) 找到。
這是一個只允許使用 CPU 的 30% 的切片單元例子:
/etc/systemd/system/my.slice
[Slice] CPUQuota=30%
記得 daemon-reload 來應用 .slice 文件的更改。
資源可以直接在服務定義或者 drop-in 文件中指定:
[Service] MemoryMax=1G
這個例子將內存使用限制在 1 GB。
一個服務可以在指定切片下運行:
[Service] Slice=my.slice
systemd-run 可以用於在特定切片下運行命令。
# systemd-run --slice=my.slice command
--uid=username 選項可以以特定用戶的身份運行命令。
# systemd-run --uid=username --slice=my.slice command
--shell 選項可以在指定切片下啟動一個 shell。
非特權用戶可以在特定條件下將提供給他們的服務分成若干 cgroups。
必須使用 Cgroups v2 才能允許非 root 用戶管理 cgroup 資源。
並非所有系統資源都可以由用戶控制。
| Controller | Can be controlled by user | Options |
|---|---|---|
| cpu | 需要委派 | CPUAccounting, CPUWeight, CPUQuota, AllowedCPUs, AllowedMemoryNodes |
| io | 需要委派 | IOWeight, IOReadBandwidthMax, IOWriteBandwidthMax, IODeviceLatencyTargetSec |
| memory | 是 | MemoryLow, MemoryHigh, MemoryMax, MemorySwapMax |
| pids | 是 | TasksMax |
| rdma | 否 | ? |
| eBPF | 否 | IPAddressDeny, DeviceAllow, DevicePolicy |
為了讓用戶控制 CPU 和 IO 資源的使用,需要委派給用戶。這可以使用 drop-in 文件來完成。
加入你的 UID 是 1000:
/etc/systemd/system/user@1000.service.d/delegate.conf
[Service] Delegate=cpu cpuset io
重啟並確認用戶會話下的切片有了 CPU 和 IO 控制器。
$ cat /sys/fs/cgroup/user.slice/user-1000.slice/cgroup.controllers
cpuset cpu io memory pids
用戶切片文件可以放置在 ~/.config/systemd/user/。
可以這樣在特定切片下運行命令:
$ systemd-run --user --slice=my.slice command
你也可以在切片裡運行你的登陸 shell:
$ systemd-run --user --slice=my.slice --shell
cgroups 資源可以在運行時使用 systemctl set-property 命令進行調整。選項語法與 systemd.resource-control(5) 中相同。
--runntime 選項,否則調整將永久性生效。系統範圍的調整保存在 /etc/systemd/systemd/system.control/,用戶範圍內的保存在 .config/systemd/user.control/
例如,切斷所有用戶會話的 Internet 訪問:
$ systemctl set-property user.slice IPAddressDeny=any
與使用 systemd 管理相比,cgroup 虛擬文件系統要更更接近底層。"libgroup" 提供了一個庫和一些使管理更容易的實用程序,因此我們也將在這裡使用它們。
使用更接近底層的方式的原因很簡單:systemd 不為 cgroups 中的「每個接口文件」提供接口,也不應該期望它在未來的任何時間點提供它們。從它們中讀取以獲取有關 cgroup 資源使用的其他信息是完全無害的。
一個 cgroup 應該只由一組程序來寫入,以避免竟態條件,即「單一寫入規則」。這不是由內核強制執行的,但遵循此建議可以防止難以調試的問題發生。為了讓 systemd 停止管理某些子控制組,請參閱 Delegate= 屬性。否則,systemd 可能覆蓋你設置的內容。
Delegate= 設置的組(要委派所有權限,設置 Delegate=yes)。cgroups 允許你創建「專用」組。您甚至可以授予創建自定義組的權限給常規用戶。 groupname 是 cgroup 名稱:
# cgcreate -a user -t user -g memory,cpu:groupname
cpu 或 \*。Now all the tunables in the group groupname are writable by your user:
現在,您的用戶可以調整 groupname 組中的所有設置:
$ ls -l /sys/fs/cgroup/groupname
total 0 -r--r--r-- 1 root root 0 Jun 20 19:38 cgroup.controllers -r--r--r-- 1 root root 0 Jun 20 19:38 cgroup.events -rw-r--r-- 1 root root 0 Jun 20 19:38 cgroup.freeze --w------- 1 root root 0 Jun 20 19:38 cgroup.kill -rw-r--r-- 1 root root 0 Jun 20 19:38 cgroup.max.depth -rw-r--r-- 1 root root 0 Jun 20 19:38 cgroup.max.descendants -rw-r--r-- 1 root root 0 Jun 20 19:38 cgroup.pressure -rw-r--r-- 1 root root 0 Jun 20 19:38 cgroup.procs -r--r--r-- 1 root root 0 Jun 20 19:38 cgroup.stat -rw-r--r-- 1 root root 0 Jun 20 19:38 cgroup.subtree_control -rw-r--r-- 1 root root 0 Jun 20 19:38 cgroup.threads -rw-r--r-- 1 root root 0 Jun 20 19:38 cgroup.type -rw-r--r-- 1 root root 0 Jun 20 19:38 cpu.idle -rw-r--r-- 1 root root 0 Jun 20 19:38 cpu.max -rw-r--r-- 1 root root 0 Jun 20 19:38 cpu.max.burst -rw-r--r-- 1 root root 0 Jun 20 19:38 cpu.pressure -r--r--r-- 1 root root 0 Jun 20 19:38 cpu.stat -r--r--r-- 1 root root 0 Jun 20 19:38 cpu.stat.local -rw-r--r-- 1 root root 0 Jun 20 19:38 cpu.uclamp.max -rw-r--r-- 1 root root 0 Jun 20 19:38 cpu.uclamp.min -rw-r--r-- 1 root root 0 Jun 20 19:38 cpu.weight -rw-r--r-- 1 root root 0 Jun 20 19:38 cpu.weight.nice -rw-r--r-- 1 root root 0 Jun 20 19:38 io.pressure -rw-r--r-- 1 root root 0 Jun 20 19:38 irq.pressure -r--r--r-- 1 root root 0 Jun 20 19:38 memory.current -r--r--r-- 1 root root 0 Jun 20 19:38 memory.events -r--r--r-- 1 root root 0 Jun 20 19:38 memory.events.local -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.high -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.low -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.max -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.min -r--r--r-- 1 root root 0 Jun 20 19:38 memory.numa_stat -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.oom.group -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.peak -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.pressure --w------- 1 root root 0 Jun 20 19:38 memory.reclaim -r--r--r-- 1 root root 0 Jun 20 19:38 memory.stat -r--r--r-- 1 root root 0 Jun 20 19:38 memory.swap.current -r--r--r-- 1 root root 0 Jun 20 19:38 memory.swap.events -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.swap.high -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.swap.max -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.swap.peak -r--r--r-- 1 root root 0 Jun 20 19:38 memory.zswap.current -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.zswap.max -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.zswap.writeback -r--r--r-- 1 root root 0 Jun 20 19:38 pids.current -r--r--r-- 1 root root 0 Jun 20 19:38 pids.events -r--r--r-- 1 root root 0 Jun 20 19:38 pids.events.local -rw-r--r-- 1 root root 0 Jun 20 19:38 pids.max -r--r--r-- 1 root root 0 Jun 20 19:38 pids.peak
cgroups 是有層次的,此您可以創建盡任意多的子組。如果普通用戶想要創建名為 foo 的新子組,可以運行:
$ cgcreate -g cpu:groupname/foo
正如前文所提到的,在任何時候,只「應該」有一個程序寫入 cgroup。這不會影響非寫入操作,包括在組內生成新進程、將進程移動到另一個組或從 cgroup 文件讀取屬性。
libcgroup 包含一個簡單的工具用於在 cgroup 中運行新進程。如果普通用戶想在我們之前的 groupname/foo 下運行一個 bash shell:
$ cgexec -g cpu:groupname/foo bash
在 shell 內部,我們可以確認它屬於哪個 cgroup:
$ cat /proc/self/cgroup
0::/groupname/foo
這會使用 /proc/$PID/cgroup,一個存在於每個進程中的文件。手動寫入文件也會導致 cgroup 發生變化。
要將所有 'bash' 命令移動到此組:
$ pidof bash
13244 13266
$ cgclassify -g cpu:groupname/foo `pidof bash`
$ cat /proc/13244/cgroup
0::/groupname/foo
如果不想使用 cgclassify,內核提供了在 cgroups 之間移動進程的另外兩種方法。這兩個是等價的:
$ echo 0::/groupname/foo > /proc/13244/cgroup $ echo 13244 > /sys/fs/cgroup/groupname/foo/cgroup.procs
一個新的子目錄 /sys/fs/cgroup/group/foo 將在 groupname/foo 創建時創建。這些文件可以讀取和寫入以更改組的屬性。(再次提醒,除非委派完成,否則不建議寫入這些文件!)
讓我們試著看看我們組中所有的進程占用了多少內存:
$ cat /sys/fs/cgroup/groupname/foo/memory.current
1536000
要限制組中所有進程使用的 RAM (不包括交換空間),請運行以下命令:
$ echo 10000000 > /sys/fs/cgroup/groupname/foo/memory.max
要更改此組的 CPU 優先級(默認值為 100):
$ echo 10 > /sys/fs/cgroup/groupname/foo/cpu.weight
您可以通過列出 cgroup 目錄下的文件來查找更多可以調節的設置或統計信息。
如果您希望在引導時創建 cgroup,則可以在 /etc/cgconfig.conf 中定義它們。這會導致在引導時啟動一個服務以配置您的 cgroups。請參閱有關此文件語法的相關手冊頁;我們將不會說明如何使用真正已棄用的機制。
下面的示例顯示一個 cgroup,它將指定的命令使用的內存限制為 2GB。
$ systemd-run --scope -p MemoryMax=2G --user command
下面的示例顯示一個命令使用的 CPU 限制為一個 CPU 核心的 20%。
$ systemd-run --scope -p CPUQuota="20%" --user command
在 MATLAB 中進行大計算可能會使您的系統崩潰,因為Matlab沒有任何保護以防止占用機器的所有內存或 CPU。以下示例顯示一個將 Matlab 使用的資源限制為前 6 個 CPU 內核和 5 GB 內存的 cgroup。
~/.config/systemd/user/matlab.slice
[Slice] AllowedCPUs=0-5 MemoryHigh=6G
像這樣啟動 Matlab(請務必使用正確的路徑):
$ systemd-run --user --slice=matlab.slice /opt/MATLAB/2012b/bin/matlab -desktop
- 有關控制器以及特定開關和可調參數含義的信息,請參閱內核文檔的 v2 版本(或安裝 linux-docs包 包並查看
/usr/src/linux/Documentation/cgroup目錄)。 - Linux 手冊頁:cgroups(7)
- 詳細完整的資源管理指南可在 Red Hat Enterprise Linux 文檔中找到。
有關命令和配置文件,請參閱相關手冊頁,例如 cgcreate(1) 或 cgrules.conf(5)。
在 cgroup 的當前版本 v2 之前,存在一個稱為 v1 的早期版本。V1 提供了更靈活的選項,包括非統一層級和線程粒度的管理。現在來看,這是個壞主意(參見 v2 的設計理由):
- 儘管可以存在多個層級,並且進程可以綁定到多個層級,但一個控制器只能用於一個層級。這使得多個層級本質上毫無意義,通常的設置是將每個控制器綁定到一個層級(例如
/sys/fs/cgroup/memory/),然後將每個進程綁定到多個層級。這反過來使得像cgcreate這樣的工具對於同步進程在多個層級中的成員關係變得至關重要。 - 線程粒度的管理導致 cgroup 被濫用作進程管理自身的一種方式。正確的方法是使用系統調用,而不是為了支持這種用法而出現的複雜接口。自我管理需要笨拙的字符串處理,並且本質上容易受到競態條件的影響。
為了避免進一步的混亂,cgroup v2 在移除功能的基礎上制定了 兩條關鍵設計規則:
- 如果一個 cgroup 擁有子 cgroup,則它不能附加進程(根 cgroup 除外)。這在 v2 中是強制執行的,有助於實現使下一條規則(單一寫入規則)。
- 每個 cgroup 在同一時間應該只有一個進程管理它(單一寫入規則)。這條規則並未在任何地方強制執行,但在大多數情況下都應遵守,以避免軟體因爭相管理同一個組而產生的衝突痛苦。
- 在有 systemd 系統上,根 cgroup 由 systemd 管理,任何非 systemd 進行的更改都違反了這條規則(這並沒有被未強制執行,因此只是建議),除非在相關的服務或作用域單元上設置了
Delegate=選項,告知 systemd 不要干預其內部內容。
- 在有 systemd 系統上,根 cgroup 由 systemd 管理,任何非 systemd 進行的更改都違反了這條規則(這並沒有被未強制執行,因此只是建議),除非在相關的服務或作用域單元上設置了
在 systemd v258 之前,可以使用內核參數 SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1 systemd.unified_cgroup_hierarchy=0 來強制使用 cgroup-v1 啟動(第一個參數在 v256 中加入 以增加使用 cgroup-v1 的難度)。然而,此功能現已被移除。了解這一點仍然有價值,因為有些軟體喜歡在不告知您的情況下將 systemd.unified_cgroup_hierarchy=0 放入您的內核命令行,導致整個系統崩潰。