docker,容器,编排,和基于容器的系统设计模式

都2020年了,容器,或者说docker容器这个概念,从事互联网行业的开发者应该都不会感到陌生。不管大厂仍是小厂的应用部署如今都首选docker容器。java

可是docker虽好,却并不是万能。docker自己,其实仅仅是提供了一种沙盒的机制,对不一样应用进行隔离。镜像是它出彩的一个设计,可让开发者们快速部署应用。但这对大型应用管理来讲,是远远不够的。开发者们在乎识到这个问题后,提出了编排这个概念,从而引起的新的纷争。。。node

本篇文章从容器的历史开始提及,而后介绍编排领域,swarm和k8s的纷争,最后讨论基于容器的系统设计模式,这个设计模式参考自google的论文,固然是基于k8s的啦~linux

PS:容器并不是只有docker,但本篇暂略去了它们的差别,大部分状况下这两个词能够等价。程序员

从容器提及

背景

在虚拟机和云计算较为成熟的时候,各家公司想在云服务器上部署应用,一般都是像部署物理机那样使用脚本或手动部署,但因为本地环境和云环境不一致,每每会出现各类小问题。web

这时候有个叫Paas的项目,就是专一于解决本地环境与云端环境不一致的问题,而且提供了应用托管的功能。简单得说,就是在云服务器上部署Paas对应的服务端,而后本机就能一键push,将本地应用部署到云端机器。而后因为云服务器上,一个Paas服务端,会接收多个用户提交的应用,因此其底层提供了一套隔离机制,为每一个提交的应用建立一个沙盒,每一个沙盒之间彼此隔离,互不干涉。redis

看看,这个沙盒是否是和docker很相似呢?实际上,容器技术并非docker的专属,docker只是众多实现容器技术中的一个而已。那为何后来docker会变得如日中天呢?仍是得从Paas提及。docker

Paas的本质就是经过一套打包(本地)-分发(云)的机制,帮助用户将应用分发到大规模的集群中,容器技术只是其中比较底层的一部分而已。听起来很完美,但问题偏偏就出如今这个打包功能上。打包功能比较繁琐,要为每一个应用,语言,版本都打一个包,重点是打包过程经常出现问题,极可能本地运行得好好的,打包到Paas上就出现问题,并且这种问题无迹可寻,只能经过试错解决。换句话说,Paas确实可让你体验到一键部署的快感,但在这以前,你要先体验打包过程的万千痛苦数据库

这个让用户痛苦万分的打包,docker的一个小创新的却可以解决,那就是镜像。镜像自己也是一种打包机制,而且这个镜像一般包含完整的操做系统,能够尽量还本来地环境,同时你的应用还包含在这里面。编程

经过镜像这种东西,你能够方便得在本地开发,而后将镜像上传到云端服务器部署,且基本不须要或者只须要少许修改就可使云端服务器拥有和本地同样的应用环境,而后能够经过这个镜像创建彼此隔离的沙盒环境,以部署本身的多个应用。windows

可是,docker虽然解决了Paas打包难的问题,但Paas本来的大规模集群部署的能力,倒是docker的弱项,甚至docker自己并无这方面的功能。

才有了后来提出的容器编排概念,Swarm和K8s就是围绕这块而起的纷争,固然那是另一个故事了。

docker实现原理

说完了docker实现原理,接下来就来看看docker底层是如何实现沙盒隔离机制的。

提及docker,不少人都会将它与虚拟机进行比较,基本都会引用下面这张图:

docker vs 虚拟机

其中左边是虚拟机的结构,右边是docker容器的结构,但这张图其实不是那么准确。在虚拟机中,经过Hypervisor对硬件资源进行虚拟化,在这部分硬件资源上安装操做系统,从而可让上层的虚拟机和底层的宿主机相互隔离。但docker是没有这种功能的,咱们在docker容器中看到的与宿主机相互隔离的沙盒环境(文件系统,资源,进程环境等),本质上是经过Linux的Namespace机制,CGroups(Control Groups)和Chroot等功能实现的。实际上Docker依旧是运行在宿主机上的一个进程(进程组),只是经过一些障眼法让docker觉得本身是一个独立环境。接下来咱们简单介绍下这部份内容。

若是在一个docker容器里面,使用ps命令查看进程,可能只会看到以下的输出:

/ # ps
PID  USER   TIME COMMAND
  1 root   0:00 /bin/bash
  10 root   0:00 ps

在容器中执行ps,只会看到1号进程/bin/bash和10号进程ps。前面有说到,docker容器自己只是Linux中的一个进程(组),也就是说在宿主机上,这个/bin/bash的pid多是100或1000,那为何在docker里面看到的这个/bin/bash进程的pid是1呢?答案是linux提供的Namespace机制,将/bin/bash这个进程的进程空间隔离开了

具体的作法呢,就是在建立进程的时候添加一个可选的参数,好比下面这样:

int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);

那样后,建立的线程就会有一个新的命名空间,在这个命名空间中,它的pid就是1,固然在宿主机的真实环境中,它的pid仍是原来的值。上面的这个例子,其实只是pid Namespace(进程命名空间),除此以外,还有network Namespace(网络命名空间),mount Namespace(文件命名空间,就是将整个容器的根目录root挂载到一个新的目录中,而后在其中放入内核文件看起来就像一个新的系统了)等,用以将整个容器和实际宿主机隔离开来。而这其实也就是容器基础的基础实现了。

可是,上述各类Namespace其实还不够,还有一个比较大的问题,那就是系统资源的隔离,好比要控制一个容器的CPU资源使用率,内存占用等,不然一个容器就吃尽系统资源,其余容器怎么办。

而Linux实现资源隔离的方法就是Cgroups,具体的使用方法就很少介绍。Cgroups主要是提供文件接口,即经过修改 /sys/fs/cgroup/下面的文件信息,好比给出pid,CPU使用时间限制等就能限制一个容器所使用的资源。

因此,docker自己只是linux中的一个进程,经过Namespace和cgroup将它隔离成一个个单独的沙盒。明白这点,就会明白docker的一些特性,好比说太过依赖内核的程序在docker上可能执行会出问题,好比没法在低版本的宿主机上安装高本版的docker等,由于本质上仍是执行在宿主机的内核上。

对了,还有mac和windows系统,这些是怎么实现的呢?很简单,它们的docker都是创建在虚拟化的linux上的,因此其实仍是linux。

说完容器,接下来就开始介绍编排了。

编排之争

这里咱们主要会介绍编排这个概念,以及从这个概念起引起的docker swarm和k8s的纷争。

docker自己只是提供打包-部署的功能,它并无提供分布式集群(大规模集群)管理的功能,这实际上是本来Paas项目的主要领域。而编排才是容器技术的核心魅力所在,没有编排,容器就只是一个沙箱工具。因此从docker成熟之后,你会发现它的主要发力点是在编排,也就是swarm项目上,不过这个docker的亲儿子,swarm编排工具,却败给了横空出世的k8s。

为何会这样?

先说说什么是容器的编排,说简单些就是对(docker)容器的配置,运行时候的行为的管理

那么docker swarm是怎么进行编排的呢?这其实还涉及到另外一个项目,docker-compose,这两个项目与docker Machine合称为docker三剑客(怎么听起来有点low)。

前面说到编排就是对容器的配置和运行行为进行管理,那么很天然的想法就是将这些配置和行为的定义都写到一个配置文件里面,好比用户须要运行容器A,容器B,容器C。那么咱们能够将这几个容器相关的配置和关联,好比网络,磁盘,启动副本,出错行为等配置,还有容器间的协做方式(启动顺序等)都写到一个配置文件。最后经过一条命令,加载并执行这个配置文件,就可以实现容器的编排了

swarm作的事情很简单,有时候简单不必定是好事,由于那意味着难以知足业界复杂的需求。好比它在处理有状态服务上的无力,又好比它难以处理多个服务间复杂的关系(处理服务的顺序是不够的)。这时候,脱胎于Borg的kubernetes(k8s)出如今人们的面前。它身上,沉淀着google数十年的经验,能够说它就是那个站在巨人肩膀上的宠儿。那么相比于swarm,它的优点到底在哪里呢?

答案在他的设计上,这个提及来得详细介绍k8s才能说明白。从设计上说,k8s总体是基于API设计,即总体架构中涉及的组件均可插拔,以容器举例,在k8s中,容器是可替换的,只要知足对应的接口设计的标准便可,docker是其中一种方案,而其余容器技术也是可选方案。和容器相似的还有网络插件,volume插件等等。有关k8s的详细内容这里很少介绍,有兴趣的童鞋能够参考如下文档:

即整个设计是以集群管理为核心,总体架构都是松散的,可插拔的。而swarm则是以docker为核心,二者设计就存在本质上的区别。

然后在容器的基础上,k8s添加了另外一层的封装,即Pod,所谓Pod,是一组相同或功能相似的容器所组成的组(task group)。为何要有Pod呢?还记得容器的本质是什么吗,是操做系统中的一组进程,从某种程度上来讲,从进程这个层次来进行管理,有些繁杂了。好比Linux都有进程组这个概念管理相同或彼此联系的一些进程(好比一个功能由多个进程协做完成,这多个进程构成一个进程组)。

在容器编排中,每每多个容器间也会有相似进程和进程组的关系(这里就不举例了,不少分布式组件都有这种状况),因此须要一个更高层次的抽象来帮助咱们对容器进行管理。在k8s中,承担相似进程组的就是Pod,Pod是一种逻辑上的概念,同时Pod也是k8s中最小的调度单位,同组Pod中的容器都是共享volumns。

Pods

有了Pod,也就是组这个概念后,就可以更加方便对不一样服务进行管理。可是它还有个更重要的意义,那就是基于容器的系统设计模式。

基于容器的分布式系统设计之道

让咱们回到1980年,假设你是一个写惯了C的程序员,你接触到一个名叫面向对象的编程概念,你会怎么看待这个东西呢?可以想象这个东西在30年后会占据编程领域的大半壁江山吗?

而现在,docker容器(或者说Pod)就是一种相似OOP的东西,核心都是经过模块化封装,将不一样的东西相互隔离,让它们相互配合,完成某些事情

从这个角度,或许就能明白为何前面说到的,容器价值不高,真正有价值的是编排。由于咱们一样不会以为一个java object有多大价值,OOP的编程思想,及其衍生的设计模式才是精髓。

那么从分布式系统的设计模式的角度来讲,容器能够有多少种分类呢?和分布式系统的搭建模式相似,有三种。

  • 单容器模式(single-container patterns for container management)
  • 单节点协做模式(single-node patterns of closely cooperating containers)
  • 和多节点协做模式(multi-node patterns)

PS:这部份内容多参考自google的Design patterns for container-based distributed systems,想看原味论文的童鞋请戳最下方的连接。

单容器模式和单节点协做模式看起来类似,但实际是彻底不一样的东西。

单容器模式,简单说就是在传统Docker的基础上(传统docker的行为比较简单,只有run(),pause(),stop()),提供更加丰富的功能和生命周期的管理。说得更简单点,使用k8s管理单个docker服务。

咱们主要介绍单节点协做模式和多节点协做模式。

单节点协做模式

单节点协做模式,简单说就是在一个分布式的容器服务环境中,经过一个单节点的服务辅助进行管理的这类模式。在这种模式中,须要依赖于k8s中,Pod这个概念的抽象,Pod即task group,一组相同或相似服务的容器的集合。

主要有如下几种设计模式。

Sidecar pattern(边车模式)

边车,这个词可能不少人没听过(包括我了解这个东西以前)。咱们先来贴一下边车的图,

边车就是摩托车旁边的那个小车,在某些环境下(比赛,我猜的),旁边车上的人能够给车手递水、食物等操做。

边车模式也是相似的,即在主服务(的容器,main container)身边提供一个辅助容器,帮助主服务作一些脏活累活。

好比一个web应用,它会将日志信息写入到磁盘中,这时候咱们就能够新增长一个日志采集的边车,协助web服务完成日志采集的工做。就像下面这样:

仍是挺好理解的,这样的好处,相信了解过设计模式的童鞋随随便便就能列举几个,不过这里仍是从容器的角度详细介绍下:

  1. 容器是资源分配的一个单元。将边车服务分离后,能够更加灵活地经过cgroup配置资源,或者一些动态调节资源的操做(好比忙时给web服务更多资源而边车更少资源)。
  2. 容器是最小的打包单位。有助于不一样服务的责任划分和测试。
  3. 容器能够是复用的单位,好比能够将日志服务用语其余服务。
  4. 提供了错误边界,可使系统能够正常降级,好比日志服务出错,不会致使web服务出错。
  5. 容器是最小的部署单位,能够为每一个服务升级,和回滚。但这也多是缺点,由于服务一多难以管理。

由于分离因此多了这些好处,听起来仍是蛮诱人的~

Ambassador pattern(外交官模式)

外交官模式,提供一个容器做为代理与主服务(main container)通讯。
就至关于在通讯口出作多一层代理,好比主服务觉得是与一个本地redis通讯,但实际上代理会真正与一个redis集群交互。

外交官模式的好处是,让主服务与外部组件之间相互隔离。只经过代理的话,那么外部组件能够无缝进行替换,而这一切主服务都是无感知的。而后是方便测试和复用,其实就是服务之间解耦的好处啦,和上面边车模式是有点相似的。

Adapter pattern(适配器模式)

前面说的两种模式,主要是为了帮助主服务(main coninter)更专一于本身的职责。而适配器模式则是为了方便其余组件。

举个例子,假设你有多个服务(web,数据库,缓存服务等),而后须要一个监控监控这几个组件是否正常。正常状况下,须要让监控系统获取不一样服务之间的指标信息,而后才能进行监控。

但这样的问题是,若是增长或减小服务,那么对监控系统来讲会很麻烦。适配器模式可以解决这种困扰。

若是多个服务,web,数据库,缓存等都提供一个统一的对外接口,那么咱们就可以使用一个适配器容器,统一获取这些服务的指标信息,而后由监控系统经过这个适配器容器统一获取全部的指标信息。以下图所示。

OK,那么以上就是单节点协做状况下的三种设计模式,下面再看看多节点的协做模式。

多节点协做模式

这部份内容会比较简单一些,这里就不花太多篇幅进行讲述。

除了单节点上的协做容器,模块化容器还使构建协做的多节点分布式应用程序变得更加容易。不过这部份内容听起来很高大上,但实际上是很好理解的东西。

好比分布式领域的zookeeper,你们应该都不陌生,在论文中,这种多个节点提供领导者选举的模式,被称为领导者选举模式。一样的,kafka这类消息队列,被称之为工做队列模式(rk queue pattern)。而最后一种,则是相似spark的,master worker计算模式,即将一个计算任务分布到多个其余计算节点的这种方式,称之为Scatter/gather pattern模式。

列举的几种模式都是经过多个节点协做,而且经过暴露接口提供对外服务。不过其实基本就是常见的使用容器搭建分布式服务的方式,若是使用过docker来搭建hadoop这一套东西,那么对所谓的多节点协做模式确定不会陌生。

想一想也是,若是真的将分布式系统当作一个工程项目,那么这些多节点的部署模式确实须要一个名分。这能够算是一个典型的实践先于理论,理论总结实践的例子吧。(不过我仍是以为这部份内容有水论文的嫌疑)

那么关于容器的分布式系统设计的内容就先到这吧,有兴趣看原论文的童鞋能够翻到最下。

小结

OK,本文主要介绍了docker容器的发家历史,而后介绍容器编排的重要性,并简单说了为何swarm会在编排的战争中输给了k8s。最后则从容器编排这个概念延伸到基于容器技术的设计模式,三种模式中,单节点协做模式算是比较新颖,仍是有些启发价值的。

以上~

参考文章:

Design patterns for container-based distributed systems

kubernetes设计理念

Docker 核心技术与实现原理

An Introduction to Docker and Analysis of its Performance

相关文章
相关标签/搜索