Linux Cgroup提供了對一組程式及子程式的資源限制,控制和統計的能力,這些資源包括CPU,記憶體,儲存,網路等。通過Cgroup,可以方便的吸納之某個程式的資源佔用,並且可以實時監控程式和統計資訊。
Cgroup完成資源限制主要通過下面三個元件
- cgroup: 是對程式分組管理的一種機制
- subsystem: 是一組資源控制的模組
- hierarchy: 把一組cgroup串成一個樹狀結構(可讓其實現繼承)
說半天概念,估計大家也是雲裡霧裡,我直接在Liunx命令列中演示怎麼使用
Cgroup
,這樣大家應該會對Cgroup有一個更清晰的認識。
- 建立一個用來存放掛載點的資料夾
mkdir cgroup-demo
- 掛載hierarchy
mount -t cgroup -o none,name=cgroup-demo cgroup-demo ./cgroup-demo
- 檢視生成的預設檔案
一旦我們掛載了
hierarchy
,那麼就會在這個資料夾中生成一些預設檔案ls cgroup-demo
大致解釋下這幾個檔案的作用,主要是這個task
檔案
cgroup.clone_children
:cpuset的subsystem會讀取該檔案,如果該檔案裡面的值為1的話,那麼子cgroup將會繼承父cgroup的cpuset配置cgroup.procs
:記錄了樹中當前節點cgroup中的程式組IDtask
: 標識該cgroup下的程式ID,如果將某個程式的ID寫到該檔案中,那麼便會將該程式加入到當前的cgroup中。
- 新建
子cgroup
只要在掛載了
hierarchy
的資料夾下,新建一個新的資料夾,那麼該新的資料夾會被kernel
自動標記為該cgroup的子group
cd cgroup-demo
mkdir cgroup1
可以看到,我們新建資料夾之後,資料夾裡面會自動生成一些預設的檔案,這個
cgroup1
就是cgroup-demo
的子cgroup,預設情況下,他會繼承父cgroup的配置。
- 通過subsystem限制cgroup中程式的資源
上面建立的hierarchy並沒有關聯到任何的subsystem,所以沒辦法通過上面的hierarchy中的cgroup節點來限制程式的資源佔用,其實系統預設已經為每個subsystem建立了一個預設的hierarchy,它在Linux的
/sys/fs/cgroup
路徑下ls /sys/fs/cgroup
如果我們想限制某個程式ID的記憶體,那麼就在/sys/fs/cgroup/memory
資料夾下建立一個限制 mermory的cgroup,建立方式和上面一樣,只要建立一個資料夾即可,kernel 自動把該資料夾標記為一個cgroup,我們來嘗試一下
cd /sys/fs/cgroup/memory
mkdir cgroup-demo-memory
可以看到該檔案下,自動給我們建立出來了很多限制資原始檔,我們只要將
程式ID
寫到該資料夾下的task
檔案中,然後修改其meory.limit_in_bytes
的檔案,就能達到限制該程式的記憶體使用。
package main
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"strconv"
"syscall"
)
const (
// 掛載了 memory subsystem的hierarchy的根目錄位置
cgroupMemoryHierarchyMount = "/sys/fs/cgroup/memory"
)
func main() {
if os.Args[0] == "/proc/self/exe" {
//容器程式
fmt.Printf("current pid %d \n", syscall.Getpid())
cmd := exec.Command("sh", "-c", "stress --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 {
panic(err)
}
}
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
err := cmd.Start()
if err != nil {
panic(err)
}
// 得到 fork出來程式對映在外部名稱空間的pid
fmt.Printf("%+v", cmd.Process.Pid)
// 建立子cgroup
newCgroup := path.Join(cgroupMemoryHierarchyMount, "cgroup-demo-memory")
if err := os.Mkdir(newCgroup, 0755); err != nil {
panic(err)
}
// 將容器程式放到子cgroup中
if err := ioutil.WriteFile(path.Join(newCgroup, "tasks"), []byte(strconv.Itoa(cmd.Process.Pid)), 0644); err != nil {
panic(err)
}
// 限制cgroup的記憶體使用
if err := ioutil.WriteFile(path.Join(newCgroup, "memory.limit_in_bytes"), []byte("100m"), 0644); err != nil {
panic(err)
}
cmd.Process.Wait()
}
這兩節帶大家瞭解了docker的原理,那麼下一節我將帶領大家用go把docker容器的框架搭建起來,開始真正編寫docker了。
文章會首發於我微信公眾號上,掃碼關注,及時獲取最新內容
本作品採用《CC 協議》,轉載必須註明作者和本文連結