Linux Namespace
Linux Namespace是Linux提供的一种OS-level virtualization的方法。目前在Linux系统上实现OS-level virtualization的系统有Linux VServer、OpenVZ、LXC Linux Container、Virtuozzo等,其中Virtuozzo是OpenVZ的商业版本。以上种种本质来说都是使用了Linux Namespace来进行隔离。
那么究竟什么是Linux Namespace?Linux很早就实现了一个系统调用chroot,该系统调用能够为进程提供一个限制的文件系统。虽然文件系统的隔离要比单纯的chroot复杂的多,但是至少chroot提供了一种简单的隔离模式:chroot内部的文件系统无法访问外部的内容。Linux Namespace在此基础上,提供了对UTS、IPC、mount、PID、network的隔离机制,例如对不同的PID namespace中的进程无法看到彼此,而且每个PID namespace中的进程PID都是单独制定的。这一点对OS-level Virtualization非常有用,这是因为:对于不同的Linux运行环境中,都有一个init进程,其PID=0,由于不同的PID namespace中都可以指定自己的0号进程,所以可以通过该技术来进行PID环境的隔离。
OS-level Virtualization相比其他的虚拟化技术更加轻量级。
Linux在使用Namespace的时候,需要显式的在配置中指定将那些Namespace的支持编译到内核中。
进程的若干个ID的意义
PID:Process ID,进程ID,即进程的唯一标识
TGID:处于某个线程组中的所有进程都有统一的线程组ID(Thread Group IP,TGID)。线程可以用clone加CLONE_THREAD来创建。线程组中的主进程成为group leader,可以通过线程组中任何线程的的task_struct->group_leader成员获得。
独立进程可以合并成进程组(使用setpgrp系统调用)。进程组成员的task_struct->pgrp属性值都是相同的(PGID),即进程组组长的PID。用管道连接的进程在一个进程组中。
几个进程组可以合并成一个会话。会话中所有进程都有同样的SID(Session ID,会话ID),保存在task_struct->session中。SID可以通过setsid系统调用设置。
引入进程PID命名空间后的PID框架
随着内核不断的添加新的内核特性,尤其是PID Namespace机制的引入,这导致PID存在命名空间的概念,并且命名空间还有层级的概念存在,高级别的可以被低级别的看到,这就导致高级别的进程有多个PID,比如说在默认命名空间下,创建了一个新的命名空间,占且叫做level1,默认命名空间这里称之为level0,在level1中运行了一个进程在level1中这个进程的pid为1,因为高级别的pid namespace需要被低级别的pid namespace所看见,所以这个进程在level0中会有另外一个pid,为xxx.套用上面说到的pid位图的概念,可想而知,对于每一个pid namespace来说都应该有一个pidmap,上文中提到的level1进程有两个pid一个是1,另一个是xxx,其中pid为1是在level1中的pidmap进行分配的,pid为xxx则是在level0的pidmap中分配的. 下面这幅图是整个pidnamespace的一个框架
Linux Namespaces机制提供一种资源隔离方案。PID,IPC,Network等系统资源不再是全局性的,而是属于特定的Namespace。每个Namespace里面的资源对其他Namespace都是透明的。要创建新的Namespace,只需要在调用clone时指定相应的flag。Linux Namespaces机制为实现基于容器的虚拟化技术提供了很好的基础,LXC(Linux containers)就是利用这一特性实现了资源的隔离。不同container内的进程属于不同的Namespace,彼此透明,互不干扰。下面我们就从clone系统调用的flag出发,来介绍各个Namespace。
当调用clone时,设定了CLONE_NEWPID,就会创建一个新的PID Namespace,clone出来的新进程将成为Namespace里的第一个进程。一个PID Namespace为进程提供了一个独立的PID环境,PID Namespace内的PID将从1开始,在Namespace内调用fork,vfork或clone都将产生一个在该Namespace内独立的PID。新创建的Namespace里的第一个进程在该Namespace内的PID将为1,就像一个独立的系统里的init进程一样。该Namespace内的孤儿进程都将以该进程为父进程,当该进程被结束时,该Namespace内所有的进程都会被结束。PID Namespace是层次性,新创建的Namespace将会是创建该Namespace的进程属于的Namespace的子Namespace。子Namespace中的进程对于父Namespace是可见的,一个进程将拥有不止一个PID,而是在所在的Namespace以及所有直系祖先Namespace中都将有一个PID。系统启动时,内核将创建一个默认的PID Namespace,该Namespace是所有以后创建的Namespace的祖先,因此系统所有的进程在该Namespace都是可见的。
当调用clone时,设定了CLONE_NEWIPC,就会创建一个新的IPC Namespace,clone出来的进程将成为Namespace里的第一个进程。一个IPC Namespace有一组System V IPC objects 标识符构成,这标识符有IPC相关的系统调用创建。在一个IPC Namespace里面创建的IPC object对该Namespace内的所有进程可见,但是对其他Namespace不可见,这样就使得不同Namespace之间的进程不能直接通信,就像是在不同的系统里一样。当一个IPC Namespace被销毁,该Namespace内的所有IPC object会被内核自动销毁。
PID Namespace和IPC Namespace可以组合起来一起使用,只需在调用clone时,同时指定CLONE_NEWPID和CLONE_NEWIPC,这样新创建的Namespace既是一个独立的PID空间又是一个独立的IPC空间。不同Namespace的进程彼此不可见,也不能互相通信,这样就实现了进程间的隔离。
当调用clone时,设定了CLONE_NEWNS,就会创建一个新的mount Namespace。每个进程都存在于一个mount Namespace里面,mount Namespace为进程提供了一个文件层次视图。如果不设定这个flag,子进程和父进程将共享一个mount Namespace,其后子进程调用mount或umount将会影响到所有该Namespace内的进程。如果子进程在一个独立的mount Namespace里面,就可以调用mount或umount建立一份新的文件层次视图。该flag配合pivot_root系统调用,可以为进程创建一个独立的目录空间。
当调用clone时,设定了CLONE_NEWNET,就会创建一个新的Network Namespace。一个Network Namespace为进程提供了一个完全独立的网络协议栈的视图。包括网络设备接口,IPv4和IPv6协议栈,IP路由表,防火墙规则,sockets等等。一个Network Namespace提供了一份独立的网络环境,就跟一个独立的系统一样。一个物理设备只能存在于一个Network Namespace中,可以从一个Namespace移动另一个Namespace中。虚拟网络设备(virtual network device)提供了一种类似管道的抽象,可以在不同的Namespace之间建立隧道。利用虚拟化网络设备,可以建立到其他Namespace中的物理设备的桥接。当一个Network Namespace被销毁时,物理设备会被自动移回init Network Namespace,即系统最开始的Namespace。
当调用clone时,设定了CLONE_NEWUTS,就会创建一个新的UTS Namespace。一个UTS Namespace就是一组被uname返回的标识符。新的UTS Namespace中的标识符通过复制调用进程所属的Namespace的标识符来初始化。Clone出来的进程可以通过相关系统调用改变这些标识符,比如调用sethostname来改变该Namespace的hostname。这一改变对该Namespace内的所有进程可见。CLONE_NEWUTS和CLONE_NEWNET一起使用,可以虚拟出一个有独立主机名和网络空间的环境,就跟网络上一台独立的主机一样。
以上所有clone flag都可以一起使用,为进程提供了一个独立的运行环境。LXC正是通过在clone时设定这些flag,为进程创建一个有独立PID,IPC,FS,Network,UTS空间的container。一个container就是一个虚拟的运行环境,对container里的进程是透明的,它会以为自己是直接在一个系统上运行的。
一个container就像传统虚拟化技术里面的一台安装了OS的虚拟机,但是开销更小,部署更为便捷。