自己动手写docker笔记(4)构造简单实现run命令版本的容器

原书代码

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

Linux Proc

Linux 下的/proc 文件系统是由内核提供,它其实不是一个真正的文件系统,只包含了系统运行时信息(比如系统内存,mount 设备信息,一些硬件配置等等,它只存在于内存中,而不占用外存空间。它是以文件系统的形式为访问内核数据的操作提供接口。
比如说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

当你去遍历这个目录的时候会发现很多数字,这些都是为每个进程创建的空间,数字就是他们的 PID。

重要术语 相关说明
/proc/N pid为N的进程信息
/proc/N/cmdline 进程启动命令
/proc/N/cwd 链接到进程当前工作目录
/proc/N/environ 进程环境变量列表
/proc/N/exe 链接到进程的执行命令文件
/proc/N/fd 包含进程相关的所有的文件描述符
/proc/N/maps 与进程相关的内存映射信息
/proc/N/mem 指代进程持有的内存,不可读
/proc/N/root 链接到进程的根目录
/proc/N/stat 进程的状态
/proc/N/statm 进程使用的内存的状态
/proc/N/status 进程状态信息,比stat/statm更具可读性
/proc/self 链接到当前正在运行的进程

实现 run 命令

实现一个简单版本的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函数写了些什么:

package main

import (
    log "github.com/Sirupsen/logrus"
    "github.com/urfave/cli"//这个包提供了命令行工具
    "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` 内初始化了一下`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命令实现

//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命令执行的真正函数
    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)//调用Run方法去准备启动容器
        return nil
    },
}

先来看看Run函数做了些什么:

//run函数在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函数都写了些什么

//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之后的调用:


查看InitCommand命令的具体实现

//main_command.go
//此方法为内部操作,禁止外部调用
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)//获取传递过来的参数
        log.Infof("command %s", cmd)//写入日志
        err := container.RunContainerInitProcess(cmd, nil)//执行容器初始化操作
        return err
    },
}

那么这里看看RunContainerInitProcess函数做了些什么

//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 在本文件系统中不允许运行其他程序
  • MS_NOSUID 在本系统中运行程序的时候不允许set-user-ID或者set-group-ID
  • MS_NODEV 这个参数是自从Linux 2.4以来所有 mount 的系统都会默认设定的参数

解释一下

——————

运行一下:

#记得先在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`,所以目前我们的系统文件系统是继承自我们的父进程的,这里我们运行了一下`ls`命令,发现容器启动起来以后,打印出来了当前目录的内容,然后退出了.
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • linux资料总章2.1 1.0写的不好抱歉 但是2.0已经改了很多 但是错误还是无法避免 以后资料会慢慢更新 大...
    数据革命阅读 12,128评论 2 34
  • 转载自:http://blog.csdn.net/hguisu/article/details/6122513原作...
    miaoiao阅读 1,507评论 0 7
  • 线性表 一 线性表特点 除第一个元素外,其他元素的前面都只有一个元素 除最后一个元素外,其他位置的数据后面都只有一...
    Carrism阅读 308评论 0 0
  • 我以为我是个心底藏着很多故事的人,但奈何无太多表达能力,所以只能掩藏于心,但我还是想把这样的我讲给你听……好了,先...
    遇见yj阅读 1,056评论 28 6