上文我们使用了Network这个namespace写了个demo演示了网络空间,把veth一端塞进容器里头,并解决了容器网络问题。目前为止,这个go写的简单容器已经演示了好几个Linux的命名空间的使用。今天的主题是使用UTS
namespace。
开始之前先来引出目前的问题。
引出问题
还是上次的代码,在编译之前在宿主机上查看主机名,在demo容器里面也查看下主机名:
ubuntu@VM-0-7-ubuntu:~/workspace/gons$ hostname
VM-0-7-ubuntu
ubuntu@VM-0-7-ubuntu:~/workspace/gons$ go build
ubuntu@VM-0-7-ubuntu:~/workspace/gons$ ./gons
arg0=./gons,
arg0=initFuncName,
>> namespace setup code goes here <<
newRoot:/tmp/ns-proc/rootfs
status: waiting network ...
run netsetter: stdout:
status: waiting network ...
-[namespace-process]-# hostname
VM-0-7-ubuntu
-[namespace-process]-# exit
ubuntu@VM-0-7-ubuntu:~/workspace/gons$
问题来了,宿主机和demo容器进程里看到的进程主机名是一🐱一样的,都是VM-0-7-ubuntu。
如果没记错,在之前介绍reexec的时候有提过这个问题。
容器内的主机名和宿主主机名一致会带来一些问题 比如:
- 使用者会混淆,不知道操作是在容器内还是宿主上
- 安全隐患,直接暴露了宿主机的身份。对应容器而言肯定知道的越少越好,隔离更高。
当时谈到的办法是在设置了命名空间之后,但是启动sh进程之前的初始化动作去去实现修改主机名,以避免和宿主机的hostname相同。
但是之后只是这么说了,并没有去实现。
撸起袖子
其实操作起来非常简单
先在config.go添加一个assignHostname()
的
import "github.com/Pallinder/go-randomdata"
...
func assignHostname() error{
name := randomdata.SillyName()
if err := syscall.Sethostname([]byte(name)); err != nil {
return fmt.Errorf("Error setting hostname - %s\n", err)
os.Exit(1)
}
return nil
}
这里添加了一个简单的函数,发现网上有个不错的随机名称库go-randomdata,直接引用来生成随机人民,并通过调用系统函数syscall.Sethostname
进行设置容器所属命名空间下的主机名。
当然,具体的hostname你可以根据自己需求定义,这里仅仅是为了演示所以使用的随机库。
同时在init函数以key:initFuncName注册到内存中的匿名函数添加调用assignHostname的操作。
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]
if err := mountProc(newRoot); err != nil {
fmt.Printf("Error mounting /proc - %s\n", err)
os.Exit(1)
}
fmt.Printf("newRoot:%s \n",newRoot)
if err := pivotRoot(newRoot); err != nil {
fmt.Printf("Error running pivot_root - %s\n", err)
os.Exit(1)
}
if err := assignHostname(); err !=nil{
fmt.Printf("Error setting hostname - %s\n", err)
}
if err := waitNetwork(); err != nil {
fmt.Printf("Error waiting for network - %s\n", err)
os.Exit(1)
}
nsRun() //calling clone() to create new process goes here
})
if reexec.Init() {
os.Exit(0)
}
}
最重要的是
main函数中调用clone设置命名空间的时候我们是有传递syscall.CLONE_NEWUTS
这个flag的。(其实一直都有传递这个flag,只是还没进行相应设置)。
通过设置UTS命名空间。我们新的容器进程可以设置和宿主不同的hostname,因此在代码里的assignHostname()
操作并不会影响到宿主机上,因为他们是两个不同的命名空间。
现在来看下程序运行情况:
ubuntu@VM-0-7-ubuntu:~/workspace/gons$ hostname
VM-0-7-ubuntu
ubuntu@VM-0-7-ubuntu:~/workspace/gons$ go build
ubuntu@VM-0-7-ubuntu:~/workspace/gons$ ./gons
arg0=./gons,
arg0=initFuncName,
>> namespace setup code goes here <<
newRoot:/tmp/ns-proc/rootfs
status: waiting network ...
run netsetter: stdout:
status: waiting network ...
-[namespace-process]-# hostname
Raccoonwax
-[namespace-process]-# exit
ubuntu@VM-0-7-ubuntu:~/workspace/gons$ hostname
VM-0-7-ubuntu
ubuntu@VM-0-7-ubuntu:~/workspace/gons$
可以看到,进入容器进程之前或者之后宿主上的主机名都是VM-0-7-ubuntu
,而进入到容器进程里面查看得到的主机名是Raccoonwax
。证明了UTS namespace很好地帮我们实现主机名称的隔离。
由于目前已经到了专题系列第七章,代码也越写越长了,完整的工程代码可以访问git仓库获取,鉴于国内网络,暂时先托管国内gitee仓库
接下来做什么
关于Linux namespac这里已经用了七章来探索。现在基本上已经覆盖了好几个常用的namespace并配备了demo和源码,尽管还有很多不完善,暂时先作为一个初稿,后期再慢慢维护和完善。目前还缺的主要是cgroup
方面的。cgroup是一个比较大的范围,可能又是比较长的篇幅,暂时先埋个坑,有时间再学习学习。