linux proc
linux 下的/proc 文件系統(tǒng)是又內(nèi)核提供的,包含了系統(tǒng)運(yùn)行時(shí)信息(系統(tǒng)內(nèi)存瞬场、mount 設(shè)備信息野瘦,硬件配置等),為訪問(wèn)內(nèi)核數(shù)據(jù)提供接口查剖。
實(shí)現(xiàn)runC
項(xiàng)目結(jié)構(gòu)
https://github.com/justinmjc/mymoby
tag:3.1節(jié)
代碼解析
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 初始化日志配置笋庄。
接下來(lái)開(kāi)下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命令執(zhí)行的真正函數(shù)。
1判斷參數(shù)是否包含command
2獲取用戶指定的command
3調(diào)用Run function去準(zhǔn)備啟動(dòng)容器
*/
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獲取傳遞過(guò)來(lái)的command參數(shù)
2執(zhí)行容器初始化操作
*/
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 調(diào)用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”指的是當(dāng)前運(yùn)行進(jìn)程自己的環(huán)境菌仁,exec其實(shí)是自己調(diào)用自己,通過(guò)這種方式對(duì)創(chuàng)建出來(lái)的進(jìn)程進(jìn)行初始化静暂。
args的第一個(gè)參數(shù)"init"济丘,實(shí)際上就是會(huì)去調(diào)用initCommand進(jìn)行初始化操作
下面的clone參數(shù)就是去fork出來(lái)一個(gè)新進(jìn)程,使用namespace隔離環(huán)境
Run執(zhí)行完在NewParentProcess()后執(zhí)行parent.Start()洽蛀,Start方法是真正前面創(chuàng)建好的command的調(diào)用摹迷,它首先clone出一個(gè)Namespace隔離的進(jìn)程,然后在子進(jìn)程中調(diào)用/proc/self/exe,發(fā)送init,初始化容器的一些資源郊供。
最后峡碉,運(yùn)行
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 中調(diào)用的syscall.Exec方法,最終調(diào)用了Kernel的execve 這個(gè)系統(tǒng)函數(shù)驮审。它的作用是執(zhí)行當(dāng)前filename對(duì)應(yīng)的程序鲫寄。他會(huì)覆蓋當(dāng)前進(jìn)行的鏡像、數(shù)據(jù)和堆棧信息疯淫,PID地来,這些都會(huì)被將要運(yùn)行的程序覆蓋。