目標(biāo)
這篇博客的目標(biāo)是實(shí)現(xiàn)如下命令.
root@nicktming:~/go/src/github.com/nicktming/mydocker# go build .
root@nicktming:~/go/src/github.com/nicktming/mydocker# ./mydocker run -it /bin/sh
# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 23:55 pts/4 00:00:00 /bin/sh
root 4 1 0 23:56 pts/4 00:00:00 ps -ef
源碼: 代碼下載
git checkout
實(shí)現(xiàn)
實(shí)現(xiàn)run方法
根據(jù)前面博客對(duì)
urfave/cli的介紹,所以在main方法中直接使用. 該run方法很簡(jiǎn)單,就是調(diào)用/bin/sh命令.
.
|-- command
| |-- command.go
| `-- run.go
|-- main.go
|-- README.md
`-- urfave-cli-examples
|-- test01.go
|-- test02.go
`-- test03.go
main方法
package main
import (
"github.com/nicktming/mydocker/command"
"github.com/urfave/cli"
"log"
"os"
)
func main() {
app := cli.NewApp()
app.Name = "mydocker"
app.Usage = "implementation of mydocker"
app.Commands = []cli.Command{
command.RunCommand,
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
可以看一下在
command/command.go文件中RunCommand
var RunCommand = cli.Command{
Name: "run",
Flags: []cli.Flag {
cli.BoolFlag{
Name: "it",
Usage: "enable tty",
},
},
Action: func(c *cli.Context) error {
tty := c.Bool("it")
command := c.Args().Get(0)
Run(command, tty)
return nil
},
}
可以看到
run命令中有一個(gè)flag為it用于執(zhí)行命令時(shí)是否需要tty,之后會(huì)調(diào)用command/run.go中的Run方法.
func Run(command string, tty bool) {
cmd := exec.Command(command)
// for kinds of namespace
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS | syscall.CLONE_NEWNET | syscall.CLONE_NEWIPC,
}
if tty {
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
cmd.Stdin = os.Stdin
}
/**
* Start() will not block, so it needs to use Wait()
* Run() will block
*/
if err := cmd.Start(); err != nil {
log.Printf("Run Start err: %v.\n", err)
log.Fatal(err)
}
cmd.Wait()
}
執(zhí)行命令
go build .
./mydocker run -it /bin/sh
但是存在一個(gè)問(wèn)題是當(dāng)執(zhí)行ps -ef時(shí)還是可以看到整個(gè)宿主機(jī)的進(jìn)程. 但是先執(zhí)行mount -t proc proc /proc后在執(zhí)行ps -ef的時(shí)候就只顯示當(dāng)前namespace內(nèi)進(jìn)程的狀態(tài)了, 這就是mount namespace的作用. 因此接下來(lái)的一個(gè)任務(wù)就是要在此容器起來(lái)前先執(zhí)行該mount命令.
實(shí)現(xiàn)init命令
需要做以下修改
1. 在command/command.go中增加InitCommand命令
var InitCommand = cli.Command{
Name: "init",
Flags: []cli.Flag {
cli.BoolFlag{
Name: "it",
Usage: "enable tty",
},
},
Action: func(c *cli.Context) error {
command := c.Args().Get(0)
Init(command)
return nil
},
}
2. 在
command增加init.go以下內(nèi)容:
package command
import (
"log"
"os"
"os/exec"
"syscall"
)
func Init(command string) {
defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
syscall.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), "")
cmd := exec.Command(command)
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
log.Printf("Init Run() function err : %v\n", err)
log.Fatal(err)
}
}
3. 在
command/run.go中的Run方法中修改, 將先執(zhí)行當(dāng)前進(jìn)程(/proc/self/exe)的init命令, 參數(shù)為command. 表示在執(zhí)行用戶的command命令以前,在已經(jīng)做好namespace隔離的進(jìn)程中先執(zhí)行init命令(其實(shí)就是執(zhí)行mount -t proc proc /proc操作), 然后再執(zhí)行用戶command.
//cmd := exec.Command(command)
cmd := exec.Command("/proc/self/exe", "init", command)
4. 在
main.go的main方法中加入InitCommand命令.
app.Commands = []cli.Command{
command.RunCommand,
command.InitCommand,
}
運(yùn)行
go build .
./mydocker run -it /bin/sh
# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 23:51 pts/3 00:00:00 /proc/self/exe init /bin/sh
root 4 1 0 23:51 pts/3 00:00:00 /bin/sh
root 5 4 0 23:51 pts/3 00:00:00 ps -ef
可以看到容器內(nèi)
1號(hào)進(jìn)程是進(jìn)入容器內(nèi)第一個(gè)執(zhí)行的進(jìn)程, 這個(gè)就是PID namespace的作用. 但是這個(gè)依然沒(méi)有達(dá)到預(yù)期的目標(biāo), 因?yàn)檫@個(gè)1號(hào)是程序自己因?yàn)樾枰プ?code>mount proc proc /proc操作而使用的一個(gè)進(jìn)程, 并不是我們預(yù)期的用戶進(jìn)程在這里是4. (是由init進(jìn)程fork出來(lái)執(zhí)行command(/bin/sh)的一個(gè)子進(jìn)程.)
因此接下來(lái)的任務(wù)是將這個(gè)
init隱藏起來(lái), 讓用戶初始容器的命令進(jìn)程成為真正的1號(hào)進(jìn)程.
實(shí)現(xiàn)用戶進(jìn)程成為1號(hào)進(jìn)程
由于
command/init.go中的實(shí)現(xiàn)是用cmd.Run形式,所以會(huì)出現(xiàn)這樣的情況.
1. 修改
command/init.go中的Init方法代碼如下:
func Init(command string) {
defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
syscall.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), "")
/*
cmd := exec.Command(command)
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
log.Printf("Init Run() function err : %v\n", err)
log.Fatal(err)
}
*/
if err := syscall.Exec(command, []string{command}, os.Environ()); err != nil {
log.Printf("syscall.Exec err: %v\n", err)
log.Fatal(err)
}
}
執(zhí)行結(jié)果:
root@nicktming:~/go/src/github.com/nicktming/mydocker# go build .
root@nicktming:~/go/src/github.com/nicktming/mydocker# ./mydocker run -it /bin/sh
# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 21:55 pts/6 00:00:00 /bin/sh
root 4 1 0 21:55 pts/6 00:00:00 ps -ef
關(guān)于syscall.Exec
syscall.exec會(huì)執(zhí)行參數(shù)指定的命令,但是并不創(chuàng)建新的進(jìn)程,只在當(dāng)前進(jìn)程空間內(nèi)執(zhí)行,即替換當(dāng)前進(jìn)程的執(zhí)行內(nèi)容,會(huì)重用同一個(gè)進(jìn)程號(hào)PID.
使用exec.Command方式
func main() {
log.Printf("pid:%d\n", os.Getpid())
cmd := exec.Command("sh")
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
log.Printf("Init Run() function err : %v\n", err)
log.Fatal(err)
}
}
運(yùn)行:
root@nicktming:~/go/src/github.com/nicktming/mydocker/test/syscall# go run TestExec.go
2019/03/25 23:47:29 pid:20255
# echo $$
20258
使用
syscall.Exec
func main() {
log.Printf("pid:%d\n", os.Getpid())
// cmd := exec.Command("sh")
//
// cmd.Stdin = os.Stdin
// cmd.Stderr = os.Stderr
// cmd.Stdout = os.Stdout
//
// if err := cmd.Run(); err != nil {
// log.Printf("Init Run() function err : %v\n", err)
// log.Fatal(err)
// }
command := "/bin/sh"
if err := syscall.Exec(command, []string{command}, os.Environ()); err != nil {
log.Printf("syscall.Exec err: %v\n", err)
log.Fatal(err)
}
}
執(zhí)行命令:
root@nicktming:~/go/src/github.com/nicktming/mydocker/test/syscall# go run TestExec.go
2019/03/25 23:53:52 pid:20872
# echo $$
20872
總結(jié)
本節(jié)實(shí)現(xiàn)了容器的基本命令
run, 一個(gè)最基本的功能.
參考
1. 自己動(dòng)手寫(xiě)docker.(基本參考此書(shū),加入一些自己的理解,加深對(duì)
docker的理解)
全部?jī)?nèi)容
mydocker.png
1. [mydocker]---環(huán)境說(shuō)明
2. [mydocker]---urfave cli 理解
3. [mydocker]---Linux Namespace
4. [mydocker]---Linux Cgroup
5. [mydocker]---構(gòu)造容器01-實(shí)現(xiàn)run命令
6. [mydocker]---構(gòu)造容器02-實(shí)現(xiàn)資源限制01
7. [mydocker]---構(gòu)造容器02-實(shí)現(xiàn)資源限制02
8. [mydocker]---構(gòu)造容器03-實(shí)現(xiàn)增加管道
9. [mydocker]---通過(guò)例子理解存儲(chǔ)驅(qū)動(dòng)AUFS
10. [mydocker]---通過(guò)例子理解chroot 和 pivot_root
11. [mydocker]---一步步實(shí)現(xiàn)使用busybox創(chuàng)建容器
12. [mydocker]---一步步實(shí)現(xiàn)使用AUFS包裝busybox
13. [mydocker]---一步步實(shí)現(xiàn)volume操作
14. [mydocker]---實(shí)現(xiàn)保存鏡像
15. [mydocker]---實(shí)現(xiàn)容器的后臺(tái)運(yùn)行
16. [mydocker]---實(shí)現(xiàn)查看運(yùn)行中容器
17. [mydocker]---實(shí)現(xiàn)查看容器日志
18. [mydocker]---實(shí)現(xiàn)進(jìn)入容器Namespace
19. [mydocker]---實(shí)現(xiàn)停止容器
20. [mydocker]---實(shí)現(xiàn)刪除容器
21. [mydocker]---實(shí)現(xiàn)容器層隔離
22. [mydocker]---實(shí)現(xiàn)通過(guò)容器制作鏡像
23. [mydocker]---實(shí)現(xiàn)cp操作
24. [mydocker]---實(shí)現(xiàn)容器指定環(huán)境變量
25. [mydocker]---網(wǎng)際協(xié)議IP
26. [mydocker]---網(wǎng)絡(luò)虛擬設(shè)備veth bridge iptables
27. [mydocker]---docker的四種網(wǎng)絡(luò)模型與原理實(shí)現(xiàn)(1)
28. [mydocker]---docker的四種網(wǎng)絡(luò)模型與原理實(shí)現(xiàn)(2)
29. [mydocker]---容器地址分配
30. [mydocker]---網(wǎng)絡(luò)net/netlink api 使用解析
31. [mydocker]---網(wǎng)絡(luò)實(shí)現(xiàn)
32. [mydocker]---網(wǎng)絡(luò)實(shí)現(xiàn)測(cè)試
