七天用Go寫個docker(第二天)

1. Cgroup概念

Linux Cgroup提供了對一組進程及子進程的資源限制,控制和統計的能力,這些資源包括CPU,內存,存儲,網絡等。通過Cgroup,可以方便的吸納之某個進程的資源佔用,並且可以實時監控進程和統計信息。

Cgroup完成資源限制主要通過下面三個組件

  • cgroup: 是對進程分組管理的一種機制
  • subsystem: 是一組資源控制的模塊
  • hierarchy: 把一組cgroup串成一個樹狀結構(可讓其實現繼承)

2. Cgroup使用

說半天概念,估計大家也是雲裏霧裏,我直接在Liunx命令行中演示怎麼使用Cgroup,這樣大家應該會對Cgroup有一個更清晰的認識。

  1. 創建一個用來存放掛載點的文件夾
mkdir cgroup-demo
  1. 掛載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中的進程組ID
  • task: 標識該cgroup下的進程ID,如果將某個進程的ID寫到該文件中,那麼便會將該進程加入到當前的cgroup中。
  1. 新建子cgroup

只要在掛載了hierarchy的文件夾下,新建一個新的文件夾,那麼該新的文件夾會被kernel 自動標記爲該cgroup的子group

cd cgroup-demo
mkdir cgroup1

可以看到,我們新建文件夾之後,文件夾裏面會自動生成一些默認的文件,這個cgroup1就是cgroup-demo的子cgroup,默認情況下,他會繼承父cgroup的配置。

  1. 通過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了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章