1. Cgroup概念
Linux Cgroup提供了對一組進程及子進程的資源限制,控制和統計的能力,這些資源包括CPU,內存,存儲,網絡等。通過Cgroup,可以方便的吸納之某個進程的資源佔用,並且可以實時監控進程和統計信息。
Cgroup完成資源限制主要通過下面三個組件
- cgroup: 是對進程分組管理的一種機制
- subsystem: 是一組資源控制的模塊
- hierarchy: 把一組cgroup串成一個樹狀結構(可讓其實現繼承)
2. Cgroup使用
說半天概念,估計大家也是雲裏霧裏,我直接在Liunx命令行中演示怎麼使用
Cgroup
,這樣大家應該會對Cgroup有一個更清晰的認識。
- 創建一個用來存放掛載點的文件夾
mkdir cgroup-demo
- 掛載hierarchy
mount -t cgroup -o none,name=cgroup-demo cgroup-demo ./cgroup-demo
3. 查看生成的默認文件
一旦我們掛載了
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
的文件,就能達到限制該進程的內存使用。
3. Go語言中使用Cgroup
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了。