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

pibigstar發表於2020-03-19

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

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

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

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

  1. 建立一個用來存放掛載點的資料夾
    mkdir cgroup-demo
  2. 掛載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的檔案,就能達到限制該程式的記憶體使用。

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了。

文章會首發於我微信公眾號上,掃碼關注,及時獲取最新內容

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

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章