原书代码
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`命令,发现容器启动起来以后,打印出来了当前目录的内容,然后退出了.