您的位置 首页 > 腾讯云社区

容器:namespace文件隔离实现---超级大猪

最近很开心,准备写一个docker系列的读书笔记,记录我在腾讯云逗比的青春。

参考资料: 自己动手写docker-4 https://juejin.im/post/5c2b495af265da6134388142 使用golang理解Linux namespace(四)-clone前的初始化 https://here2say.com/38/

代码大部分来源于上面两篇文章。

引言

首先偷一张图:

容器的实现,依赖于Linux下的命名空间隔离,基础知识就不说啦。

文件隔离

第一篇准备从容器的文件隔离读起,因为文件系统的配置比较麻烦。

创建第一个程序,实现clone

在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 <<nn") newRoot := os.Args[1] fmt.Printf("newRoot:%sn", 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 - %sn", err) os.Exit(1) } }隔离文件系统

当调用clone函数,传入CLONE_NEWNS的时候,会惊奇的发现,容器仍然可以访问主机上的所有目录。这是因为新命名空间会把原调用者的mount list直接复制。 要解决这个问题,需要在使用命名空间隔离之前调用pivot_root系统调用将rootfs切换,go已经在syscall里面封装好了,下面操作:

下载一个需要的rootfs,比如alpine-minirootfs-3.9.3-x86_64.tar.gz。谷歌一下就好。mkdir -p /tmp/ns-proc/rootfstar -C /tmp/ns-proc/rootfs/ -xf alpine-minirootfs-3.9.3-x86_64.tar.gz封装一下pivot的调用: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:%vn", 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:%vn", 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 }在main里面进行调用,依照第一节clone的方法,可以把子程序改成用cmd启动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 <<nn") newRoot := os.Args[1] fmt.Printf("newRoot:%sn", newRoot) if err := dockertest.PivotRoot(newRoot); err != nil { fmt.Printf("Error running pivot_root - %sn", 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 - %sn", 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 - %sn", 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=1361043按上面的解决,go build main sudo unshare -m # 此时会进入到另一个以root为用户的账户下 ./main

大功告成,可以cd /,看看是不是文件系统已经换了。示意图:

---来自腾讯云社区的---超级大猪

关于作者: 瞎采新闻

这里可以显示个人介绍!这里可以显示个人介绍!

热门文章

留言与评论(共有 0 条评论)
   
验证码: