软件的部署方案从计算机软件出现以来就一直在不断演化着。而这部署方案的演化也体现着软件行业生态,甚至是整个计算机行业的演化。以下基于我当前的理解,讨论一些在现在还有着深刻影响的部署方法。
源代码部署
源代码部署是非常古老的方案了,开发者在编写完代码后,直接将自己编写的源代码发给用户,用户自己进行编译运行。在当初计算机的硬件体系十分多样的时候,不同的芯片使用不同的机器代码,而作为C/C++的高级语言则能够在不同平台之间基本保持不变。使用每个平台自己的编译器进行编译才能保证编译出来的二进制能与自己的芯片相兼容。
时至今日,这个问题已经得到了缓解,目前依然活跃的平台只有x86,ARM,PowerPC等为数不多的几个了,开发者完全可以针对自己想支持的所有平台分别编译二进制文件。但是这个源代码部署的方式并没有随之而消亡,原因是一个大型的软件有很多可以配置的选项,而这些选项需要在编译时确定。如果用户从源代码开始编译的话,就能最大限度地获得满足自己需求的二进制文件。另一个需要从源码编译的原因是依赖库的版本问题。一般来说,一个软件的二进制文件只能与它的依赖库的某些版本合作。而如果你希望使用不同版本的依赖库,虽然使用编译好的二进制不行,但使用源代码重新编译或许就可以。
该部署方案虽然非常灵活,是适用范围最广的部署方案,但其缺点也十分明显。第一,首先需要收集各种依赖的软件,如库,编译器等,有时还会有版本不兼容的问题,整个过程非常容易出错。第二,费时较长,一个较大的软件的编译过程通常需要数十分钟到数个小时的时间,一旦出错基本就要再来一遍。
复制二进制文件
那既然编译麻烦,就由软件的开发者编译好,将编译好的文件分发给大家安装不就好了吗?可是这样面临着更大的问题。比如,在编译时实用的A库的版本是2.0,但在部署的环境中只有1.0,你的软件就没法正常运行了。有时候,即使版本正确,但安装的路径不一致也会导致问题。只有当编译和部署的环境完全一致时才能保证正常。该种方法基本不会在大范围的软件部署中使用,基本只会用于部署少量几台电脑。
二进制软件包部署
属于该类的最著名的就是deb包和rpm包了。这类软件包中保存了编译好的二进制文件,一些配置文件和必要的数据,以及一些元数据。元数据中记载了软件包的依赖信息,即该软件包中的软件需要和其他的哪些软件(库)的什么版本一起安装才能工作,与什么版本安装不能正常工作。安装时,软件包管理器依据这些信息来试图满足所有依赖。
目前大多数Linux的发行版都采用类似的方法来分发软件,比如Ubuntu,Debian使用deb包,CentOS使用rpm包。该方法实行这么多年来也被证明是非常实用的方法,软件的安装者只要指定我想安装什么包,剩下的就只要交给包管理程序就行了。
但是还是有一些问题的。比如,一个你依赖的库进行了一次小的升级,用户安装了这个升级。但这个升级却导致你的软件工作不正常了。再比如软件A依赖B,软件C依赖D,而B和D之间相互不兼容,于是你莫名其妙地面临了A和C二选一的问题。
与依赖项一起打包部署
随着现代计算机硬盘空间的不断扩大,人们有了部署软件的另一选择,那就是将软件的和它的所有依赖项打包在一起进行分发。我的软件只使用我自己带的依赖,这样便可以一口气解决之前提到的软件之间各种不兼容的问题。也使得同一个软件包在不同发行版中部署成为可能。
当然,这也不是没有代价的,由于将依赖库一同打包,部署所需的磁盘空间和要传输的数据量都大大增加了,同一个依赖库不能在各个不同的软件之间共享了。但这些因素都正变得越来越廉价,不是吗。而且,这也牺牲了灵活性,比如在一个依赖库中有一个bug被修复了,用户并不能简单地升级这个库来应用这个修复。
较新的snap便一个支持这种方法的工具。它是Ubuntu的Canonical的产品。但看上去这个平台不是很开放,我连如何部署一个私有源都没找到。
容器技术
同样是将依赖项打包在一起进行分发,容器技术则做得更加彻底。在容器中运行的软件将几乎不可能收到容器外运行的软件的影响,其文件系统,网络等都是完全隔离的。此举更保证了软件能在部署环境中正常运行。
当然,也是有代价的。由于其完全隔离,在容器里访问主机上的资源也变得困难了。由于其隔离得更加彻底,需要打包得东西也更多,包括一些系统的软件也需要打包进容器镜像中。
虚拟机
这个吧,当然是隔离得更加彻底了,整个系统都打包进去了。不过我目前还没见谁用它来部署单个软件的。
虚拟机技术就不光是硬盘空间占用更多了,包括运行速度,内存占用等都会受到影响,而却没有什么更多的好处,所以没什么人用吧。
我们如何抉择
当前,我没的C++程序只在大约5台计算机上部署,且部署频率很低。因此,随便哪种方式对我们来说关系都不大。我们当前用的是最原始的源代码部署方式。但我个人依然希望能切换到更加高级的部署方式,通过工程的手段来保证程序运行时更加可靠。同时也能提高部署、测试的频率。我目前比较倾向于snap的方式,这样:1.我们可以用任意库的任意版本,反正和我们的软件一起打包,而不用受限于Linux的发行版;2.我们的软件经常需要和特定的硬件交互,容器的高度隔离反而会给我们带来更大的麻烦。