概述
用过Docker的开发者都知道,Docker容器在本质上是宿主机上的一个进程。也就是常说的容器是操作系统级的虚拟化。容器与容器之间做了资源的隔离,所以在一个容器内部的各种操作会给人一种仿佛在独立的系统环境中的感觉。外部应用对容器进行访问时,也会有这种感觉。而做这种容器资源隔离的Linux内核机制就是namespace。
感受一下namespace的存在
在具体了解namespace之前,我们先感受一下namespace的存在。
我们可以使用命令sudo ls -l /proc/[pid]/ns
查看pid为[pid]
的进程所属的namespace。比如我查看pid为1的进程。
可以看到namespace共分为7种类型。分别为ipc、mnt、pid、uts、net、cgroups、user。
如果某个软链接如ipc指向了同一个ipc namespace,那么这两个进程则是在同一个ipc namespace下的。如
我们可以看到pid为2的进程与pid为1的进程同属一个ipc namespace。因为它们的指向相同。
以此类推,这两个进程的mnt、net、pid、user、cgroups、uts namespace也都相同。
如若两个进程某个软链接指向不同,即说明这两个进程该资源已经被隔离了。
操作namespace的API
既然我们知道了实现容器资源隔离的Linux内核机制是namespace,那么,我们就想了解一下Linux提供的namespace操作API。
包括有clone(),setns(),unshare(),接下来分别做简单介绍:
clone()
clone()系统调用大家应该都比较熟悉,它的功能是创建一个新的进程。有别于系统调用fork(),clone()创建新进程时有许多的选项,通过选择不同的选项可以创建出合适的进程。我们也可以使用clone()来创建一个属于新的namespace的进程。这是Docker使用namespace的最基本的方法。
我们可以用man命令查看clone()的调用方式。
fn:传入子进程运行的程序主函数
child_stack:传入子进程使用的栈空间
flags:使用哪些标志位,与namespace相关的标志位主要包括CLONE_NEWIPC、CLONE_NEWPID、CLONE_NEWNS、CLONE_NEWNET、CLONE_USER、CLONE_UTS。具体含义后面会详述。
arg:传入的用户参数
setns()
这个系统调用顾名思义就是设置namespace。详细说来,就是将进程加入到一个已经存在的namespace中。对应于Docker的操作就是在一个Docker容器中用exec运行一个新命令。因为一个Docker容器其实就是一个已经存在的namespace,而用Docker exec执行一个命令,就是将该命令在该容器的namespace中运行,也就是将该命令的进程加入到一个已经存在的namespace中。
依然用man命令看一下这个系统调用的使用。
fd:表示要加入的namespace的文件描述符。它是一个指向/proc/[pid]/ns目录的文件描述符,可