Control Group 簡稱 cgroup,是 linux kernel 裡的一個功能, 用來限制、記錄、隔離資源使用。
最早於 2006 年由 google 工程師開發,後來合併到 kernel 2.6.24 中。
cgroup 的特點有以下幾種:
1. 限制資源的使用:執行某個應用程式,卻佔掉大部份的系統資源,導致別的程式根本無法執行,或在多核心的機器上,讓某應用程式只能使用某些核心,保留一些給其他程式用,或限制某程式最多使用 2G 的記憶體。
2. 使用資源的優先權:有沒有辦法調整讓 A 程式比 B 程式多用一些 cpu 呢?
3. 記錄 cpu 使用量以收取費用:對某些公司來說,cpu 的使用是要收費的,但要如何算出使用者到底使用了多少 cpu 呢? 早期這類的工作是很繁雜的,一般是透過 psacct 來收取整個系統上所有指令使用的 cpu 時間,再寫一些 scripts 來分析出各個使用者的 cpu 使用量,產生報表,現在透過 cgroup 可以很容易的抓出資料。
4. 隔離不同群組的 process:對於不同群組的 process 做到隔離,可用於 lxc (linux container)。
5. freezing groufreezing groups or checkpointing and restarting: 假設一個程式執行要 8 小時 cpu 時間才能完成,但跑到 7.3 小時卻因為某狀況導致失敗,等故障排除後,要再重頭開始跑 8 小時,這樣很沒效率。若在中間有做 checkpoint,假設每小時一次,則可以從 7 小時的部份接續下去跑,理論上是很完美,只是不知道現在 cgroup 能做到什麼程度就是。
這邊以實例來操作 cgroup ,目前在 debian wheezy 上有遇到一些問題,只能使用 root 使用者來分配資源,而無法以一般使用者,還沒找出問題在哪,所以先用 arch linux 為例,基本上,目前 cgroup 的實作,試過幾個 distribution,都大同小異,fedora、ubuntu、arch linux,都是類似,試過都沒問題,但 debian wheezy 則有點不同。
進入系統後,首先檢查 cgroup 是否有掛載起來:
$ mount | grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (rw,nosuid,nodev,noexec,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
在 arch linux 中,可看到 cgroup 掛載於 /sys/fs/cgroup/ 底下,接著檢查系統的 cgroup 支援哪些 controller.
$ cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 2 3 1
cpu 3 14 1
cpuacct 3 14 1
memory 4 1 1
devices 5 1 1
freezer 6 1 1
net_cls 7 1 1
blkio 8 1 1
debian wheezy 預設 memory 是沒啟動的,可在開機時 grub 加入 cgroup_enable=memory 來開啟。
由於 cgroup 的 controller 滿多,功能也很廣,這邊只針對幾個做說明。
cpuset: 設定使用那幾個 cpu core.
cpu 和 cpuacct 是同一個:設定 cpu shares,share 值越大,能用 cpu 的時間越多。
memory:限制記憶體使用量。
要使用 cgroup,可透過底下幾個方法:
1. 直接以命令列指令存取 /sys/fs/cgroup 來達到。
2. 使用 libcgroup 的一些指令,像 cgcreate, cgexec, cgclassify 來操作。
3. 透過 LXC 來實作。
這邊介紹 1, 2 種方法,lxc 則留著以後再分享。
以命令列指令存取 cgroup:
操作前準備:
以 root 來建立 low 群組,low 繼承了 cpu controller 的所有屬性
$ sudo mkdir /sys/fs/cgroup/cpu/low
將權限開給 behappy 使用者
$ sudo chown behappy:users -R /sys/fs/cgroup/cpu/low
cpu share 預設為 1024
$ cat /sys/fs/cgroup/cpu/low/cpu.shares
1024
以一般使用者將之 low 群組設成 512
$ echo 512 > /sys/fs/cgroup/cpu/low/cpu.shares
重覆上面,建立 high 群組,cpu share 設為 2048
$ sudo mkdir /sys/fs/cgroup/cpu/high
$ sudo chown behappy:users -R /sys/fs/cgroup/cpu/high
$ echo 2048 > /sys/fs/cgroup/cpu/high/cpu.shares
另外建立 first_core、second_core 群組,為 cpu 第一個核心及第二核心,位於 cpuset controller 底下。
$ sudo mkdir /sys/fs/cgroup/cpuset/first_core
$ sudo chown behappy:users -R /sys/fs/cgroup/first_core
$ sudo mkdir /sys/fs/cgroup/cpuset/second_core
$ sudo chown behappy:users -R /sys/fs/cgroup/second_core
檢視原本 cpuset 裡的設定:
$ cat /sys/fs/cgroup/cpuset/cpuset.cpus
0-1
有二個 cpu core 0 和 1
而我們建立的 first_core 及 second_core 裡的設定是空的
$ cat /sys/fs/cgroup/cpuset/first_core/cpuset.cpus
將 first_core 群組設成第一個 cpu core:
$ echo 0 > /sys/fs/cgroup/cpuset/first_core/cpuset.cpus
將 second_core 群組設成第二個 cpu core:
$ echo 1 > /sys/fs/cgroup/cpuset/second_core/cpuset.cpus
這樣大致上準備完成,接著開始試驗。
拿 /usr/bin/yes 這指令來操 cpu,為了易於閱讀,設置二個執行檔:
$ cp /usr/bin/yes /tmp/yes_low
$ cp /usr/bin/yes /tmp/yes_high
另外開個 top 在旁邊觀看 cpu 使用情形,按 1 來顯示各別 cpu core 使用情形
$ /tmp/yes_low &>/dev/null &
$ /tmp/yes_high &>/dev/null &
$ top
top - 13:14:41 up 3:34, 2 users, load average: 0.85, 0.34, 0.16
Tasks: 119 total, 3 running, 114 sleeping, 2 stopped, 0 zombie
%Cpu0 : 99.3 us, 0.7 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 99.7 us, 0.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem: 3951368 total, 1223052 used, 2728316 free, 150640 buffers
KiB Swap: 3145724 total, 0 used, 3145724 free, 531504 cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
9860 behappy 20 0 7120 356 284 R 99.9 0.0 0:11.32 yes_low
9861 behappy 20 0 7120 356 284 R 98.6 0.0 0:08.98 yes_hig
下方可看到二個程式,各佔 cpu 99%,而上面則顯示目前二個 cpu core 都是 99% 使用率,這是正常的狀況下。
接著我們將 yes_low 加入到 low 群組
$ echo 9860 > /sys/fs/cgroup/cpu/low/tasks
將 yes_high 加入到 high 群組
$ echo 9861 > /sys/fs/cgroup/cpu/low/tasks
理論上 top 所看到的 cpu 使用狀況應該有變化,但是並沒有,因為有二個 cpu core,而使用 cpu 的程式也是二個而已,所以在夠用的情況下,二個程式都可以取得所需的資源,因此一樣佔用 99%,所以我們要讓二個程式使用同一個 cpu core 才行。
$ echo 9860 > /sys/fs/cgroup/cpuset/first_core/tasks
-bash: echo: write error: No space left on device
哦哦,失敗,要先設定 cpuset.mems 才行,不知道為什麼 :)
$ echo 0 > /sys/fs/cgroup/cpuset/first_core/cpuset.mems
再設定一次就可成功
$ echo 9860 > /sys/fs/cgroup/cpuset/first_core/tasks
將 yes_high 也加入 first_core 群組
$ echo 9861 > /sys/fs/cgroup/cpuset/first_core/tasks
過個三秒後,應該可看到 top 上的變化
$ top
top - 13:14:41 up 3:34, 2 users, load average: 0.85, 0.34, 0.16
Tasks: 119 total, 3 running, 114 sleeping, 2 stopped, 0 zombie
%Cpu0 : 99.8 us, 0.7 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 1.7 us, 0.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem: 3951368 total, 1223052 used, 2728316 free, 150640 buffers
KiB Swap: 3145724 total, 0 used, 3145724 free, 531504 cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
9861 behappy 20 0 7120 356 284 R 79.9 0.0 0:11.32 yes_high
9860 behappy 20 0 7120 356 284 R 20.2 0.0 0:08.98 yes_low
很明顯上方可看到,第一個 cpu core 用了 99.8%,而第二個就只有 1.7%,因為我們設定 yes_high 及 yes_low 都只使用第一個 cpu core;接著下方,由於 yes_low 比重是 512,yes_high 是 2048,為 1:4,因此 cpu loading 也趨近於 1:4 ( 20.2 : 79.9 )。
這樣空下一顆 cpu core 可以用來做其他事情,而不會導致系統變得很頓。
cpuacct 的部份,在剛建立一個 cpu 或 cpuacct 群組時,cpuacct.usage 為 0,像剛才建立的 high 群組
$ cat /sys/fs/cgroup/cpu/high/cpuacct.usage
0
經過執行了 yes_high 並加入 high 群組後,cpuacct.usage 就會開始累計,所以看內容就可知道用了多少 cpu time,單位為 ns (nano second,十的負九次方秒)。
$ cat /sys/fs/cgroup/cpu/high/cpuacct.usage
22493005441
這樣大概就是 22.5 秒,但觀察 cpuacct.stat 可看到,這 22.5 秒是 user + system 全部總合,因此要看 cpuacct.stat 裡面 user 的部份才是真正 user 使用的 cpu time,約 22.44 秒。
$ cat /sys/fs/cgroup/cpu/high/cpuacct.stat
user 2244
system 7
另外,由於只用到第一個 cpu core,因此在 cpuacct.usage_percpu 中看到第二個 cpu core cpu time 為 0
$ cat /sys/fs/cgroup/cpu/high/cpuacct.usage_percpu
22493005441 0
memory 的部份,一樣先建立一個群組,設定好限制,再將程式加入此群組即可。
以 root 建立一個 mymemory 群組
$ sudo mkdir /sys/fs/cgroup/memory/mymemory
$ sudo chown -R behappy:users /sys/fs/cgroup/memory/mymemory
用一般使用者設定記憶體上限為 10M Bytes
$ echo 10000000 > /sys/fs/cgroup/memory/mymemory/memory.limit_in_bytes
在旁邊先開個 terminal 來監控記憶體運作情形,使用 free,來觀察記憶體變化
$ watch free
然後開啟一個 shell,並將 shell 加入 mymemory 群組,這樣從這個 shell 裡所執行的程式,都屬於 mymemory 群組。
$ bash
找出剛才執行 bash 的 pid
$ echo $$
5312
將 bash 加入 mymemory 群組
$ echo 5312 > /sys/fs/cgroup/memory/mymemory/tasks
加入後,執行一個超過 10M 記憶體的程式,如 libreoffice、或看看影片,可立刻看到記憶體監控視窗中,已開始用到 swap 了,因為只給 10M,但不夠用,會自動使用 swap,印證我們限制記憶體使用是成功的。
操作上大致是如此,但是若每次都以存取 /sys 來動作,似乎很麻煩,因此有 libcgroup 的工具可用。
以 libcgroup 存取 cgroup:
fedora 是 libcgroup,直接裝上即可,而 debian 及 ubuntu 除了安裝 libcgroup1 還要再加上 cgroup-bin。
arch linux 從 AUR 中裝上:
$ yaourt -S libcgroup
以 libcgroup 來重覆上面建立 first_core, second_core, high, low 群組的指令。
建立 first_core 群組
$ sudo cgcreate -a behappy:users -t behappy:users -g cpuset:/first_core
建立 second_core 群組
$ sudo cgcreate -a behappy:users -t behappy:users -g cpuset:/second_core
建立 high 群組
$ sudo cgcreate -a behappy:users -t behappy:users -g cpu:/high
建立 low 群組
$ sudo cgcreate -a behappy:users -t behappy:users -g cpu:/low
設定 first_core 群組的 cpuset.cpus 為第一個 cpu core
$ cgset -r cpuset.cpus=0 /first_core
設定 second_core 群組的 cpuset.cpus 為第二個 cpu core
$ cgset -r cpuset.cpus=1 /second_core
設定 high 群組的 cpu.shares 為 2048
$ cgset -r cpu.shares=2048 /high
設定 low 群組的 cpu.shares 為 512
$ cgset -r cpu.shares=512 /low
指定 yes_high 到 first_core 群組
$ cgclassify -g cpuset:/first_core 9861
指定 yes_low 到 first_core 群組
$ cgclassify -g cpuset:/first_core 9860
指定 yes_high 到 high 群組
$ cgclassify -g cpu:/high 9861
指定 yes_low 到 low 群組
$ cgclassify -g cpu:/low 9860
cgclassify 是用於將已執行中的程式指到一個群組,而 cgexec 則用來執行一個程式,並將之加入一個群組。
執行 /tmp/yes_high &>/dev/null & 這指令,並將之加入 first_core 及 high 群組
$ cgexec -g cpu:/high -g cpuset:/first_core /tmp/yes_high &>/dev/null &
執行 /tmp/yes_low &>/dev/null & 這指令,並將之加入 first_core 及 low 群組
$ cgexec -g cpu:/low -g cpuset:/first_core /tmp/yes_low &>/dev/null &
檢視 yes_low 使用了多少 cpu time:
$ cgget -r cpuacct.usage /low
/low:
cpuacct.usage:
22493005441
memory 的部份:
$ sudo cgcreate -a behappy:users -t behappy:users -g memory:/mymemory
$ cgset -r memory.limit_in_bytes=10000000 /mymemory
還不錯用,至少比 echo xx > 什麼的方便多了不是嗎?
但我們建立的群組,重新開機後就會不見,因此要寫在 /etc/cgconfig.conf 中,然後開機時啟動 cgconfig service
$ cat /etc/cgconfig.conf
group first_core {
perm {task {
uid = behappy;
}
admin {
uid = behappy;
}
}
cpuset {
cpuset.mems = "0";
cpuset.cpus = "0";
}
}
group second_core {
perm {
task {
uid = behappy;
}
admin {
uid = behappy;
}
}
cpuset {
cpuset.mems = "0";
cpuset.cpus = "1";
}
}
group low {
perm {
task {
uid = behappy;
}
admin {
uid = behappy;
}
}
cpu {
cpu.shares = "512";
}
}
group high {
perm {
task {
uid = behappy;
}
admin {
uid = behappy;
}
}
cpu {
cpu.shares = "2048";
}
}
group mymemory {
perm {
task {
uid = 1000;
}
admin {
uid = 1000;
}
}
memory {
memory.limit_in_bytes = "10000000";
}
}
$ sudo systemctl start cgconfig
可是,若每次執行程式,都要手動加入到某個群組,還是很麻煩啊?沒問題,有 cgrulesengd 這個 daemon 來幫助我們,依照 /etc/cgrules.conf 所指定的,自動在程式執行後加入群組中,開機時要啟動 cgred service。
$ cat /etc/cgrules.conf
#《user》 《controllers》《destination》
#《user》:《process name》 《controllers》《destination》
behappy:yes_high cpuset first_core
% cpu,cpuacct high
behappy:/tmp/yes_low cpuset first_core
% cpu,cpuacct low
$ sudo systemctl start cgred
排除註解總共 4 行,第 1 行是將使用者 behappy 執行的 yes_high 加入 cpuset controller 中的 first_core 群組
第 2 行前面是 %,表示接續第一行過來,若前面寫得和第一行一樣,則在找到第一行符合條件套用後就會離開,加 % 表示第 2 行和第 1 行是同一行指令,所以會 1,2 行都套用後才離開。
所以 1,2 行一組,表示將 behappy 的 yes_high 加入 first_core 及 high 群組。
3,4 行表示將 behappy 的 yes_low 加入 first_core 及 low 群組。
記得,在 cgrulesengd 啟動後,先前已執行的程式並不會變動,只有從 cgrulesengd 啟動後才執行的程式會自動加入群組。
另外,libcgroup 還有幾個指令:
$ cgdelete: 刪除某個群組
$ sudo cgdelete -g cpuset:first_core
cgclear: 清除所有群組 (這指令儘量不要下,否則全空了),若全部不見了,寫個 /tmp/cgconfig.conf,內容如下,並以 cgconfigparser 來載入:
mount {
cpuset = /sys/fs/cgroup/cpuset;
cpu = /sys/fs/cgroup/cpu,cpuacct;
cpuacct = /sys/fs/cgroup/cpu,cpuacct;
memory = /sys/fs/cgroup/memory;
devices = /sys/fs/cgroup/devices;
freezer = /sys/fs/cgroup/freezer;
net_cls = /sys/fs/cgroup/net_cls;
blkio = /sys/fs/cgroup/blkio;
}
cgconfigparser: 檢查 cgconfig.conf 設定檔的格式錯誤,若沒問題則載入。
$ cgconfigparser -l /tmp/cgconfig.conf
cgsnapshot: 將目前 cgroup 狀態 dump 下來,可存成設定檔。
$ cgsnapshot > /tmp/cgconfig.conf
總結:
Cgroup 是非常複雜的東東,這邊只做基本介紹,有興趣可到 /sys/fs/cgroup 裡去看看每個檔案的內容。
現在 linux 有了 cgroup 後,簡直是如虎添翼,擁有早期只有大型主機 + 商用unix 才有的功能,現在 linux + PC 就可達到,真是越來越強了。
參考文件:
1. http://en.wikipedia.org/wiki/Cgroup
2. https://wiki.archlinux.org/index.php/Cgroups
3. http://www.kernel.org/doc/Documentation/cgroups/cgroups.txt
4. https://access.redhat.com/knowledge/docs/en-US/Red_Hat_Enterprise_Linux/6/html/Resource_Management_Guide/index.html
沒有留言:
張貼留言