上文我们使用了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的时候有提过这个问题。

容器内的主机名和宿主主机名一致会带来一些问题 比如:

  1. 使用者会混淆,不知道操作是在容器内还是宿主上
  2. 安全隐患,直接暴露了宿主机的身份。对应容器而言肯定知道的越少越好,隔离更高。

当时谈到的办法是在设置了命名空间之后,但是启动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是一个比较大的范围,可能又是比较长的篇幅,暂时先埋个坑,有时间再学习学习。