Linux Cgroups
[toc]
Linux Cgroups
Namespace是用來實現程序之間的隔離,但是並沒有限制其空間的大小。如果想要限制一個程序可以使用的空間,保證各個程序之間不會互相爭搶就要用到 Cgroups。
Linux Cgroups(Linux Control Groups)提供了對一組程序及將來子程序的資源限制、控制、統計的能力。這些資源包括cpu、記憶體、儲存、網路 等。通過Cgroups,可以方便的控制某個程序佔用的資源,並可以實施監控和統計資訊。
Cgroups中的三個元件
cgroup 是對程序分組管理的一種機制,一個cgroup包含一組程序,並可以在這個cgroup上增加Linux subsystem的各種引數配置,將一組程序和一 組subsystem的系統引數關聯起來。
subsystem 是一組資源控制的模組。包含以下幾項。
- blkio 設定對塊裝置輸入輸出的訪問控制。例如磁碟
- cpu 設定cgroup中程序的cpu被排程策略。
- cpuacct 可以統計cgroup中程序的cpu佔用。
- cpuset 在多核機器上,設定cgroup中程序可以使用的cpu和記憶體。此處僅限於NUMA架構。
- devices 控制cgroup對裝置的訪問。
- freezer 掛起(suspend)和恢復(resue) cgroup中的程序。
- memory 用於控制cgroup中程序的記憶體佔用。
- net_cls 將cgroup中程序產生的網路包分類,便於linux tc(traffic controller)可以根據分類區分出來自某個cgroup包並做監控。
- net_prio 設定cgroup中程序產生的網路流量的優先順序。
- ns 使cgroup中的程序在新的Namespace中fork新程序時,建立一個新的cgroup,這個cgroup包含新的Namespace中的程序。
每個subsystem會關聯到定義的cgroup,並對這個cgroup中的程序做限制和控制。這些subsystem是逐步合併到核心中的,可以安裝apt-get install cgroup-bin 然後通過 lssubsys -a 檢視
- hierarchy 把一組cgroup串成一個柱狀結構,這樣的樹便是一個hierarchy,通過這種結構,Cgroups可以做到繼承。
三個元件的關係
- 系統建立hierarchy 之後,所有的程序都會加入這個hierarchy的cgroup的根節點。在這個cgroup根節點是hierarchy預設建立的。
- 一個subsystem只能附加到一個hierarchy上面。
- 一個程序可以作為多個cgroup的成員,但是cgroup必須在不同的hierarchy中。
- 一個程序fork的子程序和父程序在同一個cgroup中也可以根據需要移到其他cgroup中。
Kernel介面
前面說道Cgroups中的hierarchy是一種樹狀結構,Kernel為了對Cgroups的配置更直觀,也會顯示為樹狀結構。下面進行例項,瞭解如何操作Cgroups。
首先建立並掛在一個hierarchy(cgroup樹),如下.
[email protected]:~$ mkdir cgroup-test [email protected]:~$ sudo mount -t cgroup -o none,name=cgroup1 cgroup1 ./cgroup-test/ [email protected]:~$ ls ./cgroup-test/ cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks
這些檔案就是這個hierarchy中cgroup根節點的配置項,上面這些檔案含義如下。
- cgroup.clone_children, cpuset的subsystem會讀取這個檔案的配置,如果值是1(預設值0),子cgroup才會繼承父cgroup的cpuset配置。
- cgroup.procs 是樹中當前結點cgroup的程序組id,現在的位置是在根節點,這個檔案中會有現在系統中所有程序組的ID。
- notify_on_release和release_agent會在一起使用。notify_on_release標識當這個cgroup最後一個程序退出的時候是否執行了 release_agent;release_agent則是一個路徑,通常用作程序退出後自動清理掉不再使用的cgroup。
- tasks標識該cgroup下面的程序ID,如果把一個程序ID寫到tasks中便會將相應的程序加入到這個cgroup中。
然後建立剛才建立的hierarchy上cgroup根節點中擴展出的兩個子cgroup。
可以看到建立子資料夾的同時,Kernel會標記這個cgroup的子cgroup,他們會繼承父cgroup的屬性。
- 在cgroup中新增和移動程序 一個程序在Cgroups的hierarchy中,只能在一個cgroup節點上存在,系統所有程序都會預設在根節點上存在,可以將程序移動到其他節點上,只需要將 程序ID移動到cgroup節點的tasks檔案即可。
可以看到當前程序已經被新增到cgroup-1中了。**第一行**
通過subsystem限制cgroup程序的資源 上面的hierarchy沒有關係任何的subsystem,所以沒有限制cgroup佔用的系統資源。本質系統預設為subsystem建立了hierarchy,比如memory的hierarchy。
可以看到/sys/fs/cgroup/memory目錄掛載在memory subsystem的hierarchy上。下面進入到memory目錄下建立cgroup。限制記憶體。
這樣就建立成功,並添加了記憶體使用的限制。
可以看到9752 使用記憶體最大為100M
Docker是如何使用Cgroups的
Docker是通過Cgroups實現容器資源的限制和監控。
可以看到最大限制是134217728 使用的是1970176.這些都是我們在/sys/fs/cgroup/memory中找到的。由此可見docker本質上也是這樣做的。
Go語言實現Cgroups限制容器資源
在Namespace的基礎之上增加Cgroup的限制,使其具有限制記憶體的功能。
package main
import (
"os"
"os/exec"
"log"
"syscall"
"path"
"fmt"
"io/ioutil"
"strconv"
)
const cgroupMemoryHierarchyMount = "/sys/fs/cgroup/memory" //記憶體掛載點的路徑
func main() {//
if os.Args[0] == "/proc/self/exe"{
fmt.Printf("current pid %d", syscall.Getpid())
fmt.Println()
cmd := exec.Command("sh", "-c" ,"strees --vm-bytes 200m --vm-keep -m 1") // 之前我們通過命令列,這裡命令還是一樣的。
cmd.SysProcAttr = &syscall.SysProcAttr{
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run();err!=nil{
fmt.Println(err)
os.Exit(1)
}
}
cmd :=exec.Command("/proc/self/exe")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags:syscall.CLONE_NEWUTS|syscall.CLONE_NEWPID|syscall.CLONE_NEWNS,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err !=nil{
fmt.Println("error", err)
os.Exit(1)
}else {
//獲取fork的程序pid
fmt.Printf("%v" ,cmd.Process.Pid)
// 在系統中預設建立掛在了memory subsystem的hierarchy上建立Cgroup
os.Mkdir(path.Join(cgroupMemoryHierarchyMount,"testmemorylimit"),0755)
// 將容器加入到這個Cgroup中
ioutil.WriteFile(path.Join(cgroupMemoryHierarchyMount,"testmemorylimit","tasks"),[]byte(strconv.Itoa(cmd.Process.Pid)),0644)
//限制cgroup的使用
ioutil.WriteFile(path.Join(cgroupMemoryHierarchyMount,"testmemorylimit","memory.limit_in_bytes"),[]byte("100m"),0644)
}
cmd.Process.Wait()
}
通過top就可以檢視。