七天用 Go 写个 docker(第一天)

1. docker详解

很多人刚接触docker的时候就会感觉非常神奇,感觉这个技术非常新颖,其实并不然,docker使用到的技术都是之前已经存在过的,只不过旧酒换了新瓶罢了。简单来说docker本质其实是一个特殊的进程,这个进程特殊在它被NamespaceCgroup 技术做了装饰,Namespace将该进程与Linux系统进行隔离开来,让该进程处于一个虚拟的沙盒中,而Cgroup则对该进程做了一系列的资源限制,两者配合模拟出来一个沙盒的环境。

2. Namespace

Linux对线程提供了六种隔离机制,分别为:uts pid user mount network ipc ,它们的作用如下:

  • uts: 用来隔离主机名
  • pid:用来隔离进程PID号的
  • user: 用来隔离用户的
  • mount:用来隔离各个进程看到的挂载点视图
  • network: 用来隔离网络
  • ipc:用来隔离System V IPC 和 POSIX message queues

3. 环境配置

因为我们是在Windows里面写代码,然后将代码编译好,放到Linux中执行,所以这里我们要更改下我们goland的环境,因为在不同的环境中,go导入的文件也是不同,如果我们的环境使用的Windows,那么使用 os/exec 包时,导入的将是 exec_windows.go,而如果我们的环境是Linux,那么将会导入exec_linux.go文件,因为只有Linux才会给创建进程时提供这个隔离参数,所以我们需要把环境改成Linux。

在这里插入图片描述

4. Go实现进程隔离

4.1 隔离uts

package main

import (
    "log"
    "os"
    "os/exec"
    "syscall"
)
func main() {
    cmd := exec.Command("sh")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS,
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    if err := cmd.Run(); err != nil {
        log.Fatal(err)
    }
}

我们编译一下,放到Linux中测试一下,windows中编译需要先把GOOS改成Linux,然后再执行go build,编译脚本如下

SET CGO_ENABLED=0
SET GOOS=linux
SET GOARCH=amd64
go build main.go


在这里插入图片描述

main 放到Linux环境中运行,这里我用的是 Centos

4.2 测试能否隔离主机名

  1. 给main文件添加可执行权限
    chmod +x main
  2. 查看当前主机名
  3. 执行 main 文件
    ./main
  4. 修改主机名
    hostname -b 新主机名

    再次查看主机名,我们看到已经将主机名修改为 test
  5. 退出 shell,再次查看主机名

    这时我们发现,外部的主机名,并没有被改变,说明该进程成功的将自己的hostname与外部的hostname进行隔离了。这也证明我们使用 uts namespace成功了。

4.2 其他的隔离

我们想对此进程进行那种隔离,只需要在Cloneflags中添加参数即可

package main

import (
    "log"
    "os"
    "os/exec"
    "syscall"
)

func main() {
    cmd := exec.Command("sh")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        // 隔离 uts,ipc,pid,mount,user,network
        Cloneflags: syscall.CLONE_NEWUTS |
            syscall.CLONE_NEWIPC |
            syscall.CLONE_NEWPID |
            syscall.CLONE_NEWNS |
            syscall.CLONE_NEWUSER |
            syscall.CLONE_NEWNET,
        // 设置容器的UID和GID
        UidMappings: []syscall.SysProcIDMap{
            {
                // 容器的UID
                ContainerID: 1,
                // 宿主机的UID
                HostID: 0,
                Size:   1,
            },
        },
        GidMappings: []syscall.SysProcIDMap{
            {
                // 容器的GID
                ContainerID: 1,
                // 宿主机的GID
                HostID: 0,
                Size:   1,
            },
        },
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    if err := cmd.Run(); err != nil {
        log.Fatal(err)
    }
}

文章会首发于我微信公众号上,扫码关注,及时获取最新内容

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 4年前 自动加精
讨论数量: 11
wangchunbo

原来docker 原理是 隔离主机啊!

4年前 评论

学到了,mark一下

3年前 评论

Docker的原理果然是隔离主机......

3年前 评论

这个在隔离之后产生的文件会被保存到什么地方呢?

3年前 评论

@codawu 并不会产生文件哦,他只是一个进程而已,docker分两层,只读层和读写层,我们在docker里面生成的文件就会放到读写层里面,第四天会讲到~

3年前 评论

学到了。好像懂了,但有疑问的东西更多了 :joy:继续学习去 :smile:

3年前 评论
不忘初心

持续关注 赞

3年前 评论

牛! :cow:

2年前 评论

大佬, "fork/exec /bin/bash: invalid argument" 请问这个情况怎么解决呢?

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!