从操作系统进程概念演进出发理解docker的核心原理

image.png                                              

 

导语:作为一个软件开发技术人员,你想知道现在热门的Docker技术是解决了什么问题,技术原理是什么? 本文从操作系统进程概念的演进出发阐述容器技术的核心原理。

 

操作系统与进程

进程(Process)是现代操作系统的核心概念,是程序的运行实例,是系统资源分配和调度的基本单位。

在操作系统出现之前,应用开发人员必须为“真正的计算机”编写程序。没有虚拟内存支持,应用开发人员必须自己考虑具体机器上物理内存的布局和使用安排;没有分时多任务支持,CPU的使用效率低下。

为了简化应用程序开发,提高计算资源的利用率,人们设计了操作系统来支持多任务并发执行,核心模型就是进程。

进程为程序的执行提供了一个虚拟执行环境,叫做进程空间。应用程序仿佛运行在一个独立的虚拟机器上,独占CPU和内存空间,通过标准接口(系统调用)来访问系统资源。

在计算机主要用于科学计算和数据处理的年代,计算的基本模型是:输入 + 计算 = 输出,这里计算需要的资源基本只是CPU和内存。通过提供CPU和内存的隔离,进程概念的设计为多用户多任务共享同一个计算机提供了完美的方案。

虚拟主机技术

进程概念体现了容器的核心思想,即为应用程序的执行提供独立的空间(隔离性),为系统资源或服务的访问提供一致的接口(标准性),可以说进程就是容器技术1.0。

随着技术的发展特别是计算机应用领域的扩展,计算模式发生了变化,一个应用程序需要的计算资源不仅仅是CPU和内存了:它可能需要监听某个网络端口,它需要存放在文件系统某个确定位置的配置文件,它需要链接某个确定版本的动态链接库,它需要某些环境变量的设置。。。

也就是说,应用程序不仅需要CPU和内存,还需要依赖网络,环境变量,文件系统等等的资源。它需要这样一个环境抽象,不仅独占CPU和内存,还可以获得它想要监听的任意网络端口(不会被其它人占用),配置文件总是在它需要它在的位置(不会跟其它人冲突),机器上装的动态链接库总是那个确定的版本,环境变量不会跟别人冲突。。。

当然,最理想的情况是为每个应用部署自己的服务器,完全不用考虑与其他应用的资源冲突,但这种方式显然会导致计算资源的巨大浪费,让人无法接受。解决方案是在一台物理主机上运行多个虚拟主机的虚拟主机技术。

通过Hypervisor层 ------ 一种运行在基础物理服务器和操作系统之间的中间软件层,可允许多个操作系统和应用共享硬件。

image.png

每个虚拟主机的用户仿佛拥有自己独立的主机,你现在拥有了令人激动的root帐号,你的地盘你做主,完全没有限制,想装什么软件装什么软件,想用那个端口用那个端口。好吧, 你的妈妈再也不用担心你跟别人冲突了。。。

虚拟机技术完美地解决了资源共享与隔离的问题,推动了云计算的蓬勃发展。虚拟机是云计算系统为用户分配计算资源的基本单位。比如,从阿里云或者亚马逊云,你可以根据自己的计算资源需求购买一定配置的虚拟机。VMWare, VirtualBox, Xen, KVM 等都是虚拟机技术的典型代表。

更完善的隔离与容器2.0

虚拟机方案存在一个问题: 虚拟机软件本身是一个重量级的应用,需要消耗可观的计算资源,一个物理服务器只能支持很有限个数的虚拟机。我们现在的处境是, 即便是为了运行一个非常轻量级的应用,却需要附带运行一个重量级的虚拟机,计算资源的使用效率是很低的. 这就像我们需要运送一捆稻草,却使用一辆重型卡车!

需要更合理的解决方案!回想下,使用虚拟机方案主要是因为操作系统进程对资源的隔离支持不够完善,为什么不完善操作系统进程的隔离支持呢,让它不仅支持CPU和内存的隔离,还要支持其它资源的隔离?

实际上,这件事情十几年前就已经开始在做了,这就是Linux内核的命名空间功能(namespace)。

Linux 内核namespace 功能提供六个命名空间的隔离支持:UTS,IPC,PID,Network,Mount,User,很容易猜到,Network namespace提供的是网络空间的隔离,你可以占用你想要的任意端口了;Mount namespace 提供文件系统的隔离,你想要的配置文件总是在文件系统的那个位置了,不会跟别人冲突了;你想要的动态库,JDK等都是你需要的样子,不会版本不匹配了。。。

该功能是由三个系统调用API来实现的:clone(), setns()以及unshare(),这里只介绍下clone().

fork()是POSIX规范用于创建进程的标准系统调用,新的进程拥有自己独立的进程空间,提供了CPU和内存的隔离。自然地,我们可以提供更多flag来在提供更多的隔离选项,这是通过clone()来实现的,它是Linux上fork()的一个更通用的变体,用于在创建一个新的进程的同时创建namespace的隔离,它使用一个flag来指示隔离哪一些计算资源。

我们看到,Linux namespace是进程概念的补充和演进,提供了所有的计算资源的隔离支持,相比于虚拟机, 它的资源利用率大大提高,overhead非常少,这也是现在流行的容器技术基础,我们进入了容器2.0时代。

基于Linux namespace 的隔离支持,Docker构建了一套完整的框架,涵盖应用程序的创建,发布,获取和运行的各个方面。它于2013年3月一经发布,立即引起席卷云计算领域的一股狂潮,愈演愈烈,至今热度不减!

下图是Docker的系统架构,其核心概念是Container 与 Image,类似于进程与程序的关系:

 

image.png

 

Image是传统可执行文件的演进,你可以想到,它在可执行文件的基础上增加了对网络资源,文件系统资源以及其它需要隔离资源的需求描述。它通常基于某个Image基础,比如Ubuntu或者Redhat操作系统Image,并包含它需要的配置文件,依赖的动态库,工具等文件系统依赖;还包含它需要占用的网络资源说明,比如网络端口需求等。

Image的构建需求是由Docekrfile进行描述。Docker build 工具根据Dockerfile来完成Image的构建。

Container是传统进程概念的完善,它是Image 的运行实例。在内存和CPU隔离的基础上,每个Container都会获得在其Image里说明的所有计算资源的隔离,这也是说它是进程概念的完善的原因。

当然,Image和Container目前都不是操作系统(Linux)标准的编程模型:Image 并非操作系统标准的可执行文件格式,所以它的执行也并不能由操作系统直接完成。这就是Docker daemon的核心功能。

Docker daemon负责接收docker run 命令,加载和解析对应的Image,借助namespace API,根据Image说明的资源隔离需求来构建Container实例。

Docker pull 命令和Docker Registry并非容器的核心概念,但它大大方便了容器的使用。类似于Maven,它们实现一个远程仓库的概念:Docker Registry就是一个存放Image的远程仓库, docker pull/push命令用于下载和上传Image。

我们看到,基于进程和Linux namespace,通过Image, Container和Docker daemon, docker 构建了一套完整的容器技术框架,是Linux进程概念的进一步完善。与虚拟机技术类似,容器技术为应用程序提供了计算资源隔离需求的完整支持。与虚拟机技术不同,容器技术没有Hypervisior层和VM层的额外开销,计算资源利用率大大提高。

总结

本文从应用程序对计算资源的隔离需求出发,分析了传统基于进程的多任务系统,虚拟机技术以及现在以Docker为代表的容器技术背后的核心原理。容器技术提供了计算资源隔离的完备支持,并且具有非常高的资源利用率。但Docker框架并非操作系统内核功能,需要在操作系统基础之上运行的docker daemon来支持,个人感觉这个方案并不彻底和完美,如果能让操作系统能够原生支持容器,就像支持进程那样应该是更完美的方案,这个应该是coreOS的核心理念。另外,除了资源隔离,应用程序间还有各种需要紧密合作、资源共享的需求,这是kubernets框架的核心技术。后面的课程里我们会围绕着这三个技术框架继续对容器技术进行细致深入的分析。


猜你喜欢

转载自blog.51cto.com/sunhongbo/2170540