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

该博客介绍了如何实现一个简单的容器运行时,类似于Docker的`run`命令。通过分析Go代码,展示了如何使用`/proc`文件系统创建进程,并模拟Docker的命名空间和CGroup限制。代码示例包括`run`和`init`命令的实现,以及对`/proc`目录的解释。最终,通过实际运行和`ps`命令的对比,证明了简易容器的运行效果。
摘要由CSDN通过智能技术生成

原书代码

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

},

}

5ab0d501052b

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

5ab0d501052b

//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)

}

解释一下:

5ab0d501052b

让我们看一下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

}

解释一下:

5ab0d501052b

接着看看返回cmd之后的调用:

5ab0d501052b

查看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

},

}

5ab0d501052b

那么这里看看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

}

解释一下先:

5ab0d501052b

这里的MountFlag的意思如下

MS_NOEXEC 在本文件系统中不允许运行其他程序

MS_NOSUID 在本系统中运行程序的时候不允许set-user-ID或者set-group-ID

MS_NODEV 这个参数是自从Linux 2.4以来所有 mount 的系统都会默认设定的参数

解释一下

5ab0d501052b

——————

运行一下:

#记得先在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`命令,发现容器启动起来以后,打印出来了当前目录的内容,然后退出了.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值