Docker进阶与实战
上QQ阅读APP看书,第一时间看更新

2.4.2 Namespace的接口和使用

对Namespace的操作,主要是通过clone、setns和unshare这3个系统调用来完成的。

clone可以用来创建新的Namespace。它接受一个叫flags的参数,这些flag包括CLONE_NEWNS、CLONE_NEWIPC、CLONE_NEWUTS、CLONE_NEWNET、CLONE_NEWPID和CLONE_NEWUSER,我们通过传入这些CLONE_NEW*来创建新的Namespace。这些flag对应的Namespace都可以从字面上看出来,除了CLONE_NEWNS,这是用来创建Mount Namespace的。指定了这些flag后,由clone创建出来的新进程,就位于全新的Namespace里了,并且很自然地这个新进程以后创建出来的进程,也都在这个Namespace中。

提示

Mount Namespace是第一个实现的Namespace,当初实现时并不是为了实现Linux容器,因此也就没有预料到会有新的Namespace出现,因此用了CLONE_NEWNS而不是CLONE_NEWMNT之类的名字。

那么,能不能为已有的进程创建新的Namespace呢?答案是可以,unshare就是用来达到这个目的的。调用这个系统调用的进程,会被放进新创建的Namespace里,要创建什么Namespace由flags参数指定,可以使用的flag也就是上面提到的那些。

以上两个系统调用都是用来创建新的Namespace的,而setns则可以将进程放到已有的Namespace里,问题是如何指定已有的Namespace?答案在procfs里。每个进程在procfs下都有一个目录,在那里面就有Namespace相关的信息,如下。

# ls l /proc/$$/ns
total 0
lrwxrwxrwx 1 root root 0 Jun 16 14:39 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Jun 16 14:39 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Jun 16 14:39 net -> net:[4026531957]
lrwxrwxrwx 1 root root 0 Jun 16 14:39 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Jun 16 14:39 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jun 16 14:39 uts -> uts:[4026531838]

这里每个虚拟文件都对应了这个进程所处的Namespace。因此,如果另一个进程要进入这个进程的Namespace,可以通过open系统调用打开这里面的虚拟文件并得到一个文件描述符,然后把文件描述符传给setns,调用返回成功的话,就进入这个进程的Namespace了。

提示

docker exec命令的实现原理就是setns。

以下是一个简单的程序,在Linux终端调用这个程序就会进入新的Namespace,同时也可以打开另一个终端,这个终端是在host的Namespace里,这样就可以对比两个Namespace的区别了。

#define _GNU_SOURCE
#include <sys/wait.h>
#include <sys/utsname.h>
#include <sched.h>
#include <stdio.h>

#define STACK_SIZE (1024 * 1024)
static char stack[STACK_SIZE];
static char* const child_args[] = { "/bin/bash", NULL };

static int child(void *arg)
{
        execv("/bin/bash", child_args);
        return 0;
}

int main(int argc, char *argv[])
{
        pid_t pid;

        pid = clone(child, stack+STACK_SIZE, SIGCHLD|CLONE_NEWUTS, NULL);

        waitpid(pid, NULL, 0);
}

这个程序创建了UTS Namespace,可以通过修改flag,创建其他Namespace,也可以创建几个Namespace的组合。这个程序将会用来为下面的内容做演示。