自己動手寫docker筆記(4)構造簡單實現(xiàn)run命令版本的容器

原書代碼

https://github.com/xianlubird/mydocker.git
#code-3.1

Linux Proc

Linux 下的/proc 文件系統(tǒng)是由內(nèi)核提供,它其實不是一個真正的文件系統(tǒng),只包含了系統(tǒng)運行時信息(比如系統(tǒng)內(nèi)存,mount 設備信息,一些硬件配置等等,它只存在于內(nèi)存中,而不占用外存空間。它是以文件系統(tǒng)的形式為訪問內(nèi)核數(shù)據(jù)的操作提供接口。
比如說lsmod就和cat /proc/modules是等效的

root@taroballs-PC:~# ls /proc/
1     1284  1524  1802  2103  31    430   514        cpuinfo      modules
10    1294  1525  1815  2144  312   4332  515        crypto       mounts
1005  13    1529  1818  2148  318   435   516        devices      mtrr
1030  1312  153   1826  22    32    436   517        diskstats    net
1031  1320  1530  1832  221   3237  437   518        dma          pagetypeinfo
1032  1346  154   1837  225   3241  438   525        driver       partitions

當你去遍歷這個目錄的時候會發(fā)現(xiàn)很多數(shù)字,這些都是為每個進程創(chuàng)建的空間,數(shù)字就是他們的 PID。

重要術語 相關說明
/proc/N pid為N的進程信息
/proc/N/cmdline 進程啟動命令
/proc/N/cwd 鏈接到進程當前工作目錄
/proc/N/environ 進程環(huán)境變量列表
/proc/N/exe 鏈接到進程的執(zhí)行命令文件
/proc/N/fd 包含進程相關的所有的文件描述符
/proc/N/maps 與進程相關的內(nèi)存映射信息
/proc/N/mem 指代進程持有的內(nèi)存,不可讀
/proc/N/root 鏈接到進程的根目錄
/proc/N/stat 進程的狀態(tài)
/proc/N/statm 進程使用的內(nèi)存的狀態(tài)
/proc/N/status 進程狀態(tài)信息,比stat/statm更具可讀性
/proc/self 鏈接到當前正在運行的進程

實現(xiàn) run 命令

實現(xiàn)一個簡單版本的run命令,類似docker run -ti [command]

代碼目錄結構如下:

root@taroballs-PC:~# tree  mydocker/ -L 2
mydocker/
├── container
│   ├── container_process.go
│   └── init.go
├── Godeps
│   ├── Godeps.json
│   └── Readme
├── main_command.go
├── main.go
├── run.go
└── vendor
    ├── github.com
    └── golang.org

首先分析下main.go函數(shù)寫了些什么:

package main

import (
    log "github.com/Sirupsen/logrus"
    "github.com/urfave/cli"http://這個包提供了命令行工具
    "os"
)

const usage = `mydocker is a simple container runtime implementation.
                   The purpose of this project is to learn how docker works and how to write a docker by ourselves
                   Enjoy it, just for fun.`

func main() {
    app := cli.NewApp()
    app.Name = "mydocker"
    app.Usage = usage
    //暫時定義兩個命令init、run
    app.Commands = []cli.Command{
        initCommand,
        runCommand,
    }
    //`app.Before` 內(nèi)初始化了一下`logrus`的日志配置。
    app.Before = func(context *cli.Context) error {
        // Log as JSON instead of the default ASCII formatter.
        log.SetFormatter(&log.JSONFormatter{})
        log.SetOutput(os.Stdout)
        return nil
    }
    //運行出錯時 記錄日志
    if err := app.Run(os.Args); err != nil {
        log.Fatal(err)
    }
}

分別看下兩條命令的具體定義,查看main_command.go文件

runcommand命令實現(xiàn)

//main_command.go
var runCommand = cli.Command{
    Name:  "run",
    Usage: `Create a container with namespace and cgroups limit
                mydocker run -ti [command]`,
    Flags: []cli.Flag{
        cli.BoolFlag{
            Name:        "ti",
            Usage:       "enable tty",
        },
    },
    //這里是run命令執(zhí)行的真正函數(shù)
    Action: func(context *cli.Context) error {
        if len(context.Args()) < 1 {//判斷是否包含參數(shù)
            return fmt.Errorf("Missing container command")
        }
        cmd := context.Args().Get(0)//獲取參數(shù)
        tty := context.Bool("ti")
        Run(tty, cmd)//調(diào)用Run方法去準備啟動容器
        return nil
    },
}

先來看看Run函數(shù)做了些什么:

//run函數(shù)在run.go中

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)
}

解釋一下:

讓我們看一下NewParentProcess函數(shù)都寫了些什么

//container/container_process.go 
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之后的調(diào)用:


查看InitCommand命令的具體實現(xiàn)

//main_command.go
//此方法為內(nèi)部操作,禁止外部調(diào)用
var initCommand = cli.Command{
    Name:   "init",
    Usage:  "Init container process run user's process in container. Do not call it outside",
    Action: func(context *cli.Context) error {
        log.Infof("init come on")
        cmd := context.Args().Get(0)//獲取傳遞過來的參數(shù)
        log.Infof("command %s", cmd)//寫入日志
        err := container.RunContainerInitProcess(cmd, nil)//執(zhí)行容器初始化操作
        return err
    },
}

那么這里看看RunContainerInitProcess函數(shù)做了些什么

//container/init.go 
func RunContainerInitProcess(command string, args []string) error {
    logrus.Infof("command %s", command)

    defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
    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
}

解釋一下先:

這里的MountFlag的意思如下

  • MS_NOEXEC 在本文件系統(tǒng)中不允許運行其他程序
  • MS_NOSUID 在本系統(tǒng)中運行程序的時候不允許set-user-ID或者set-group-ID
  • MS_NODEV 這個參數(shù)是自從Linux 2.4以來所有 mount 的系統(tǒng)都會默認設定的參數(shù)

解釋一下

——————

運行一下:

#記得先在GOPATH準備兩個包
git clone https://github.com/Sirupsen/logrus.git
git clone https://github.com/urfave/cli.git
#記得移動到GOPATH跑程序

Result

root@taroballs-PC:/home/taroballs/go/src/github.com/xianlubird/mydocker# ./mydocker run -ti /bin/sh
{"level":"info","msg":"init come on","time":"2018-02-01T00:47:22+08:00"}
{"level":"info","msg":"command /bin/sh","time":"2018-02-01T00:47:22+08:00"}
{"level":"info","msg":"command /bin/sh","time":"2018-02-01T00:47:22+08:00"}
# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 00:47 pts/0    00:00:00 /bin/sh
root         6     1  0 00:47 pts/0    00:00:00 ps -ef
# 

對比一下運行docker鏡像容器

root@taroballs-PC:~# docker run -ti ubuntu /bin/sh
# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 16:51 pts/0    00:00:00 /bin/sh
root         5     1  0 16:51 pts/0    00:00:00 ps -ef
# 

是不是相類似呢?在運行個/bin/ls試試看

root@taroballs-PC:/home/taroballs/go/src/github.com/xianlubird/mydocker# ./mydocker run -ti /bin/ls
{"level":"info","msg":"init come on","time":"2018-02-01T00:54:45+08:00"}
{"level":"info","msg":"command /bin/ls","time":"2018-02-01T00:54:45+08:00"}
{"level":"info","msg":"command /bin/ls","time":"2018-02-01T00:54:45+08:00"}
container  main_command.go  mydocker  README.md  vendor
Godeps     main.go      network   run.go
root@taroballs-PC:/home/taroballs/go/src/github.com/xianlubird/mydocker# 
#由于我們沒有`chroot`,所以目前我們的系統(tǒng)文件系統(tǒng)是繼承自我們的父進程的,這里我們運行了一下`ls`命令,發(fā)現(xiàn)容器啟動起來以后,打印出來了當前目錄的內(nèi)容,然后退出了.
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容