最近很开心,准备写一个docker系列的读书笔记,记录我在腾讯云逗比的青春。
参考资料: 自己动手写docker-4 https://juejin.im/post/5c2b495af265da6134388142 使用golang理解Linux namespace(四)-clone前的初始化 https://here2say.com/38/
代码大部分来源于上面两篇文章。
首先偷一张图:
容器的实现,依赖于Linux下的命名空间隔离,基础知识就不说啦。
第一篇准备从容器的文件隔离读起,因为文件系统的配置比较麻烦。
在go里实现clone,docker使用了一个比较巧妙的方法,在linux中,/proc/self/exe 文件代表自己,也就是说,在程序中cmd请求这个地址,等同于使用自己再启动一个实例,也就是clone。 docker的库官方实现了这个功能,go包的地址是:"github.com/docker/docker/pkg/reexec"
也就是说,可以使用下面的代码来实现clone的功能
package main
import (
"fmt"
"github.com/docker/docker/pkg/reexec"
"main/code/dockertest"
"os"
"os/exec"
"syscall"
)
func init() {
fmt.Printf("arg0=%s,\n", os.Args[0])
reexec.Register("initFuncName", func() {
fmt.Printf("\n>> namespace setup code goes here <<\n\n")
newRoot := os.Args[1]
fmt.Printf("newRoot:%s\n", newRoot)
////在这里写子程序的代码
})
if reexec.Init() {
os.Exit(0)
}
}
func main() {
var rootfsPath = "/tmp/ns-proc/rootfs"
cmd := reexec.Command("initFuncName", rootfsPath)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Printf("Error running the reexec.Command - %s\n", err)
os.Exit(1)
}
}
当调用clone函数,传入CLONE_NEWNS的时候,会惊奇的发现,容器仍然可以访问主机上的所有目录。这是因为新命名空间会把原调用者的mount list直接复制。 要解决这个问题,需要在使用命名空间隔离之前调用pivot_root系统调用将rootfs切换,go已经在syscall里面封装好了,下面操作:
package dockertest
import (
"fmt"
"os"
"path/filepath"
"syscall"
)
//implement pivot_root by syscall
func PivotRoot(newroot string) error {
preRoot := "/.pivot_root"
putold := filepath.Join(newroot, preRoot) //putold:/tmp/ns-proc/rootfs/.pivot_root
fmt.Printf("root:%v, old:%v\n", newroot, putold)
// pivot_root requirement that newroot and putold must not be on the same filesystem as the current root
//current root is / and new root is /tmp/ns-proc/rootfs and putold is /tmp/ns-proc/rootfs/.pivot_root
//thus we bind mount newroot to itself to make it different
//try to comment here you can see the error
if err := syscall.Mount(newroot, newroot, "", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
fmt.Println("mount error", err)
return err
}
// create putold directory, equal to mkdir -p xxx
if err := os.MkdirAll(putold, 0700); err != nil {
fmt.Println("mkdir error", err)
return err
}
// call pivot_root
fmt.Printf("pivot newroot:%v, putold:%v\n", newroot, putold)
if err := syscall.PivotRoot(newroot, putold); err != nil {
fmt.Println("PivotRoot error", err)
return err
}
// ensure current working directory is set to new root
if err := os.Chdir("/"); err != nil {
fmt.Println("chdir error", err)
return err
}
// umount putold, which now lives at /.pivot_root
putold = preRoot
if err := syscall.Unmount(putold, syscall.MNT_DETACH); err != nil {
return err
}
// remove putold
if err := os.RemoveAll(putold); err != nil {
return err
}
return nil
}
sh
。nsRun
就使用了系统的namespace在cmd启动的时候进行了隔离。package main
import (
"fmt"
"github.com/docker/docker/pkg/reexec"
"main/code/dockertest"
"os"
"os/exec"
"syscall"
)
func init() {
fmt.Printf("arg0=%s,\n", os.Args[0])
reexec.Register("initFuncName", func() {
fmt.Printf("\n>> namespace setup code goes here <<\n\n")
newRoot := os.Args[1]
fmt.Printf("newRoot:%s\n", newRoot)
if err := dockertest.PivotRoot(newRoot); err != nil {
fmt.Printf("Error running pivot_root - %s\n", err)
os.Exit(1)
}
nsRun() //calling clone() to create new process goes here
})
if reexec.Init() {
os.Exit(0)
}
}
func nsRun() {
cmd := exec.Command("sh")
cmd.Dir = "/"
//set identify for this demo
cmd.Env = []string{"PS1=-[namespace-process]-# "}
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWNS,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Printf("Error running the /bin/sh command - %s\n", err)
os.Exit(1)
}
}
func main() {
var rootfsPath = "/tmp/ns-proc/rootfs"
cmd := reexec.Command("initFuncName", rootfsPath)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Printf("Error running the reexec.Command - %s\n", err)
os.Exit(1)
}
}
./main
,但是,此时开始报错:pivot_root invalid argument
You may have some mounts with MS_SHARED
。具体参考:https://bugzilla.redhat.com/show_bug.cgi?id=1361043go build main
sudo unshare -m # 此时会进入到另一个以root为用户的账户下
./main
大功告成,可以cd /
,看看是不是文件系统已经换了。示意图: