(4)构造容器之实现run命令版的容器

2018-01-04  本文已影响0人  爱喝咖啡的土拨鼠

linux proc

linux 下的/proc 文件系统是又内核提供的,包含了系统运行时信息(系统内存、mount 设备信息,硬件配置等),为访问内核数据提供接口。

实现runC

项目结构
项目结构.png

https://github.com/justinmjc/mymoby

tag:3.1节

代码解析

main.go

package main

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

const usage  = `my moby is a simple contaniner runtime implementation.`
func main() {
    app :=cli.NewApp()
    app.Name = "mymoby"
    app.Usage=usage

    app.Commands = []cli.Command{
        initCommand,
        runCommand,
    }

    app.Before = func(context *cli.Context) error {
        logrus.SetFormatter(&logrus.JSONFormatter{})

        log.SetOutput(os.Stdout)
        return nil
    }

    if err := app.Run(os.Args); err!=nil{
        log.Fatal(err)
    }
}


使用github.com/urfave/cli提供的命令行工具,定义了mymoby的基本命令initCommand和runCommand,然后在app.Before 初始化日志配置。

接下来开下initCommand和runCommand的定义,在main_command.go文件中

main_command.go

package main

import (
    "github.com/urfave/cli"
    "fmt"
    log "github.com/Sirupsen/logrus"
    "./container"
)

var runCommand = cli.Command{
    Name:"run",
    Usage:`Create a container with namespace and cgroups limit mymoby run -ti [command]`,
    Flags:[]cli.Flag{
        cli.BoolFlag{
        Name:"ti",
        Usage:"enable tty",
        },

    },
    /*
    这里是run命令执行的真正函数。
    1判断参数是否包含command
    2获取用户指定的command
    3调用Run function去准备启动容器
     */
    Action: func(context *cli.Context) error{
        if len(context.Args())<1{
            return fmt.Errorf("Missing container command")
        }
        cmd := context.Args().Get(0)
        tty := context.Bool("ti")
        Run(tty,cmd)
        return nil
    },

}

var initCommand = cli.Command{
    Name: "init",
    Usage: "Init container process run user's process in container.Do not call it outside",
    /*
    1获取传递过来的command参数
    2执行容器初始化操作
     */
    Action: func(context *cli.Context)  error {
        log.Infof("init come on")
        cmd := context.Args().Get(0)
        err :=container.RunContainerInitProcess(cmd, nil)
        return err
    },
}


runCommand中的Run在run.go中定义

package main

import (
    log "github.com/Sirupsen/logrus"
    "./container"
    "os"
)
func Run(tty bool,command string)  {
    parent := container.NewParentProcess(tty,command)
    if err :=parent.Start(); err!=nil{
        log.Error(err)
    }
    parent.Wait()
    os.Exit(1)
}

Run 调用NewParentProcess(),在container_process.go中定义

package container

import (
    "os/exec"
    "syscall"
    "os"
)

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
}


在NewParentProcess()中“/proc/self”指的是当前运行进程自己的环境,exec其实是自己调用自己,通过这种方式对创建出来的进程进行初始化。

args的第一个参数"init",实际上就是会去调用initCommand进行初始化操作

下面的clone参数就是去fork出来一个新进程,使用namespace隔离环境

Run执行完在NewParentProcess()后执行parent.Start(),Start方法是真正前面创建好的command的调用,它首先clone出一个Namespace隔离的进程,然后在子进程中调用/proc/self/exe,发送init,初始化容器的一些资源。

最后,运行



maojiancai@bogon:~/mygo/mymoby$ go build .
maojiancai@bogon:~/mygo/mymoby$ ls
container  main_command.go  main.go  mymoby  README.md  run.go  src
maojiancai@bogon:~/mygo/mymoby$ ./mymoby run -ti /bin/sh
{"level":"error","msg":"fork/exec /proc/self/exe: operation not permitted","time":"2018-01-02T06:56:12-08:00"}
maojiancai@bogon:~/mygo/mymoby$ sudo ./mymoby run -ti /bin/sh
[sudo] password for maojiancai:
{"level":"info","msg":"init come on","time":"2018-01-02T06:56:44-08:00"}
{"level":"info","msg":"command %s/bin/sh","time":"2018-01-02T06:56:44-08:00"}
# ps -ef
UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 06:56 pts/0    00:00:00 /bin/sh
root          4      1  0 06:56 pts/0    00:00:00 ps -ef
#

在init.go 中调用的syscall.Exec方法,最终调用了Kernel的execve 这个系统函数。它的作用是执行当前filename对应的程序。他会覆盖当前进行的镜像、数据和堆栈信息,PID,这些都会被将要运行的程序覆盖。

上一篇下一篇

猜你喜欢

热点阅读