本身動手寫docker-3

3.構造容器

示例代碼node

3.1 構造實現run 命令版本的容器

相似這麼啓動./myDocker run -ti /bin/bashgit

大體流程 github

1. 調用本身建立新的namespace
func NewParentProcess(tty bool, command string) *exec.Cmd {
	args := []string{"init", command}
	cmd := exec.Command("/proc/self/exe", args...)
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS |
			syscall.CLONE_NEWNET | syscall.CLONE_NEWIPC,
	}
	if tty {
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
	}
	return cmd
}
// 以後調用cmd.Start(), 調用本身傳入了init,自身處理Init傳參以下
複製代碼
2.在新的namespace中進行init操做並執行命令(例如/bin/bash)
func RunContainerInitProcess(command string, args []string) error {
	logrus.Infof("command %s", command)

	defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
	syscall.Mount("", "/", "", syscall.MS_PRIVATE | syscall.MS_REC, "")
	// 須要加上上面一行,源碼有誤,或者見後面的坑的修改方法,同時這個方法會返回error最好捕捉下,MS_REC爲目錄子樹遞歸的建立綁定掛載
	syscall.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), "")
	argv := []string{command}
	if err := syscall.Exec(command, argv, os.Environ()); err != nil {
		logrus.Errorf(err.Error())
	}
	return nil
}
複製代碼
  • 關於mount namespace和共享子樹
    • mount namespaces提供了過分的隔離,若是徹底隔離,一個掛在可能須要在全部的namespace都掛載一遍
    • mount 時關於共享提供了4個參數MS_SHARED, MS_PRIVATE,MS_SLAVE,MS_UNBINDABLE
  • 有個坑,例如ubuntu18中,默認掛載時shared,能夠經過/proc/$pid/mountinfo
  • 須要從新設置下sudo mount --make-rprivate / 否則namespace裏面修改/proc會影響其餘namespace裏面的/proc(應該只對此行命令以後的子進程等適用)
  • 修改以後
  • 其餘 使用unshare建立新的namespace shell:$unshare --user --mount --ipc --pid --net --uts -r --fork --propagation private bash

3.2 增長容器資源隔離

大體流程 docker

主要邏輯就是捕獲參數修改hierarchy文件系統:如下爲控制cgroup的相關代碼,以memory子系統爲例,
package subsystems

import(
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strconv"
)

type MemorySubSystem struct {
}

func (s *MemorySubSystem) Set(cgroupPath string, res *ResourceConfig) error {
	if subsysCgroupPath, err := GetCgroupPath(s.Name(), cgroupPath, true); err == nil {
		if res.MemoryLimit != "" {
			if err := ioutil.WriteFile(path.Join(subsysCgroupPath, "memory.limit_in_bytes"), []byte(res.MemoryLimit), 0644); err != nil {
				return fmt.Errorf("set cgroup memory fail %v", err)
			}
		}
		return nil
	} else {
		return err
	}

}

func (s *MemorySubSystem) Remove(cgroupPath string) error {
	if subsysCgroupPath, err := GetCgroupPath(s.Name(), cgroupPath, false); err == nil {
		return os.RemoveAll(subsysCgroupPath)
	} else {
		return err
	}
}


func (s *MemorySubSystem) Apply(cgroupPath string, pid int) error {
	if subsysCgroupPath, err := GetCgroupPath(s.Name(), cgroupPath, false); err == nil {
		if err := ioutil.WriteFile(path.Join(subsysCgroupPath, "tasks"),  []byte(strconv.Itoa(pid)), 0644); err != nil {
			return fmt.Errorf("set cgroup proc fail %v", err)
		}
		return nil
	} else {
		return fmt.Errorf("get cgroup %s error: %v", cgroupPath, err)
	}
}


func (s *MemorySubSystem) Name() string {
	return "memory"
}
複製代碼

對於GetCgroupPath方法其實作了如下事情shell

  • 根據/proc/self/mountinfo 找出對應的hierarchy的虛擬文件系統
  • 此文件每行相似(此行爲memory 掛載的hierarchy文件系統) :
    • 46 32 0:41 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,memory, 根據最後的memory 找到/sys/fs/cgroup/memory,
  • 最終此文件夾下建立cgroup文件夾名就是傳入的參數cgroupPath,而後在cgroup下修改限制(Set)或在tasks文件中增長pid(Apply)

3.3 增長管道和環境變量識別

  • 主要是修改爲經過管道傳輸cmd,和加入了環境變量,略
  • 修改以後的流程
相關文章
相關標籤/搜索