【原文编者的话】作者应该是个Geeker,喜欢思考很多本质性的东西,并开创性的将Docker跟链接器来作比较,并提出了很多有意思的观点如:应用程序栈的链接器等。不管对不对,至少他这种思考的方式值得我们学习。
我必须告诉Docker,他们的创意完全满足我对“天才般创新”的标准,Docker让我花了一段时间来弄明白它到底是什么。
伟大的创新绝对不是发明能飞的汽车,而常常是一些很简单但是细细想想背后的原理非常复杂的一类事物。举个例子,比特币就是一个绝好的发明。它很简单,但是背后的故事非常复杂,仔细想想很多人都会发出疑问,它可以流通吗?有股票价值吗?可以用于债券吗?这其实是个分类的问题,可能把所有的这些不同方面的属性结合在一起就是个新的事物。
Docker绝对不是一个像OpenVZ或者BSD jails一样的容器。当我第一次使用Docker的时候,我费了很多时间在找如何在一个运行的容器中配置一个/dev的node;最后我终于理解了 Docker:其实你没有必要这样做,你只需要启动一个/dev的node的容器即可,Docker的容器是进程,而不是一个完整的虚拟机系统。
Docker是一个新的虚拟机:在不运行时表现为“静止”(immutable)包含运行程序的容器(其实就是镜像),然后启动此镜像后在系统中表现为一个进程。
但是,Docker的本质是什么呢?
这个问题我思考了好几天,最后我顿悟了:Docker是个能够将手动链接变得轻松的工具。更具体的说,是个可以手动链接应用程序还能将其保存的工具。(作者的意图是从链接这个低层工具出发来思考Docker的价值,因为低层的链接器就是将不通程序模块组合成一个程序来运行,从哲学层面上将确实跟 Docker做的事情有些类似,但是维度不一样。)
但是后来我又仔细的思考了一下,好像又不是这样的。
链接器是什么
如果你使用过GCC、clang或者Visual Studio,你就在使用编译器来构造可以执行的程序。对于C或C++来说,编译器会将源代码编译成一堆的目标文件;目标文件中包含了源代码对应于目标平台的机器代码(或者是中间代码)。但是,要使程序能够运行,你的代码一定会调用其他模块的代码——函数;所以链接过程就是把你写的程序中所有依赖于其他模块、库中的代码收集起来并链接到你的目标代码中。(注:关于程序是如何执行这个问题,可以参考译者之前的博文:程序是如何运行的)
在维基百科中有关于“链接器”的简明的解释。举个例子,比如你使用C语言写了个程序,调用了“printf”这个函数,代码通过编译器、链接器处理过后会得到一个目标文件,中间会有这么一段“从这里开始调用printf函数”;所以printf不是我程序的一部分——它是一个叫做C语言标准库的一部分(注:glibc,C语言运行时,分为静态链接与动态链接两个版本。)。链接器会分析glibc的静态库文件,收集我的程序中所需要重定位的符号信息,包括printf这个符号;然后将printf的代码从glibc中抽取出来,链接到我的程序中,组成一个完整的可以运行的可执行文件;这样我就能通过printf函数向终端输出字符信息了。
但是真实环境更加复杂,还有个叫做动态链接的概念。很多时候,除开我使用Go语言,我不希望我的可执行文件过大;我会更加希望我编译出来的文件模块分明,不同的模块包含在不同的文件中,我的入口程序在运行时来决定加载哪个模块并运行——这就是动态链接。动态链接做的很多事情跟静态链接类似,只是将链接的过程延迟到运行时完成罢了。
但是从概念上讲,什么是链接器呢?
当程序员谈起『链接器』时,他们第一反应是C/C++语言编译出来的目标文件。但是,我们回过头想想,忽略一些细节,链接器到底干了些什么呢?链接器的工作是:能够自动完成一些将分散的代码组合起来的复杂工作的程序。从学术上来说,Unix的『ID』,Windows中的『link』都是链接器;而广义上来讲,任何能够自动将代码片段链接在一起运行的程序都是链接器,比如以胶水工厂著称的SWIG。
二进制元(META-BINARIES)
想象一下没有链接器的情况,任何时候你生成一个程序后——或者运行一个需要众多动态库支持的程序——你都必须手工打开hex编辑器,从二进制代码的层面手工进行符号收集与重定位工作。但是,广义上来讲,你不用去想象,你已经在做这部分工作了,只是层面不同,比如,你需要配置一个LAMP系统的时候,你需要编辑很多配置文件使各个系统能够配合运行通畅,再比如,你需要增加Redis或 Memcached组件时,你必须要更改配置文件一样;你在进行手工链接,链接不同的程序。
再深入一点去思考的话,程序的链接跟应用程序级别的配置没什么两样,只是前者是链接低层代码段,后者是将不同的模块、系统拼接起来运行。但是,市场上并没有专注做“应用程序栈”链接的“链接器”,你必须要手动配置他们的运行参数来实现它们之间的协同工作,如:设置TCP监听端口,设置访问权限规则等等。这其实是高层次的链接过程,应用程序之间靠协议通行或者ABIs的调用。
Docker不是链接器
所以Docker是链接器了?答案是:不是!
Docker实际上是提供了一个环境,你可以在这个环境中将你要的”应用程序栈“配置好,然后保存你的设置,形成镜像;然后你就能复制这些镜像,多处运行,而不需要再次进行配置。
Docker还包含了很多配置工具,你可以配置容器运行时的网络,运行时访问安全,配置VXLAN overlays来实现同一个数据中心或者局域网中不同容器间的互访。但是这些都是附加属性,Docker的核心是可以让你保存你的手动链接结果。撇去这个功能,Docker只是部署、运行服务器端应用程序的另一种方式而已。(注:Docker的基于进程的虚拟机模型也是一个亮点。)
应用程序栈的链接器?
是不是没有基于“应用程序栈的链接器”呢?也不尽然,也有些人在尝试,但是都遇到了困难,导致都不知道要做成什么了。比如,Chef、Puppet与Saltstack这些产品,他们都提供了很多附属工具,但是核心功能是用脚本化的链接方式把小的程序模块组合成大的应用程序执行栈来运行。
但是,使用过这些工具的人都觉得它们复杂与笨重;我觉得主要是因为开发它们的程序员错误将它们定位在企业级服务器管理工具上了。他们应该开发更加通用的系统会更加实用。
如果我们能达到这些,我们还会需要Docker吗?
可能吧,因为Docker很有用。但是,如果我们拥有一个“应用程序栈”级别的“ld.so”(Linux下的动态链接器)就不好说了;因为,相比我们把应用程序栈打包生成一个Docker镜像,我们只需要一个动态链接器就能把不同的应用程序链接在一起工作了。
本文作者:肖劲
来源:51CTO