【协作式原创】自己动手写docker之run代码解析
预备知识: Linux命令
预备知识:namespace和cgroup(CentOS7.7)
一,exec替换进程映像
函数功能: 用exec函数可以把当前进程替换为一个新进程,且新进程与原进程有相同的PID
例如:在shell命令行执行ps命令,实际上是shell进程调用fork复制一个新的子进程,在利用exec系统调用将新产生的子进程完全替换成ps进程。
- MNT namespace的go代码实现
启动一个bash进程,并且单独给予和父进程不同的mount namespace.
func main() {
cmd := exec.Command("/bin/sh") // 加载可执行文件/bin/sh到内存中并运行。
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWNS,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Printf("Run error:%v\n", err)
log.Fatal(err)
}
}
- 对应如下代码
构造一个命令,用于启动init进程。
func NewParentProcess(command string, tty bool) *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
}
urfave cli预备知识
准备工作
- 阿里云抢占式实例:centos7.4
- 每次实例释放后都要重新安装go
wget https://dl.google.com/go/go1.13.4.linux-amd64.tar.gz
sudo tar -C /usr/local -xf go1.13.4.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
source ~/.bash_profile
yum -y install nano
yum install git
git clone https://github.com/yudidi/go-docker.git
git branch -all
git checkout remotes/origin/ns
demo
理解为什么增加init命令和为什么挂载proc
1_no_proc
2_add_proc
3_add_proc_and_not_affect_host
demo-init源码
syscall.Exec启动进程和os/exec.Command启动进程的区别
-
Q: run和init到底启动了几个进程
A: 2个进程,一个运行go-docker run,一个运行go-docker init。
-
这些进程的ns
13864和父进程3911,1号进程均一致
13868和父进程13864不同
-
kill掉13864,不会影响13864的运行(这就是容器内的第一个进程)
[root@192 go-docker]# kill 13864
//
[root@192 go-docker]# ps -ef|grep 138
root 13868 1 0 10:27 pts/0 00:00:00 /proc/self/exe init /bin/sh
root 13962 1323 0 10:50 pts/1 00:00:00 grep --color=auto 138
// 13868进程仍在继续打印时间
-
所以13864进程不表示容器(杀死了他,子进程仍在打印时间,说明这两个进程没有依赖关系),容器本身就不是一个进程,容器只是描述一种边界,而ns就是实现这个边界的方法。
ns这个空间中,13864是第一个在该空间运行的进程。 -
踩坑
[root@192 go-docker]# ./go-docker run --ti /bin/sh
{"level":"fatal","msg":"fork/exec /proc/self/exe: no such file or directory","time":"2020-03-06T08:24:23-05:00"}
原因: // TODO
运行之后关闭之后,centos的/proc就没有了
问题的原理
- Shared subtrees
https://segmentfault.com/a/1190000006912742
https://www.cnblogs.com/sparkdev/p/9424649.html
解决: mount -t proc proc /proc
demo-ns源码
main.go
package main
import (
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"os"
)
const usage = `go-docker`
func main() {
app := cli.NewApp()
app.Name = "go-docker"
app.Usage = usage
app.Commands = []cli.Command{
runCommand,
initCommand,
}
app.Before = func(context *cli.Context) error {
logrus.SetFormatter(&logrus.JSONFormatter{})
logrus.SetOutput(os.Stdout)
return nil
}
if err := app.Run(os.Args); err != nil {
logrus.Fatal(err)
}
}