Linux-Cgroup V2 初體驗

探索云原生發表於2024-07-11

本文主要記錄 Linux Cgroup V2 版本基本使用操作,包括 cpu、memory 子系統演示。

1. 開啟 Cgroup V2

版本檢查

透過下面這條命令來檢視當前系統使用的 Cgroups V1 還是 V2

stat -fc %T /sys/fs/cgroup/

如果輸出是cgroup2fs 那就是 V2,就像這樣

root@tezn:~# stat -fc %T /sys/fs/cgroup/
cgroup2fs

如果輸出是tmpfs 那就是 V1,就像這樣

[root@docker cgroup]# stat -fc %T /sys/fs/cgroup/
tmpfs

啟用 cgroup v2

如果當前系統未啟用 Cgroup V2,也可以透過修改核心 cmdline 引導引數在你的 Linux 發行版上手動啟用 cgroup v2。

如果你的發行版使用 GRUB,則應在 /etc/default/grub 下的 GRUB_CMDLINE_LINUX 中新增 systemd.unified_cgroup_hierarchy=1, 然後執行 sudo update-grub

具體如下:

1)編輯 grub 配置

vi /etc/default/grub

內容大概是這樣的:

GRUB_DEFAULT=0
GRUB_TIMEOUT_STYLE=hidden
GRUB_TIMEOUT=0
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
GRUB_CMDLINE_LINUX=""

對最後一行GRUB_CMDLINE_LINUX進行修改

GRUB_CMDLINE_LINUX="quiet splash systemd.unified_cgroup_hierarchy=1"

2)然後執行以下命令更新 GRUB 配置

sudo update-grub

3)最後檢視一下啟動引數,確認配置修改上了

cat /boot/grub/grub.cfg | grep "systemd.unified_cgroup_hierarchy=1"

4)然後就是重啟

reboot

重啟後檢視,不出意外切換到 cgroups v2 了

root@cgroupv2:~# stat -fc %T /sys/fs/cgroup/
cgroup2fs

發行版推薦

不過,推薦的方法仍是使用一個預設已啟用 cgroup v2 的發行版。

有關使用 cgroup v2 的 Linux 發行版的列表,

  • Container-Optimized OS(從 M97 開始)
  • Ubuntu(從 21.10 開始,推薦 22.04+)
  • Debian GNU/Linux(從 Debian 11 Bullseye 開始)
  • Fedora(從 31 開始)
  • Arch Linux(從 2021 年 4 月開始)
  • RHEL 和類似 RHEL 的發行版(從 9 開始)

2. 基本使用

cgroup v2 使用上和 v1 版本基本一致,v2 版本也是預設在/sys/fs/cgroup/目錄。

root@mydocker:~# ls /sys/fs/cgroup/
cgroup.controllers      cgroup.subtree_control  init.scope       system.slice
cgroup.max.depth        cgroup.threads          io.cost.model    user.slice
cgroup.max.descendants  cpu.pressure            io.cost.qos
cgroup.procs            cpuset.cpus.effective   io.pressure
cgroup.stat             cpuset.mems.effective   memory.pressure
  • 建立 sub-cgroup: 只需要建立一個子目錄
cd /sys/fs/cgroup
mkdir $CGROUP_NAME
  • 將程序移動到指定 cgroup:將 PID 寫入到相應 cgroup 的 cgroup.procs 檔案即可,就像這樣:
echo 1001 > /sys/fs/cgroup/test/cgroup.procs
  • 刪除 cgroup/sub-cgroup: 也是直接刪除對應目錄即可
    • 如果一個cgroup 已經沒有任何children或活程序,那直接刪除對應的資料夾就刪除該cgroup了
    • 如果一個cgroup已經沒有children,但是還有殭屍程序,也認為這個cgroup是空的,可以直接刪除
rmdir /sys/fs/cgroup/test
  • 修改 cpu、memory 限制:往對應配置檔案寫入配置內容即可
    • cpu.max 用於配置 cpu 使用限制
    • memory.max 則用於配置 記憶體使用限制
    • ...

建立 cgroup

接下來,以 cpu、memory 為例,簡單演示一下 cgroup v2 版本使用

root@mydocker:~# cd /sys/fs/cgroup/
root@mydocker:/sys/fs/cgroup# mkdir test
root@mydocker:/sys/fs/cgroup# cd test
root@mydocker:/sys/fs/cgroup/test# ls
cgroup.controllers      cpu.uclamp.max         memory.current
cgroup.events           cpu.uclamp.min         memory.events
cgroup.freeze           cpu.weight             memory.events.local
cgroup.max.depth        cpu.weight.nice        memory.high
cgroup.max.descendants  cpuset.cpus            memory.low
cgroup.procs            cpuset.cpus.effective  memory.max
cgroup.stat             cpuset.cpus.partition  memory.min
cgroup.subtree_control  cpuset.mems            memory.oom.group
cgroup.threads          cpuset.mems.effective  memory.pressure
cgroup.type             io.max                 memory.stat
cpu.max                 io.pressure            pids.current
cpu.pressure            io.stat                pids.events
cpu.stat                io.weight              pids.max

CPU

啟動一個死迴圈

root@mydocker:/sys/fs/cgroup/test# while : ; do : ; done &
[1] 90482

不出意外的話,應該佔用了 100% cpu

PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND    
90482 root      20   0   10160   1772      0 R  99.3   0.1   0:05.01 bash 

接下來使用 cgroup v2 限制該程序只能使用 20% cpu

1)修改配置

echo 2000 10000 > cpu.max

含義是在 10000 微秒的 CPU 時間週期內,有 2000 微秒是分配給本 cgroup 的,也就是本 cgroup 管理的程序在單核 CPU 上的使用率不會超過 20%。

2)將程序加入當前 cgroup

echo 90482 > cgroup.procs

再次檢視

PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND    
90482 root      20   0   10160   1772      0 R  20.2   0.1   2:07.78 bash 

可以看到,已經被限制到了 20%

Memory

接下來演示記憶體限制,使用以下程式碼來模擬記憶體消耗,

cat <<EOF > ~/mem-allocate.c
#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;
}
EOF

編譯

gcc ~/mem-allocate.c -o ~/mem-allocate

然後啟動該檔案

root@mydocker:/sys/fs/cgroup/test# ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
5M memory allocated
6M memory allocated
7M memory allocated
8M memory allocated
9M memory allocated
10M memory allocated
11M memory allocated
12M memory allocated
^C

可以看到,每秒會消耗 1M 記憶體,若不停止會一直執行直到 OOM。

接下來使用 cgroup v2 限制最多消耗 10M 記憶體。

1)修改配置

單位為位元組 10485760= 10 * 1024 * 1024

echo 10485760 > memory.max

也就是本 cgroup 管理的程序記憶體使用不會超過 10M

2)將程序加入當前 cgroup

#將當前bash加入到test中,這樣這個bash建立的所有程序都會自動加入到test中
sh -c "echo $$ >> cgroup.procs"

再次檢視

root@mydocker:/sys/fs/cgroup/test# ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
5M memory allocated
6M memory allocated
7M memory allocated
8M memory allocated
9M memory allocated
Killed

可以看到,到 10M 時就因為達到記憶體上限而被 Kill 了。

刪除 cgroup

演示完成,把 cgroup 刪除。

首先把程序 kill 一下

root@mydocker:/sys/fs/cgroup# cat test/cgroup.procs 
90444
90630
root@mydocker:/sys/fs/cgroup# kill -9 90630
root@mydocker:/sys/fs/cgroup# kill -9 90444

然後刪除目錄

root@mydocker:/sys/fs/cgroup# rmdir test

這樣 cgroup 就刪除了。

3. v1 v2 對比

v1 的 cgroup 為每個控制器都使用獨立的樹(目錄)

[root@docker cgroup]# ls /sys/fs/cgroup/
blkio  cpu  cpuacct  cpuacct,cpu  cpu,cpuacct  cpuset  devices  freezer  hugetlb  memory  net_cls  net_cls,net_prio  net_prio  perf_event  pids  rdma  systemd

每個目錄就代表了一個 cgroup subsystem,比如要限制 cpu 則需要到 cpu 目錄下建立子目錄(樹),限制 memory 則需要到 memory 目錄下去建立子目錄(樹)。

比如 Docker 就會在 cpu、memory 等等目錄下都建立一個名為 docker 的目錄,在 docker 目錄下在根據 containerID 建立子目錄來實現資源限制。

各個 Subsystem 各自為政,看起來比混亂,難以管理

因此最終的結果就是:

  1. 使用者空間最後管理著多個非常類似的 hierarchy
  2. 在執行 hierarchy 管理操作時,每個 hierarchy 上都重複著相同的操作

v2 中對 cgroups 的最大更改是將重點放在簡化層次結構上

  • v1 為每個控制器使用獨立的樹(例如 /sys/fs/cgroup/cpu/GROUPNAME /sys/fs/cgroup/memory/GROUPNAME)。
  • v2 將統一/sys/fs/cgroup/GROUPNAME中的樹,如果程序 X 加入/sys/fs/cgroup/test,則啟用 test 的每個控制器都將控制程序 X。

更多 v1 和 v2 差異見 v1 存在的問題及 v2 的設計考慮


【從零開始寫 Docker 系列】持續更新中,搜尋公眾號【探索雲原生】訂閱,閱讀更多文章。


4. 小結

本文主要分享了 Linux cgroup v2 版本的基本使用,以及 v1 和 v2 版本的差異。

更多 cgroup v2 資訊推薦閱讀:Control Group v2 及其譯文 Control Group v2(cgroupv2 權威指南)(KernelDoc, 2021)