操作系统虚拟化与Linux命名空间

2015-11-22 • 技术文章评论

虚拟化的本质需求来源于任务的隔离,也就是资源的隔离。所以,很多时候我们不是要在OS上虚拟出一个独立的OS,而是希望让虚拟机共享操作系统内核而又保持隔离性。

chroot

诞生于1979年的chroot是资源隔离的一种方案,chroot使一个进程把某个特定目录作为根目录,所有文件系统操作都被限定在该目录里进行。

但是chroot仅仅做到了文件系统层面的隔离是远远不够的,我们希望对进程、网络等各种资源进行更深层次的隔离而且希望能够实现对这些资源的审计。

Linux命名空间

Linux的命名空间机制为实现基于容器的虚拟化技术提供了很好的基础。

命名空间建立了系统的不同视图,只使用一个内核在一台物理机上运作,将全局资源都通过命名空间抽象起来,每个命名空间中的资源对其它命名空间是透明的。LXC就是利用这一特性实现了资源的隔离,由于没有多余的一层操作系统内核,容器比虚拟机更加轻量,启动更快,内存开销、调度开销也更小,更重要的是访问磁盘等IO设备不需要经过虚拟化层,没有性能损失。

当我们用fork或clone系统调用创建新进程时,有特定的标志位可以控制是与父进程共享命名空间,还是建立新的命名空间。命名空间的实现需要两个部分:每个子系统的命名空间结构,将此前所有的全局组件包装到命名空间中;将给定进程关联到所属各个命名空间的机制。子系统此前的全局属性现在封装到命名空间中,每个进程关联到一个选定的命名空间。每个可以感知命名空间的内核子系统都必须提供一个数据结构,将所有通过命名空间形式提供的对象集中起来。nsproxy结构体用于汇集指向特定于子系统的命名空间包装器的指针。

1
2
3
4
5
6
7
8
9
10
struct nsproxy {
    // 注释仅为匹配该行对应命名空间的标志位注解,不是本处引用代码的说明
    atomic_t count; 
    struct uts_namespace *uts_ns;   // 标志位CLONE_NEWUTS
    struct ipc_namespace *ipc_ns;   // 标志位CLONE_NEWIPC
    struct mnt_namespace *mnt_ns;   // 标志位CLONE_NEWNS
    struct pid_namespace *pid_ns;   // 标志位CLONE_NEWPID
    struct user_namespace *user_ns;
    struct net *net_ns; 
};

uts_namespace包含了运行内核的名称、版本、底层体系结构类型等信息(UTS即UNIX Timesharing System);ipc_namespace包含所有与进程间通信(IPC)有关的信息;mnt_namespace包含了文件系统的视图信息;pid_namespace报行了进程ID相关的信息;user_namespace包含了用于限制每个用户资源使用的信息;net_ns包含所有网络相关的命名空间参数;count是引用计数。

clone(child_exec, child_stack + STACKSIZE, clone_flags, &args)其中的clone_flags即为设置不同命名空间的标志位的或运算结果。如同时设定CLONE_NEWPID和CLONE_NEWIPC,那么不同命名空间的进程彼此不可见,也不能互相通信,这样就实现了进程间的隔离;CLONE_NEWUTS和CLONE_NEWNET一起使用,可以虚拟出一个有独立主机名和网络空间的环境,就跟网络上一台独立的主机一样。

以上所有标志位都可以一起使用,为进程提供了一个独立的运行环境。LXC就是通过clone时设定这些flag,为进程创建一个有独立PID、IPC、FS、Network、UTS空间的容器。一个容器就是一个虚拟的运行环境,对容器内的进程是透明的,它会以为自己是直接在一个系统上运行的。

几种其它虚拟化方案

OpenVZ也是Linux内核里实现操作系统级虚拟化的解决方案之一,但相对LXC,可以分辨子系统中内存和磁盘的配额分配情况,可进行独立审计,且支持检查点、热迁移等更多特性,缺点是基于非常老的Linux内核设计,对新特性支持差。

Docker如今大红大紫,它将基础运行环境和依赖都打包在镜像中,且镜像是层次化的,基于增量修改(基于AUFS实现,基础目录A和增量目录B合并为目录C,冲突时以B为准,修改C写回B),低开销实现了版本控制和分发,软件运行在独立容器中避免冲突。Docker很好地解决了依赖与资源冲突的问题,大大降低了运维成本。