前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >容器:namespace文件隔离实现

容器:namespace文件隔离实现

作者头像
超级大猪
发布2020-03-27 08:28:41
1.4K0
发布2020-03-27 08:28:41
举报
文章被收录于专栏:大猪的笔记

最近很开心,准备写一个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的功能

代码语言:javascript
复制
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里面封装好了,下面操作:

  • 下载一个需要的rootfs,比如alpine-minirootfs-3.9.3-x86_64.tar.gz。谷歌一下就好。
  • mkdir -p /tmp/ns-proc/rootfs
  • tar -C /tmp/ns-proc/rootfs/ -xf alpine-minirootfs-3.9.3-x86_64.tar.gz
  • 封装一下pivot的调用:
代码语言:javascript
复制
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
}
  • 在main里面进行调用,依照第一节clone的方法,可以把子程序改成用cmd启动sh
  • 可以参考下面的代码,nsRun就使用了系统的namespace在cmd启动的时候进行了隔离。
代码语言:javascript
复制
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)
    }
}
代码语言:javascript
复制
go build main
sudo unshare -m # 此时会进入到另一个以root为用户的账户下
./main

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-01-16 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • 文件隔离
    • 创建第一个程序,实现clone
      • 隔离文件系统
      相关产品与服务
      容器镜像服务
      容器镜像服务(Tencent Container Registry,TCR)为您提供安全独享、高性能的容器镜像托管分发服务。您可同时在全球多个地域创建独享实例,以实现容器镜像的就近拉取,降低拉取时间,节约带宽成本。TCR 提供细颗粒度的权限管理及访问控制,保障您的数据安全。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档