原書代碼
https://github.com/xianlubird/mydocker.git
#code-3.1
Linux Proc
Linux 下的/proc 文件系統(tǒng)是由內(nèi)核提供,它其實(shí)不是一個真正的文件系統(tǒng)浑槽,只包含了系統(tǒng)運(yùn)行時信息(比如系統(tǒng)內(nèi)存居扒,mount 設(shè)備信息正什,一些硬件配置等等,它只存在于內(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
當(dāng)你去遍歷這個目錄的時候會發(fā)現(xiàn)很多數(shù)字许帐,這些都是為每個進(jìn)程創(chuàng)建的空間劳坑,數(shù)字就是他們的 PID。
重要術(shù)語 | 相關(guān)說明 |
---|---|
/proc/N | pid為N的進(jìn)程信息 |
/proc/N/cmdline | 進(jìn)程啟動命令 |
/proc/N/cwd | 鏈接到進(jìn)程當(dāng)前工作目錄 |
/proc/N/environ | 進(jìn)程環(huán)境變量列表 |
/proc/N/exe | 鏈接到進(jìn)程的執(zhí)行命令文件 |
/proc/N/fd | 包含進(jìn)程相關(guān)的所有的文件描述符 |
/proc/N/maps | 與進(jìn)程相關(guān)的內(nèi)存映射信息 |
/proc/N/mem | 指代進(jìn)程持有的內(nèi)存成畦,不可讀 |
/proc/N/root | 鏈接到進(jìn)程的根目錄 |
/proc/N/stat | 進(jìn)程的狀態(tài) |
/proc/N/statm | 進(jìn)程使用的內(nèi)存的狀態(tài) |
/proc/N/status | 進(jìn)程狀態(tài)信息泡垃,比stat/statm更具可讀性 |
/proc/self | 鏈接到當(dāng)前正在運(yùn)行的進(jìn)程 |
實(shí)現(xiàn) run 命令
實(shí)現(xiàn)一個簡單版本的run命令,類似docker run -ti [command]
代碼目錄結(jié)構(gòu)如下:
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
}
//運(yùn)行出錯時 記錄日志
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
分別看下兩條命令的具體定義蔑穴,查看main_command.go文件
runcommand命令實(shí)現(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方法去準(zhǔn)備啟動容器
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命令的具體實(shí)現(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)中不允許運(yùn)行其他程序
- MS_NOSUID 在本系統(tǒng)中運(yùn)行程序的時候不允許
set-user-ID
或者set-group-ID
- MS_NODEV 這個參數(shù)是自從Linux 2.4以來所有 mount 的系統(tǒng)都會默認(rèn)設(shè)定的參數(shù)
解釋一下
——————
運(yùn)行一下:
#記得先在GOPATH準(zhǔn)備兩個包
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
#
對比一下運(yùn)行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
#
是不是相類似呢惧浴?在運(yùn)行個/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)是繼承自我們的父進(jìn)程的,這里我們運(yùn)行了一下`ls`命令衷旅,發(fā)現(xiàn)容器啟動起來以后捐腿,打印出來了當(dāng)前目錄的內(nèi)容,然后退出了.