// urfaveCli cli 包简单使用,具体可以参考官方文档funcurfaveCli(){app:=cli.NewApp()// 指定全局参数app.Flags=[]cli.Flag{cli.StringFlag{Name:"lang, l",Value:"english",Usage:"Language for the greeting",},cli.StringFlag{Name:"config, c",Usage:"Load configuration from `FILE`",},}// 指定支持的命令列表app.Commands=[]cli.Command{{Name:"complete",Aliases:[]string{"c"},Usage:"complete a task on the list",Action:func(c*cli.Context)error{log.Println("run command complete")fori,v:=rangec.Args(){log.Printf("args i:%v v:%v\n",i,v)}returnnil},},{Name:"add",Aliases:[]string{"a"},// 每个命令下面还可以指定自己的参数Flags:[]cli.Flag{cli.Int64Flag{Name:"priority",Value:1,Usage:"priority for the task",}},Usage:"add a task to the list",Action:func(c*cli.Context)error{log.Println("run command add")fori,v:=rangec.Args(){log.Printf("args i:%v v:%v\n",i,v)}returnnil},},}err:=app.Run(os.Args)iferr!=nil{log.Fatal(err)}}
具体效果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ go run main.go -h
NAME:
main - A new cli application
USAGE:
main [global options]command[command options][arguments...]COMMANDS:
complete, c complete a task on the list
add, a add a task to the list
help, h Shows a list of commands or helpfor one commandGLOBAL OPTIONS:
--lang value, -l value Language for the greeting (default: "english") --config FILE, -c FILE Load configuration from FILE
--help, -h show help
packagemainimport("os"log"github.com/sirupsen/logrus""github.com/urfave/cli")constusage=`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.`funcmain(){app:=cli.NewApp()app.Name="mydocker"app.Usage=usageapp.Commands=[]cli.Command{initCommand,runCommand,}app.Before=func(context*cli.Context)error{// Log as JSON instead of the default ASCII formatter.log.SetFormatter(&log.JSONFormatter{})log.SetOutput(os.Stdout)returnnil}iferr:=app.Run(os.Args);err!=nil{log.Fatal(err)}}
varrunCommand=cli.Command{Name:"run",Usage:`Create a container with namespace and cgroups limit
mydocker run -it [command]`,Flags:[]cli.Flag{cli.BoolFlag{Name:"it",// 简单起见,这里把 -i 和 -t 参数合并成一个Usage:"enable tty",},},/*
这里是run命令执行的真正函数。
1.判断参数是否包含command
2.获取用户指定的command
3.调用Run function去准备启动容器:
*/Action:func(context*cli.Context)error{iflen(context.Args())<1{returnfmt.Errorf("missing container command")}cmd:=context.Args().Get(0)tty:=context.Bool("it")Run(tty,cmd)returnnil},}varinitCommand=cli.Command{Name:"init",Usage:"Init container process run user's process in container. Do not call it outside",/*
1.获取传递过来的 command 参数
2.执行容器初始化操作
*/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)returnerr},}
要实现 run 命令我们需要实现 run、init 两个命令。
run.go
接着看下 Run 函数做了写什么:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Run 执行具体 command/*
这里的Start方法是真正开始前面创建好的command的调用,它首先会clone出来一个namespace隔离的
进程,然后在子进程中,调用/proc/self/exe,也就是调用自己,发送init参数,调用我们写的init方法,
去初始化容器的一些资源。
*/funcRun(ttybool,cmdstring){parent:=container.NewParentProcess(tty,cmd)iferr:=parent.Start();err!=nil{log.Error(err)}_=parent.Wait()os.Exit(-1)}
Run 命令主要调用 NewParentProcess 构建 os/exec.Cmd 对象并执行,执行完成立马退出。
fork/exec /proc/self/exe: no such file or directory
在正常第二次 mydocker 命令时出现该错误,具体如下:
1
2
3
4
5
6
7
root@mydocker:~/mydocker# ./mydocker run -it /bin/ls
{"level":"info","msg":"init come on","time":"2024-01-03T15:07:27+08:00"}{"level":"info","msg":"command: /bin/ls","time":"2024-01-03T15:07:27+08:00"}{"level":"info","msg":"command:/bin/ls","time":"2024-01-03T15:07:27+08:00"}LICENSE Makefile README.md container example go.mod go.sum main.go main_command.go mydocker run.go
root@mydocker:~/mydocker# ./mydocker run -it /bin/ls
{"level":"error","msg":"fork/exec /proc/self/exe: no such file or directory","time":"2024-01-03T15:07:28+08:00"}