简介
Linux Namespace
提供了一种内核级别隔离系统资源的方法,通过将系统的全局资源放在不同的Namespace
中,来实现资源隔离的目的。不同Namespace
的程序,可以享有一份独立的系统资源。目前Linux中提供了六类系统资源的隔离机制,分别是:
Mount
: 隔离文件系统挂载点UTS
: 隔离主机名和域名信息IPC
: 隔离进程间通信PID
: 隔离进程的IDNetwork
: 隔离网络资源User
: 隔离用户和用户组的ID
下面简单的介绍一下这些Namespace
的使用和功能。
使用
涉及到Namespace
的操作接口包括clone()
、setns()
、unshare()
以及还有/proc
下的部分文件。为了使用特定的Namespace
,在使用这些接口的时候需要指定以下一个或多个参数:
CLONE_NEWNS
: 用于指定Mount Namespace
CLONE_NEWUTS
: 用于指定UTS Namespace
CLONE_NEWIPC
: 用于指定IPC Namespace
CLONE_NEWPID
: 用于指定PID Namespace
CLONE_NEWNET
: 用于指定Network Namespace
CLONE_NEWUSER
: 用于指定User Namespace
下面简单概述一下这几个接口的用法。
clone系统调用
可以通过clone
系统调用来创建一个独立Namespace
的进程,它的函数描述如下:
int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);
它通过flags
参数来控制创建进程时的特性,比如新创建的进程是否与父进程共享虚拟内存等。比如可以传入CLONE_NEWNS
标志使得新创建的进程拥有独立的Mount Namespace
,也可以传入多个flags使得新创建的进程拥有多种特性,比如:
flags = CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC;
传入这个flags那么新创建的进程将同时拥有独立的Mount Namespace
、UTS Namespace
和IPC Namespace
。
通过/proc文件查看已存在的Namespace
在3.8内核开始,用户可以在/proc/$pid/ns
文件下看到本进程所属的Namespace
的文件信息。例如PID为2704进程的情况如下图所示:
其中4026531839是Namespace
的ID,如果两个进程的Namespace
ID相同表明进程同处于一个命名空间中。
这里需要注意的是:只/proc/$pid/ns/
对应的Namespace
文件被打开,并且该文件描述符存在,即使该PID所属的进程被销毁,这个Namespace
会依然存在。可以通过挂载的方式打开文件描述符:
touch ~/mnt mount --bind /proc/2704/mnt ~/mnt
这样就可以保留住PID为2704的进程的Mount Namespace
了,即使2704进程被销毁或者退出,ID为4026531840的Mount Namespace
依然会存在。
setns加入已存在的Namepspace
setns()
函数可以把进程加入到指定的Namespace
中,它的函数描述如下:
int setns(int fd, int nstype);
它的参数描述如下:
fd
参数:表示文件描述符,前面提到可以通过打开/proc/$pid/ns/
的方式将指定的Namespace
保留下来,也就是说可以通过文件描述符的方式来索引到某个Namespace
。nstype
参数:用来检查fd
关联Namespace
是否与nstype
表明的Namespace
一致,如果填0的话表示不进行该项检查。
通过在程序中调用setns
来将进程加入到指定的Namespace
中。
unshare脱离到新的Namespace
unshare()
系统调用用于将当前进程和所在的Namespace
分离,并加入到一个新的Namespace
中,相对于setns()
系统调用来说,unshare()
不用关联之前存在的Namespace
,只需要指定需要分离的Namespace
就行,该调用会自动创建一个新的Namespace
。
unshare()
的函数描述如下:
int unshare(int flags);
其中flags
用于指明要分离的资源类别,它支持的flags
与clone
系统调用支持的flags
类似,这里简要的叙述一下几种标志:
CLONE_FILES
: 子进程一般会共享父进程的文件描述符,如果子进程不想共享父进程的文件描述符了,可以通过这个flag来取消共享。CLONE_FS
: 使当前进程不再与其他进程共享文件系统信息。CLONE_SYSVSEM
: 取消与其他进程共享SYS V信号量。CLONE_NEWIPC
: 创建新的IPC Namespace
,并将该进程加入进来。
注意事项
这里需要注意的是:unshare()
和setns()
系统调用对PID Namespace
的处理不太相同,当unshare PID namespace时,调用进程会为它的子进程分配一个新的PID Namespace
,但是调用进程本身不会被移到新的Namespace
中。而且调用进程第一个创建的子进程在新Namespace
中的PID为1,并成为新Namespace
中的init进程。
setns()
系统调用也是类似的,调用者进程并不会进入新的PID Namespace,而是随后创建的子进程会进入。
为什么创建其他的Namespace时unshare()
和setns()
会直接进入新的Namespace,而唯独PID Namespace不是如此呢?
因为调用getpid()
函数得到的PID是根据调用者所在的PID Namespace而决定返回哪个PID,进入新的PID namespace会导致PID产生变化。而对用户态的程序和库函数来说,他们都认为进程的PID是一个常量,PID的变化会引起这些进程奔溃。
换句话说,一旦程序进程创建以后,那么它的PID namespace的关系就确定下来了,进程不会变更他们对应的PID namespace。
小结
通过上面简单的概述,对于Namespace
的操作有以下方式:
- 1、可以在进程刚创建的时候通过
clone
系统调用为新进程分配一个或多个新的Namespace
。 - 2、通过
setns()
将进程加入到已有的Namespace
中。 - 3、通过
unshare()
为已存在的进程创建一个或多个新的Namespace
。
接下来详细的介绍一下各个Namespace
的功能和特性。
Mount Namespace
Mount Namespace
用来隔离文件系统的挂载点,不同Mount Namespace
的进程拥有不同的挂载点,同时也拥有了不同的文件系统视图。Mount Namespace
是历史上第一个支持的Namespace
,它通过CLONE_NEWNS
来标识的。
挂载的概念
挂载的过程是通过mount
系统调用完成的,它有两个参数:一个是已存在的普通文件名,一个是可以直接访问的特殊文件,一个是特殊文件的名字。这个特殊文件一般用来关联一些存储卷,这个存储卷可以包含自己的目录层级和文件系统结构。
mount
所达到的效果是:像访问一个普通的文件一样访问位于其他设备上文件系统的根目录,也就是将该设备上目录的根节点挂到了另外一个文件系统的页节点上,达到给这个文件系统扩充容量的目的。
可以通过/proc
文件系统查看一个进程的挂载信息,具体做法如下:
cat /proc/$pid/mountinfo
其输出结果如下:
其中输出的格式如下:
挂载传播
进程在创建Mount Namespace
时,会把当前的文件结构复制给新的Namespace
,新的Namespace
中的所有mount
操作仅影响自身的文件系统。但随着引入挂载传播的特性,Mount Namespace
变得并不是完全意义上的资源隔离,这种传播特性使得多Mount Namespace
之间的挂载事件可以相互影响。
挂载传播定义了挂载对象之间的关系,系统利用这些关系来决定挂载对象中的挂载事件对其他挂载对象的影响。其中挂载对象之间的关系描述如下:
- 共享关系(MS_SHARED):一个挂载对象的挂载事件会跨
Namespace
共享到其他挂载对象。 - 从属关系(MS_SLAVE): 传播的方向是单向的,即只能从Master传播到Slave方向。
- 私有关系(MS_PRIVATE): 不同
Namespace
的挂载事件是互不影响的(默认选项)。 - 不可绑定关系(MS_UNBINDABLE): 一个不可绑定的私有挂载,与私有挂载类似,但是不能执行挂载操作。
其中给挂载点设置挂载关系的例子如下:
mount --make-shared /mntS # 将挂载点设置为共享关系属性 mount --make-private /mntP # 将挂载点设置为私有关系属性 mount --make-slave /mntY # 将挂载点设置为从属关系属性 mount --make-unbindable /mntU # 将挂载点设置为不可绑定属性
注意在设置私有关系属性时,在本命名空间下的这个挂载点是Slave,而父命名空间下这个挂载点是Master,挂载传播的方向只能由Master传给Slave。
绑定挂载
绑定挂载的引入使得mount
的其中一个参数不一定要是一个特殊文件,也可以是该文件系统上的一个普通文件目录。Linux中绑定挂载的用法如下:
mount --bind /home/work /home/alpha mount -o bind /home/work /home/alpha
其中/home/work
是磁盘上的存在的一个目录,而不是一个文件设备(比如磁盘分区)。如果需要将Linux中两个文件目录链接起来,可以通过绑定挂载的方式,挂载后的效果类似于在两个文件目录上建立了硬链接。
在绑定挂载中同时会涉及到挂载的传播特性,挂载传播的特性参考:Linux绑定挂载
UTS Namespace
UTS Namespace
提供了主机名和域名的隔离,也就是struct utsname
里的nodename
和domainname
两个字段。不同Namespace
中可以拥有独立的主机名和域名。
那么为什么需要对主机名和域名进行隔离呢?因为主机名和域名可以用来代替IP地址,如果没有这一层隔离,同一主机上不同的容器的网络访问就可能出问题。
IPC Namespace
IPC Namespace
是对进程间通信的隔离,进程间通信常见的方法有信号量、消息队列和共享内存。IPC Namespace
主要针对的是SystemV IPC和Posix消息队列,这些IPC机制都会用到标识符,比如用标识符来区分不同的消息队列,IPC Namespace
要达到的目标是相同的标识符在不同的Namepspace
中代表不同的通信介质(比如信号量、消息队列和共享内存)。