一、shim

containerd下的shim是充当containerd和runc之间的中间件,用来组装runc命令的参数,负责容器中进程的启动。
containerd中调用shim时执行的命令如下:

/root/lib-containerd/containerd/bin/containerd-shim {containerID} {bundleDirPath} runc

其中三个参数如下:
1、Arg0:容器Id
2、Arg1:路径
3、Arg2:执行时间

二、主函数main()

func main() {
	flag.Parse()
	cwd, err := os.Getwd() //获取当前进程目录cwd, /run/docker/libcontainerd/containerd/{containerID}/init
	if err != nil {
		panic(err)
	}
	//打开shim的日志文件:/var/run/docker/libcontainerd/containerd/{containerID}/init/shim-log.json
	f, err := os.OpenFile(filepath.Join(cwd, "shim-log.json"), os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0666)
	if err != nil {
		panic(err)
	}
	//启动容器
	if err := start(f); err != nil {
		//启动失败时,把错误信息err记录到shim的log文件f中
		if err == errRuntime {
			f.Close()
			return
		}
		//记录错误,而不是写入到stderr,因为垫片将有/dev/null,因为它是stdio,因为它应该被重新创建到系统init,并没有任何人从它读取
		writeMessage(f, "error", err)
		f.Close()
		os.Exit(1)
	}
}

三、start()函数

核心步骤是基于containerID,bundle,runtimeName 构建一个进程对象,即type process struct。
然后p.create()调用runc命令启动进程。

func start(log *os.File) error {
	// 尽快开始处理信号,以便正确地获取信息,或者在我们执行处理程序之前运行时退出
	signals := make(chan os.Signal, 2048)
	signal.Notify(signals)
	// 将shim设置为容器创建的所有孤立进程的子进程回收
	if err := osutils.SetSubreaper(1); err != nil {
		return err
	}
	// 打开exit管道(p),用来发送退出信号
	f, err := os.OpenFile("exit", syscall.O_WRONLY, 0)
	if err != nil {
		return err
	}
	defer f.Close()
	//打开control管道,可发送和接收容器控制信息:/var/run/docker/libcontainerd/containerd/redis/init/control
	control, err := os.OpenFile("control", syscall.O_RDWR, 0)
	if err != nil {
		return err
	}
	defer control.Close()
	//基于containerID,bundle,runtimeName 构建一个进程对象,即process构造函数
	p, err := newProcess(flag.Arg(0), flag.Arg(1), flag.Arg(2))
	if err != nil {
		return err
	}
	defer func() {
		if err := p.Close(); err != nil {
			writeMessage(log, "warn", err)
		}
	}()
	//创建进程,调用`runc`命令
	if err := p.create(); err != nil {
		p.delete()
		return err
	}
	//创建控制信息通道
	msgC := make(chan controlMessage, 32)
	go func() {
		//一个死循环,用于接收信号:将control管道的信息格式化写入channel controlMessage
		for {
			var m controlMessage
			if _, err := fmt.Fscanf(control, "%d %d %d\n", &m.Type, &m.Width, &m.Height); err != nil {
				continue
			}
			msgC <- m
		}
	}()
	var exitShim bool
	for {
		select {
		case s := <-signals: //监听信号通道signals
			switch s {
			case syscall.SIGCHLD:
				exits, _ := osutils.Reap()
				for _, e := range exits {
					// check to see if runtime is one of the processes that has exited
					if e.Pid == p.pid() {
						exitShim = true
						writeInt("exitStatus", e.Status)
					}
				}
			}
			// 运行时已经退出,所以垫片也可以退出
			if exitShim {
				f.Close()
				p.Wait()
				return nil
			}
		case msg := <-msgC:
			//监听控制信息通道 controlMessage:0为关闭进程输入,1为调整窗口大小
			switch msg.Type {
			case 0:
				// 关闭标准输入输出
				if p.stdinCloser != nil {
					p.stdinCloser.Close()
				}
			case 1:
				if p.console == nil {
					continue
				}
				ws := term.Winsize{
					Width:  uint16(msg.Width),
					Height: uint16(msg.Height),
				}
				term.SetWinsize(p.console.Fd(), &ws)
			}
		}
	}
	return nil
}

四、process构造方法

type process struct {
	sync.WaitGroup
	id             string //进程编号
	bundle         string
	stdio          *stdio //标准输入输出
	exec           bool
	containerPid   int //容器进程ID
	checkpoint     *checkpoint
	checkpointPath string
	shimIO         *IO 
	stdinCloser    io.Closer
	console        *os.File
	consolePath    string
	state          *processState
	runtime        string
}

1、newProcess()函数

func newProcess(id, bundle, runtimeName string) (*process, error) {
	p := &process{
		id:      id,
		bundle:  bundle,
		runtime: runtimeName,
	}
	//读取该进程的process.json文件,得到一个type processState struct对象
	s, err := loadProcess()
	if err != nil {
		return nil, err
	}
	p.state = s
	if s.CheckpointPath != "" {
		cpt, err := loadCheckpoint(s.CheckpointPath)
		if err != nil {
			return nil, err
		}
		p.checkpoint = cpt
		p.checkpointPath = s.CheckpointPath
	}
	//打开进程的输入输出端
	if err := p.openIO(); err != nil {
		return nil, err
	}
	return p, nil
}

2、create()函数

func (p *process) create() error {
	cwd, err := os.Getwd()
	if err != nil {
		return err
	}
	//设置runc日志路径
	logPath := filepath.Join(cwd, "log.json")
	//开始根据不同情况组装`runc`的参数
	args := append([]string{
		"--log", logPath,
		"--log-format", "json",
	}, p.state.RuntimeArgs...)
	//`docker exec`时标记为true了 
	if p.state.Exec {
		args = append(args, "exec",
			"-d",
			"--process", filepath.Join(cwd, "process.json"),
			"--console", p.consolePath,
		)
	} else if p.checkpoint != nil {
		args = append(args, "restore",
			"--image-path", p.checkpointPath,
			"--work-path", filepath.Join(p.checkpointPath, "criu.work", "restore-"+time.Now().Format(time.RFC3339)),
		)
		add := func(flags ...string) {
			args = append(args, flags...)
		}
		if p.checkpoint.Shell {
			add("--shell-job")
		}
		if p.checkpoint.TCP {
			add("--tcp-established")
		}
		if p.checkpoint.UnixSockets {
			add("--ext-unix-sk")
		}
		if p.state.NoPivotRoot {
			add("--no-pivot")
		}
		for _, ns := range p.checkpoint.EmptyNS {
			add("--empty-ns", ns)
		}

	} else {
		//初始化进程走这通道
		args = append(args, "create",
			"--bundle", p.bundle,
			"--console", p.consolePath,
		)
		if p.state.NoPivotRoot {
			args = append(args, "--no-pivot")
		}
	}
	args = append(args,
		"--pid-file", filepath.Join(cwd, "pid"),
		p.id,
	)
	//构造`runc` 命令
	cmd := exec.Command(p.runtime, args...)
	cmd.Dir = p.bundle
	cmd.Stdin = p.stdio.stdin
	cmd.Stdout = p.stdio.stdout
	cmd.Stderr = p.stdio.stderr
	//调用setPDeathSig来设置SysProcAttr作为特定平台的元素
	cmd.SysProcAttr = setPDeathSig()
	//开始`runc` 命令
	if err := cmd.Start(); err != nil {
		if exErr, ok := err.(*exec.Error); ok {
			if exErr.Err == exec.ErrNotFound || exErr.Err == os.ErrNotExist {
				return fmt.Errorf("%s not installed on system", p.runtime)
			}
		}
		return err
	}
	p.stdio.stdout.Close()
	p.stdio.stderr.Close()
	//等待命令执行完毕
	if err := cmd.Wait(); err != nil {
		if _, ok := err.(*exec.ExitError); ok {
			return errRuntime
		}
		return err
	}
	//读取pid文件信息,得到进程pid数据,pid文件中信息由runc写入
	data, err := ioutil.ReadFile("pid")
	if err != nil {
		return err
	}
	//string类型转int类型
	pid, err := strconv.Atoi(string(data))
	if err != nil {
		return err
	}
	//回填容器的属性pid
	p.containerPid = pid
	return nil
}
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐