本文环境:
- OS:Ubuntu 18.04.4 LTS
- Golang版本:1.12.13
Golang
Go语言是Google开发的一种静态类型、编译型的高级语言,它设计的蛮简单的,学过C的话,其实上手Go很快的,当然相比于C的话,Go有垃圾回收和并发支持,所以写起来心智负担更低一点。
对于Go的安装和配置,我以前写过一篇文章——go语言基本配置,我这里就不在赘述了。Go1.11增加了go modules
,使用它的话,就没必要一定要把代码放到GOPATH
下面啦(≧▽≦)/。 go modules
详细
使用请参考go mod 使用。
Go调用Namespace
其实对于Namespace这种系统调用,使用C语言描述是最好的(上一篇文章就是用C写的示例),但是C比较难,而且Docker也是用Go是实现的,所以我后面的文章都会用Go来写示例代码。
这里我先写了一个UTS Namespace
的例子,UTS Namespace
主要用来隔离nodename
和domainname
这两个系统标识:
1 | package main |
exec.Command("sh")
是指定了fork出来的新进程内的初始命令,cmd.SysProcAttr
这行就是设置了系统调用函数,Go帮我们封装了clone()函数,syscall.CLONE_NEWUTS
这个标识符标明创建一个UTS Namespace
。go build .
编译代码后,执行程序时我们会遇到错误fork/exec /bin/sh: operation not permitted,这是因为clone()
函数需要CAP_SYS_ADMIN
权限(这个问题我在v站上问过),解决方法是添加设置 uid
映射:
1 | package main |
我增加了CLONE_NEWUSER
标识,让新进程在User Namespace
中变成root用户。
1 | $ ./uts-easy |
启动另一个shell,查看宿主机上hostname
:
1 | $ hostname |
可以看到,外部的hostname
并没有被内部的修改所影响,这里我们大致感受了下UTS Namespace
的作用。
增加IPC Namespace
IPC Namespace
用来隔离System V IPC和POSIX message queues。每一个IPC Namespace
都有自己的System V IPC和POSIX message queues。我们稍微改动一下上面的代码。
1 | package main |
新开一个shell,在宿主机上创建一个message queue:
1 | $ ipcs -q |
运行我们自己的程序:
1 | $ ./uts-easy |
可以看到,在新的Namespace中,看不到宿主机上创建的message queue
,说明IPC是隔离的。
增加PID Namespace
PID Namespace是用来隔离进程ID的。我们自己进入Docker 容器的时候,就会发现里面的前台进程的PID为1,但是在容器外PID却不是1,这就是通过PID Namespace做到的。修改上述的代码,增加CLONE_NEWPID
:
1 | package main |
运行我们的程序:
1 | ./uts-easy |
可以看到在新的PID Namespace
中进程ID为1。
1 | $ pstree -pl | grep easy |
而我们在宿主机上可以看到它实际的PID(uts-easy
这个进程)为10768。
如果细心点,我们会发现,在我们的程序中使用ps
,top
这些命令出来的结果跟宿主机上是一样的,这是因为这些命令其实是去使用/proc这个文件夹的内容,这个就需要下面的Mount Namespace
了。
增加Mount Namespace
Mount Namespace用来隔离各个进程看到的挂载点视图。在不同Namespace的进程中,看到的文件系统层次是不一样的。在Mount Namespace中调用mount()
和unmount()
只会影响当前Namespace内的文件系统,而对全局的文件系统是没有影响的。
看到这里,也许会想到chroot()
。它也能将某一个子目录变为根节点。但是,Mount Namespace不仅能实现这个功能,而且能以更加灵活和安全的方式实现。
现在继续修改上述代码,增加CLONE_NEWNS
(Mount Namespace是Linux实现的第一个Namespace类型,因为,它的系统调用参数是NEWNS,NS是New Namespace的缩写。当时人们没有意识到,以后还会有很多类型的Namespace加入Linux大家庭)。
1 | package main |
运行程序,查看/proc的文件内容。
1 | -[namespace-process]-# ls /proc |
这里输出的结果很多,因为/proc还是宿主机的,下面将/proc mount到我们自己的Namespace下面来:
1 | -[namespace-process]-# mount -t proc proc /proc |
结果一下子少了很多,这里我们就可以用ps来查看系统的进程了。
1 | -[namespace-process]-# ps -ef |
可以看到,当前的Namespace中,sh进程是PID为1的进程。
增加Network Namespace
Network Namespace是用来隔离网络设备、IP地址端口等网络栈的Namespace。Network Namespace可以让每个容器拥有自己独立(虚拟的)网络设备,而且容器内的应用可以绑定到自己的端口,每个Namespace内的端口都不会互相冲突。在宿主机上搭建网桥后,就能很方便地实现容器之间通讯,而且不同容器上的应用可以使用相同的端口。
继续上述代码,加入CLONE_NEWNET
:
1 | package main |
运行我们的程序,查看网络设备,发现为空
1 | -[namespace-process]-# ifconfig |
在宿主机上查看网络设备,发现有lo, enp7s0这些网络设备。
1 | enp7s0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500 |
从上面的结果我们可以看出Network是隔离了。