2016年初,与Docker相关的第一条重磅新闻莫过于在1月份Docker公司宣布收购刚刚在业界小有名气的微型操作系统公司Unikernel Systems。然而时至今日,当我们在技术圈内谈及Unikernel时,看到的仍然是许多人疑惑的表情。就像『容器』这个怪物刚刚出现在国内的技术社区时那样,到处都显得格格不入。
在Docker才诞生时,布道者们通常会拿出一张经典的虚拟机vs容器对比图,来讲解容器的好处。那么我们今天的话题开始前,我先搁上这张图,加上Unikernel的对比,镇一下场子。诸位坐好,且听我把容器与Unikernel的这点事儿慢慢道来。
图1 虚拟机、容器和Unikernel
Unikernel是什么
『Unikernel』的概念在早在20世纪90年代的时候就已经有雏形了,它是一类特殊操作系统的通称,有点像我们现在所说的『容器』或者『Container』,是个概念层面的东西。最早的Unikernel操作系统原型是Exokernel和Nemesis,之后相对比较出名的有Xen社区主导的MirageOS、前Kvm开发者主导的OSv、以及由NetBSD公司开源的Rump Kernels。
在通常的操作系统中,系统的内核与用户的应用程序之间是有明确分界的,在Windows或是Linux中都有清晰的『内核态』和『用户态』定义,并通过『系统调用』的方式进行数据交换。这样做的目的是将操作系统与硬件打交道的功能与上层应用隔离,从而屏蔽其底层的差异性和复杂性。而在Unikernel的操作系统中是没有这种差别的,所有程序与底层的核心驱动都运行在一起,不存在运行时状态上下文切换的额外开销。在国内关于Unikernel的一些早期文章中将这种运行方式表述成所有程序都运行在『内核态』,这样的表述并不准确,因为Unikernel并没有专门的内核概念,而是将每个应用程序在编译时可以直接指定引入特定的驱动和核心library,相当于每个程序都是自带操作系统的,而且是单独定制裁剪的操作系统。
通过Unikernel系统方式构建出来的应用程序都是可以独立发布和直接运行在虚拟化平台上的,由于Unkernel系统原生不考虑多用户和多任务的复杂场景,因此可以做得十分精巧。一个应用服务就是一个操作系统,从而形成规模极大的操作系统集群。你可能会说,这不就是嵌入式操作系统么?单纯的从概念上看,Unikernel与嵌入式系统颇有几分相似,但Unikernel系统的运行环境通常是虚拟化的基础设施,而不是那些嵌入式硬件设备,稍后我们也会对这个关键性差异进行更深入的讲解。
说到这里,有些读者可能开始嘀咕了。在本篇开始时说到被Docker收购的那个『Unikernel Systems』与我们说所的『Unikernel』是同一个东西么?
其实不是。
被Docker收购的『Unikernel Systems』是一个由许多专注于Unikernel技术的极客们组成的,致力于推广和发展专用Unikernel产品的公司,总部在英国。两者的关系在于,Unikernel Systems公司(只有13名正式员工)同时也是Unikernel社区目前最主要的贡献者。Unikernel组织的官方网站是『unikernel.org』,而Unikernel Systems公司的官方网站是『unikernel.com』,从两者的网站风格上同样能看出千丝万缕的联系。
图2 Unikernel Systems公司网站(上)和Unikernel社区网站(下)
那么,作为一种操作系统,Unikernel与我们所熟知的Docker、Linux或者Windows有什么样的关系呢?
简单的回答是,没有必然关系,但它们之间可以有交集。通常来说,Unikernel操作系统既不能兼容Linux或Windows软件,也不能运行Docker容器(目前还没有能运行Docker的Unikernel系统)。但凡事不会是绝对的,开源界总会有一些别具一格的例外(比如稍后会介绍的Rump Kernels和OSv),正是这些例外的出现,使得已经沉闷许久的Unikernel如今得以驾上容器化的大浪,与Docker有了这次亲密的约会。
Unikernel的诞生与发展
Unikernel的前身是libOS。这个概念源于当时嵌入式操作系统架构的一次研究性的尝试,它将操作系统应用于各种硬件设备的驱动进行抽象,形成了各种不同的可替换的library,这与Linux系统最初的发展有些相似。然而这些创造者们走了一条与Linux内核不同的发展路线,他们没有将驱动与系统代码进行进一步的封装和整合而形成『内核』,更没有使用诸如『内核态』与『用户态』的划分,而是让使用系统的使用者和开发者直接与驱动通信,只需要在编译时候引入适当的的library,便能构建出快速适应不同的硬件设施的应用服务。
随着libOS的发展,出现了许多各立门户的操作系统分支,这些系统通常都只会具备有用于特定硬件的专属驱动,由于开发者自身的喜好和偏向性,各系统对不同硬件的支持差异较大。而同一时期诞生的以Unix和Linux为代表的另一操作系统门派,凭借着更好的易用性和统一性逐渐占领了主导地位,这个门派也就是现在大家所熟悉的大而全的系统内核,多用户,多任务,加上严格的内核与用户分界的操作系统风格(要是当年胜出的是libOS,整个计算机行业的发展路线也许就完全不一样啦)。
值得庆幸的是,在libOS并未完全没落之前还发生了一件事情,那就是虚拟化技术的兴起。随着虚拟化的广泛应用,一些libOS系统也开始提供用于虚拟化基础设施的library驱动。正在libOS逐渐势弱之际,当时的虚拟机化新秀Xen发布了一个独立的社区操作系统项目:MirageOS。这个项目是一个基于libOS理念设计的开源操作系统,但它比其他的libOS系统更进一步之处是,索性直接抛弃了所有物理硬件的驱动支持,专心一致做虚拟化,而将硬件的差异统统交给虚拟化平台去完成。同时,MirageOS借着Xen虚拟化的广泛应用,打出了所谓『Cloud Operating Systems』的大旗(因为MirageOS也就只能运行在虚拟化的云平台上),并将同一时期诞生的这类libOS操作系统都统称为『Unikernel』。
在MirageOS之后,Unikernel的叫法就被沿用下来了。此后还陆续诞生过许多基于这种设计理念的操作系统,例如:ClickOS、Clive、HaLVM、LING、Rump Kernels和OSv等。接下来我们要特别介绍最后的两种,因为他们在Unikernel系统的发展过程中具有十分独特的地位。
Unikernel的兼容性探索
在Unikernel不断演进的过程中,分化出许多不同的发展方向,有些带有浓厚的学术色彩,有些则充满创造性或实用性。其中值得一说的是Rump Kernels和OSv,这两个项目是Unikernel对现有系统软件的兼容性改善方面做出的十分有价值的尝试。
前面介绍MirageOS时,我们并没有提到MirageOS系统是用一门自己独有的语言编写的,这个语言叫做『OCaml』。作为典型的Unikernel程序运行方式,用户也必须通过OCaml编写自己的程序,这样才能才编译的时候打包成运行的内置操作系统的软件。从设计上来说,OCaml是理念十分先进的函数式编程语言,然而由于这门语言的受众量有限,软件移植困难,注定了MirageOS操作系统在这个大局已定的服务器系统领域很难再有大的作为。
吸取了前人的教训,Rump Kernels和OSv在设计时,分别通过各自的方式对现有的主流软件体系进行兼容。
Rump Kernels的思路干脆明了,Unikernel的架构针对的就是大规模的虚拟化服务器应用,既然POSIX/Linux是服务器软件的主流标准,那就兼容这套标准吧。Rump Kernels系统核心使用C语言开发,提供POSIX相关的各类library,以及符合POSIX标准的C/C++编译器,又原生编译了包括Python、Ruby、PHP、Bash、Nodejs、Rust等诸多语言的解释器和编译工具。这样一来几乎绝大多数能在Linux上使用的软件都可以快速迁移到Rump Kernels上了。
OSv系统同样是使用C语言编写的,不过它要稍微任性一些,因为OSv的C编译器并不兼容Linux的POSIX/GCC标准。即便如此,许多标准的C语言应用(没有用到POSIX/GCC编译器特殊语法)都是可以直接在OSv编译运行的。此外,OSv的高明之处在于,它成功的移植了Java虚拟机(这是Rump Kernels都没有做到的),这个在TIOBE编程语言流行度排行长居第一位的语言的确为OSv系统留住了不少用户。此外,OSv及时跟进的提供包括Tomcat、Cassandra、Redis等一些主流标准服务的官方操作系统,省去了用户自行编译的麻烦。
注意,我说的是『Tomcat的操作系统』、『Redis的操作系统』,这种说法未必准确(应该叫『内置操作系统的Tomcat服务』等),但值得强调的是,这些服务都是会直接运行到虚拟化硬件上面的,每个服务就是一个内置操作系统的整体,这就是Unikernel!服务与系统的边界已不存在,成为完全的单体,这种转变得让才刚刚适应容器的开发者们稍微消化一会儿。
Unikernel带来了什么
Unikernel的优势概括起来就是:架构简单、安全高效。
首先,Unikernel抹去了现代操作系统由于软件层级抽象而导致的复杂性。越是通用的操作系统(比如Linux或Windows)包含了越多对特定的应用而言并不必要的服务、驱动、依赖包等。譬如USB驱动这类东西在虚拟化的云环境其实是无用的,但在内核运行时仍然会去将它加载进来。这些多余的内容既增加软件运行的负担,又额外消耗了非必须的资源。
其次,Unikernel消除了运行时内核态与用户态切换的过程。Unikernel的核心驱动与应用程序是同时打包构建的,其内存是单地址空间(single address space),不区分系统内核区域和应用服务区域,因此不论是在启动速度还是在程序执行效率上都远高于通用的服务器操作系统。通常一个Unikernel系统的启动时间都在几十毫秒左右,可以这么说,只需一眨眼功夫,成千上万个服务节点的集群就启动就绪了。
此外,Unikernel带来的还有安全性方面的提升。一方面,只运行操作系统的核心,抛掉那些可能是漏洞来源的视频、USB驱动、系统进程极大的减小了可攻击的面积。另一方面,对于Unikernel而言,每一个操作系统都是定制用途的,其中包含的核心库均不相同,即使某个服务构建的操作系统由于特定软件问题而遭到入侵,同样的入侵手段在其他的服务中往往不能生效。这无形中增加了攻击者利用系统漏洞的成本。
最后,Unikernel也是一种不可变的基础设施(Immutable Infrastructure)。不可变的基础设施的构想是Chad Fowler于2013年提出的,并随着Docker的流行而为人们熟知。Unikernel系统一旦编译完整就不可改变的,想要对其中的内容进行重新定制的唯一办法就是修改源码然后重新编译。这种软件实施方式有点像使用一次性产品,即安装一次,不做修改,用过即扔。这种方式对保障软件部署的一致性,提高运维效率和降低管理的复杂性方面带来的好处已经被许多容器化的实践所证明。
容器式的操作系统
与Unikernel操作系统具有异曲同工之妙的还有另一类操作系统,我们暂且称它们为『容器式的操作系统』(Containered OS),典型的代表是Hyper和RacherOS。容器式的操作系统是一种通过容器技术将应用程序直接运行在虚拟化平台上的手段,如果将这类操作系统的运行模式画到最开始时候的那张对比图里面,得到的结果会与最右边的Unikernel如出一辙。
然而这些系统并不是严格意义上的Unikernel。其中的关键差别在与,容器式的系统依然存在单独的内核概念。以Hyper为例,这是一种支持将Docker镜像中的内容直接部署到Kvm或Xen虚拟化平台上的工具,当用户启动Hyper的操作系统实例时,实际上是启动了一个经过高度精简过的Linux系统内核:Hyperkernel。这个内核的启动开销几户可以小到忽略不计(小于1秒钟),启动后加载一个Docker镜像并运行其中的内容,因此同样做到了每个服务独立使用一个操作系统,又使得这个操作系统中有且只有运行该服务所必需的文件和library,也做到了每个服务的运行环境高度定制化。唯一的美中不足,是这些系统依然保留了Linux『内核态』和『用户态』的切换(虽然相比普通Linux内核,已经要高效得多)。
容器式的操作系统是近年来伴随着Docker的繁荣发展而出现的新事物,它是容器技术在Unikernel方向上的一种中间状态产物。因而同时具备有两者的优势:Docker的完美Linux兼容性和Unikernel的安全和高效。这个刚刚兴起的独立领域同样是符合当下技术演进方向、值得关注的一种趋势。
Unikernel与Docker
回到文章最前面的话题,Docker收购当下掌握Unikernel社区主导话语权的Unikernel Systems公司。可是Docker与Unikernel到底有什么可以合作的呢?
这个问题其实在2015年巴塞罗纳的DockerConEU已经有一部分的答案:用Docker来构建Unikernel系统。
在Unikernel巨大的性能和安全性优势的背后是其系统构建的繁琐,每次软件的发布都需要经过『编写应用程序』、『选择需要的library』、『将应用程序与系统编译到一起形成系统』这些复杂的重复性操作,其中涉及到许多构建过程中需要的工具和零散的文件。确保每一个开发者都能获取一套能够正确编译出『某个服务的操作系统』的环境其实是一件比较麻烦的事情,而这个事情正是Docker所擅长的。
DockerConEU 2015大会的最后一天,Docker邀请了Unikernel社区成员Justin Cormack上台演示了一段使用Docker快速构建Unikernel系统的Demo(Justin也是Unikernel Systems公司员工,当时Docker还未收购Unikernel Systems)。这次演示所使用的Unikernel正是Rump Kernels,通过使用Docker镜像,Justin在几分钟内在一个普通的Ubuntu主机上创造出构建Unikernel系统的环境,并编译出一个内置操作系统的Nginx服务、一个内置操作系统的MySQL服务和一个内置操作系统PHP服务,然后在Kvm上将这些Unikernel服务运行起来,最终展示出一个完全运行在Unikernel的Nibbleblog博客(一个类似Wordpress的使用PHP编写的博客程序)。
更让人惊叹的事情在于,最终编译出来的内置操作系统的Nginx服务只有2MB大小!这几乎就是一个Nginx二进制文件的体积,而它其实是一个可以直接运行在Kvm或Xen这样的云平台上的完整操作系统!
这个答案也许还只是Docker的Unikernel计划中微不足道的一步,未来的Unikernel系统会成为容器技术在操作系统领域的延伸吗?收购Unikernel Systems更大的意义在于其背后的Unikernel社区,独享Unikernel.org和Unikernel.com这两个域名可谓是占尽先机了吧。
从Docker创建libcontainer成为独立容器引擎,到创建Swarm成为独立集群方案,如今Docker开始投入真金白银,搅入Unikernel这片充满机遇的蓝海,这个举动足以看出Docker公司的远见。随着容器和虚拟机后技术的进一步融合,这盘棋还大有看头。