资源隔离的两种主要方案
在服务器或者大型计算机集群中,往往需要运行大量作业和应用,为保证这些作业和应用的进程之间互不干扰,人们尝试使用了资源隔离技术来为各个应用进程划定固定的资源空间,使得进程运行范围被限制在有限空间内,由此就保证了进程的运行不受其他进程的干扰。这其中被广泛使用的技术包括虚拟机技术和容器技术,这两种技术都属于虚拟化的范畴,但是实现的机理和运行效率却相差悬殊,下面我们将对这两种技术作以简单介绍,并将着重介绍容器技术涉及到的资源隔离。
虚拟机VS容器
虚拟化技术分为主机级虚拟化和容器级虚拟化,分别对应虚拟机和容器,而主机级虚拟化又分为两种,具体分类情况大致列举如下:
-
主机级虚拟化
- Type1:直接在硬件平台(host machine)上装一个虚拟机管理器(virtual machine monitor,缩写为 VMM,eg. Hypervisor),在此之上安装使用虚拟机(guest machine)。
- Type2:硬件平台先安装主机操作系统(host os),在此之上安装VMM(虚拟机管理器),在这个软件之上创建使用虚拟机。eg. VM,workstation,virtual Box
-
容器级虚拟化
- 容器级虚拟化即在内核中的一个逻辑级别能够设置相互隔离的区域,彼此之间互不干扰,由此就可以仅在用户空间实现一组隔离的组件。在这个用户空间实现的组件就称为“容器”,每个容器都容纳了一堆的进程和用户账号文件等等。
上图展示了主机级虚拟化(以Type2为例)和容器级虚拟化的层次结构图,二者的区别就在于容器技术抽离了虚拟机中的客户操作系统内核,为何如此,我们以下就来作以简单分析。
传统的虚拟化方式的的确确可以在一组硬件平台之上实现跨系统环境的隔离,调式等等,也可以实现资源的充分利用,但是其带来的资源开销也不容忽视。我们从上图可以看出,传统主机级虚拟化,存在两层调度和资源分配。在第一级内核层,要实现内存虚拟化、CPU调度、I/O管理等等;而在第二级内核层,即在宿主机内核管理的抽象层,还需再次调度。这套机制的运行将严重浪费时间,造成额外开销。 很多时候,创建虚拟机的目的是运行有限的几个生产进程,而为此付出较大的代价,显得得不偿失。因此减少中间层,减少开销,提升效率尤为重要。
由此,人们想到抽离虚拟机中进程之下的内核层,其实质也就变成了容器技术。容器技术的目的在于:在系统内核之上,提供隔离的用户空间,使得用户运行进程不受其他进程干扰,进程运行所能看到的边界就是自己所在用户空间的边界。运行虚拟机的目的不只是为了运行进程,还有隔离等等。所以,虽然抽离了内核层,但是隔离效果依然要存在,那么接下来我们就来了解一下容器技术中的隔离是如何实现的。
容器技术的资源隔离
这里,我们首先来介绍Linux中的Namespace机制,Namespace(命名空间)是Linux提供的一种内核级别环境隔离的方法。读者应该了解Linux系统中的chroot命令,chroot,即change root directory,通过修改分目录把用户jail到一个特定目录下。chroot提供了一种简单的隔离模式:chroot内部的文件系统无法访问外部的内容。而Linux Namespace在此基础之上,又提供了对UTS、mount、IPC、PID、User和Net等的隔离机制。
类型 | 功能说明 |
---|---|
UTS Namespace | 提供对主机名的隔离能力 |
Mount Namespace | 提供对磁盘挂载点和文件系统的隔离能力 |
IPC Namespace | 提供对进程间通信的隔离能力 |
PID Namespace | 提供对进程的隔离能力 |
User Namespace | 提供对用户的隔离能力 |
Network Namespace | 提供对网络的隔离能力 |
-
UTS Namespace
- UTS,即UNIX Time-sharing System namespace,提供了主机名和域名的隔离,使得子进程有独立的主机名和域名(hostname)。不难理解,运行在同一主机上的多个容器,从用户角度来看,着多个容器就是多台可以独立使用的主机,那么从系统角度来看,要对这多个让其进行区分隔离,那他们就必须拥有自己独一无二的名字,因此Linux Namespace提供了对主机名、域名的隔离。默认情况下,容器的hostname就是它的短ID,用户也可以在容器启动时设定一个名字,也通过-h或–hostname参数设置。
-
Mount Namespace
- Mount Namespace为进程提供独立的文件系统试图,让容器看上去拥有整个文件系统,即拥有/、/bin、/sbin、/etc等目录。简单来讲,mount namespace就是隔离文件系统的挂载点,这样进程就只能看到自己的mount namespace中的文件系统挂载点。因此,在容器中对文件系统进行的相关操作,并不能影响到host和其他容器的文件系统。
-
IPC Namespace
- IPC,即Inter-Process Communication,是Unix/Linux中进程间通信的一种方式,IPC有共享内存、信号量、消息队列等方法。每个用户空间都有自己的IPC,所以,为了保证同一用户空间进程之间可以通信而跨用户空间的进程之间无法通信,也需要对IPC进行隔离。IPC需要有一个全局的ID,既然是全局的,那么就意味着Namespace需要对这个ID进行隔离,不能让别的Namespace的进程看到。
-
PID Namespace
- PID,即Process ID。在所有用户空间中,每个进程都应从属于一个进程,因为每一个进程都由其父进程创建,否则,该进程则为init进程。一个系统运行要有两棵树的存在,即进程树和文件系统树,既然对于当前用户空间,若让其以为自身为当前系统中唯一一个用户空间,那么其中的进程就必须明确自己从属于某一init进程或者自己就是init进程。但一个实际的系统中,只存在一个init进程,于是为每一个用户空间创建的所谓的“init”进程,对于该用户空间来说是一个特殊的进程,但对于系统来说,并非init进程。当该特殊进程结束时,那么该用户空间也将消失。照此理解,namespace也对PID进行了隔离。
-
User Namespace
- 所有的用户进程,都应隶属于某一真实用户,不同应用空间可能存在用户ID相同,而用户名不同的情况。一个系统只应存在一个root用户,而每一个用户空间也需要一个“root”用户,这也就意味着对于用户空间而言的“root”用户对于系统而言只是一个普通用户,而对于所处用户空间而言,可以将该用户ID伪装为ID为0的用户,该用户只能在自身用户空间之内为所欲为。照此理解,namespace亦实现了对用户的隔离。
-
Network Namespace
- 对于Network的隔离,即让容器拥有自己独立的网卡、IP、路由等资源,因而,两个容器既可以通过网络通信。
在主机内核级别,所有的资源都独立只有一组,原本只支持单个用户空间的运行,后来,因为有这种运行jail/vserver的需要,就在内核级别将这些资源进行了虚拟化,即将资源切分为多个互相隔离的环境(namespace),在同一内核创建多个名称空间,每个名称空间有自己独立的主机名。
所有容器机制的实现,Linux内核已经在内核级通过一个namespace的机制对以上6中需要隔离的资源原生支持,可以直接通过系统调用向外输出。容器级虚拟化技术,可以在总体用户空间上实现资源按比例分配,也可以在单一用户空间上实现固定数量的资源(eg. num. of cores )绑定,而这一操作是借助cgroups(Control Groups,控制组)实现的。控制组就是一组按照某种标准划分的进程。Cgroups中的资源控制都是以控制组为单位实现。一个进程可以加入到某个控制组,也从一个进程组迁移到另一个控制组。一个进程组的进程可以使用cgroups以控制组为单位分配的资源,同时受到cgroups以控制组为单位设定的限制。
从容器到Docker
从以上的介绍,我们知道在Linux中,容器技术主要通过namespace和Cgroups来实现,但困难的是通过系统调用才能实现容器技术,能做到这一点的人很少,因此将其打包作为一组工具,可以极大简化用户的使用,由此诞生了LXC(Linux Container)。
LXC虽然极大简化了使用,但在面临容器分发,大规模部署时,依然存在大量的工作需要人为完成,因此,又在此基础上诞生了Docker。
容器时Linux内核中的一项技术,而Docker只是把这种技术的使用得以简化,使其得以普及。
由于笔者可能存在理解或表述错误,敬请读者批评指正,也欢迎各位同仁一起交流学习,共同进步!