【协作式原创】自己动手写docker之run代码解析 预备知识: Linux命令 预备知识:namespace和cgroup(CentOS7.7) urfave cli预备知识 准备工作 demo 参考

预备知识:namespace和cgroup(CentOS7.7)

一,exec替换进程映像
函数功能: 用exec函数可以把当前进程替换为一个新进程,且新进程与原进程有相同的PID
例如:在shell命令行执行ps命令,实际上是shell进程调用fork复制一个新的子进程,在利用exec系统调用将新产生的子进程完全替换成ps进程。

  • MNT namespace的go代码实现
    启动一个bash进程,并且单独给予和父进程不同的mount namespace.
func main()  {
    cmd := exec.Command("/bin/sh") // 加载可执行文件/bin/sh到内存中并运行。
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWNS,
    }
    cmd.Stdin  = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    if err := cmd.Run(); err != nil {
        log.Printf("Run error:%v
", err)
        log.Fatal(err)
    }
}
  • 对应如下代码
    构造一个命令,用于启动init进程。
func NewParentProcess(command string, tty bool) *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
}

urfave cli预备知识

准备工作

  1. 阿里云抢占式实例:centos7.4
  2. 每次实例释放后都要重新安装go
wget https://dl.google.com/go/go1.13.4.linux-amd64.tar.gz
sudo tar -C /usr/local -xf go1.13.4.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
source ~/.bash_profile

yum -y install nano

yum install git
git clone https://github.com/yudidi/go-docker.git
git branch -all
git checkout remotes/origin/ns

demo

理解为什么增加init命令和为什么挂载proc

1_no_proc
2_add_proc
3_add_proc_and_not_affect_host
【协作式原创】自己动手写docker之run代码解析
预备知识: Linux命令
预备知识:namespace和cgroup(CentOS7.7)
urfave cli预备知识
准备工作
demo
参考
【协作式原创】自己动手写docker之run代码解析
预备知识: Linux命令
预备知识:namespace和cgroup(CentOS7.7)
urfave cli预备知识
准备工作
demo
参考

demo-init源码

syscall.Exec启动进程和os/exec.Command启动进程的区别

  • Q: run和init到底启动了几个进程
    A: 2个进程,一个运行go-docker run,一个运行go-docker init。
    【协作式原创】自己动手写docker之run代码解析
预备知识: Linux命令
预备知识:namespace和cgroup(CentOS7.7)
urfave cli预备知识
准备工作
demo
参考

  • 这些进程的ns
    13864和父进程3911,1号进程均一致
    13868和父进程13864不同
    【协作式原创】自己动手写docker之run代码解析
预备知识: Linux命令
预备知识:namespace和cgroup(CentOS7.7)
urfave cli预备知识
准备工作
demo
参考

  • kill掉13864,不会影响13864的运行(这就是容器内的第一个进程)

[root@192 go-docker]# kill 13864
//
[root@192 go-docker]# ps -ef|grep 138
root      13868      1  0 10:27 pts/0    00:00:00 /proc/self/exe init /bin/sh
root      13962   1323  0 10:50 pts/1    00:00:00 grep --color=auto 138
// 13868进程仍在继续打印时间
  • 所以13864进程不表示容器(杀死了他,子进程仍在打印时间,说明这两个进程没有依赖关系),容器本身就不是一个进程,容器只是描述一种边界,而ns就是实现这个边界的方法。
    ns这个空间中,13864是第一个在该空间运行的进程。

  • 踩坑

[root@192 go-docker]# ./go-docker run --ti /bin/sh
{"level":"fatal","msg":"fork/exec /proc/self/exe: no such file or directory","time":"2020-03-06T08:24:23-05:00"}

原因: // TODO
运行之后关闭之后,centos的/proc就没有了
问题的原理

解决: mount -t proc proc /proc

demo-ns源码

main.go

package main

import (
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
	"os"
)

const usage = `go-docker`

func main() {
	app := cli.NewApp()
	app.Name = "go-docker"
	app.Usage = usage

	app.Commands = []cli.Command{
		runCommand,
		initCommand,
	}
	app.Before = func(context *cli.Context) error {
		logrus.SetFormatter(&logrus.JSONFormatter{})
		logrus.SetOutput(os.Stdout)
		return nil
	}
	if err := app.Run(os.Args); err != nil {
		logrus.Fatal(err)
	}
}

参考

  1. nano在CentOS上的安装和使用
  2. 如何在 CentOS 8 上安装 Go
  3. 用go写一个docker#