首先我们的问题是:产品包含了大量的服务,并且服务之间存在复杂的依赖关系,以拓扑的形式运行并相互协作,部署的时候需要手动解决整体的依赖,配制通信的协议和地址,重新部署新环境复杂度非常高。因此,我们希望有一种容器技术可以让我们构建产品所需要的所有的服务能够迅速快捷的重新部署,并且可以根据需求横向的扩展,且保证高可用性,在出现问题的时候可以自动重启或者启动备份服务。
目前有多种解决方案,考虑我们有私有云,亚马逊云以及物理机的几种部署方式,所以Docker作为解决方案的基础,在其之上选择合适的容器拓扑管理工具就成了主要任务,常见的解决方案有:
- Fleet,最早CoreOS集成的解决方案,基于Systemd管理操作系统上的容器,通过Systemd描述文件来表达容器之间启动的依赖关系,并且扩展支持Global, Single等类型的启动方式,跟系统契合的非常好,使用简单,处于比较高的应用级别,由于现在CoreOS跟Docker目前处于比较尴尬的关系,各自都想独立形成自己完整的生态系统,对其发展前景不是非常乐观;
- Kubernetes,Google根据自身很多年的容器部署经验所开源的容器管理解决方案,具有服务、节点、Pod等一系列不同层级的概念划分,通过自身的DNS服务发现可以实现很复杂的服务编排部署,对Docker兼容不错,不过略复杂些。
- Mesos+Marathon,Mesos是一个通用的资源管理调度框架,目前比较成熟的应用可以外接各种常用的计算框架,比如Hadoop MR, Storm, Spark等, Docker container作为一种long running的Framework也可以很自然的被Mesos所托管,Marathon作为Mesos之上的一种Scheduler比较擅长管理Docker这种长时间运行的“计算框架”,因此经常与Mesos绑定在一起作为Docker服务生产环境部署的解决方案讨论。
- Machine+Swarm+Compose, Docker官方提供的工具组合,特点就是与Docker engine的结合非常好,可以说能够无缝的替换原来Docker engine所使用的地方,最终所形成的容器集合可以作为单一的Docker容器来使用,从不同层面上实现了使用方法和调用的统一,缺点就是还不够成熟。
多种解决方案中我们优先选择官方提供的工具,一般来说官方提供的工具跟自己的原生服务结合的更好,也具有更长远的规划,在官方工具确实不足的情况下辅助以第三方的工具,因此初步我们决定采用Docker原生的工具Machine+Swarm+Compose辅助以Mesos来实现整个工程的部署,其中Swarm负责某一功能模块小规模的容器分配调度,Mesos负责整个集群最外层大规模容器资源调度,可以说以Mesos为主,Swarm为辅助,因为Mesos是比较成熟的资源管理框架,也有非常适合的调度引擎,Swarm还相对初步随着时间演进,也许会接管更多的调度。
简单介绍下Docker官方原生的工具:
- Machine,在一台机器上管理多个Docker 环境变量配置,方便快捷的启动本地或者远程的Docker容器,切换环境变量后,Docker command可以像在本地执行一样操作远程的Docker容器,依赖于其多种多样的Driver实现,降低了ssh登陆到各个机器去执行各种命令的复杂度;
- Swarm,将多个Docker host集成在一起作为一个虚拟的Docker host来使用,使用方式跟普通的Docker命令相同,通常可以用来实现小规模容器的负载均衡以及高可用的服务备份,其架构特点比较直接,就是在多台机器上安装Swarm agent与manager沟通,服务发现可以采用etcd/zookeeper/consul进行服务注册。这样确实实现了容器的分布式部署,不过只应对于相对简单的情况,调度算法也比较简单,适合于实现相对结构比较简单的Web应用,对于大规模的拓扑或者服务编排可能不够成熟;
- Compose,通过编写yaml格式的服务组织编排文件,我们可以通过一个命令来启动多个容器并连接在一起作为一个完整的服务来使用,通常来说面向的是开发和测试,默认多个容器都是在一个host启动的,不能作为生产环境使用,如果希望compose的服务可以在生产环境中使用,可以独立于测试的配置文件新建一个production.yaml, 结合machine和swarm来管理多个远程host并组织成一个虚拟的host来使用,这样一个compose包含的服务会部署到实际不同的机器,满足生产的需要。Compose通过link将不同容器的端口连接在一起,如果服务运行于不同的host,需要给这个Swarm集群设置共同的overlay网络,否则默认的bridge网络只能在单个host联通。
关于Docker网络解决方案的争论比较多了,CoreOS和Kubernetes都有自己的解决方案,前两者都是比较通用的PAAS工具,作为通用性的服务编排工具容器的具体实现可以是多种,Docker只是其中之一,而Docker libnetwork的解决方案过于底层,不适合作为通用的插件集成到Kubernetes或者CoreOS中,因此这两家都有自己CNI类型的解决方案,对于使用者来说我们不那么关心到底这个工具支持多少种容器,只需要知道Docker这种容器能够满足当前产品部署的需求就好,因此我们仍然以Docker的工具为主,尽管不那么通用,但是能够解决我们目前服务编排的问题。
官方的工具看起来很美好,解决方案也足够优雅和简洁,问题就是成熟程度,compose和swarm的结合仍然是在试验阶段,对于处于不同host的container,进行link仍然需要手动对整个Swarm集群设置网络,对于大规模或者复杂拓扑的部署工作量不小,因此我们借助于Mesos来做第一级的资源或者容器管理,其中第二级或者说小规模容器部署是可以在swarm中实现。
Mesos作为资深的资源管理平台,在Docker出现之前就已经被广泛利用了,基本上所有的主从类型的分布式计算框架都支持使用Mesos来做基本的资源分配调度,比如hadoop, storm,spark等等,同时Mesos的设计也可允许长时间运行的application, 不管是batch job, stream job还是普通的应用服务都可以接入Mesos来申请资源启动自身的容器。早期Mesos只支持LXC形式的资源限制,在Docker崛起之后Mesos也开始支持直接使用Docker容器来运行具体的计算框架,可以说二者既有竞争又相辅相成。说竞争是因为目前Docker自己的工具已经慢慢的可以替代一部分Mesos的应用场景了,只要机器上安装了Docker engine就可以无差别的管理所有主机,比如Swarm就可以组建简单的服务集群,管理容器在集群中的运行,同时也能够利用Machine来进行远程管理,说相辅是因为Swarm的设计是可以替换具体的调度后端的,默认情况使用自己的调度器在服务发现的基础上选择一个host来启动容器,通过配置可以选择Mesos作为其调度后端,将Swarm 作为跟Spark同等的Compute Framework来运行,这样Swarm就能够使用Mesos更加成熟和灵活的调度机制来管理容器,在此之上Compose就可以把编排好的服务运行在Mesos集群,可见Mesos和Docker结合的生态系统在当前阶段是比较和谐的。
这样,最终我们的解决方案就基本确定了,Mesos作为最基础的集群资源管理或者调度工具运行在所有的服务器上,Spark等计算框架不再独立部署,而是使用Mesos最初的LXC容器来运行,Swarm使用Docker容器通过Mesos来调度,Compose文件用来启动结合比较紧密服务堆栈,比如Tachyon集群,我们自己所开发的应用服务以及ACO集群也作为一个Docker服务堆栈在Swarm上运行。所以我们的Mesos集群上目前运行两种计算框架,Spark和Swarm,负责我们的应用和分布式计算的部署,具体的应用和服务编排都是在Compose中完成,个别复杂的应用需要手动去处理关联关系,依然是以Docker的形式运行在Mesos中。
Mesos可以把我们的机器聚合在一起作为一个机器来使用,不管是我们的应用还是分布式计算的任务,都直接提交给Mesos来进行调度,减少了对服务器的垂直划分,不存在Spark的集群, Hadoop的集群等概念,Spark或者Hadoop的job直接在Mesos的slave中分配资源并运行各自job相关的Executor, 运行结束后释放资源,就像Spark没有存在过一样, 因此从更高的角度看Mesos的Framework其实就是一个调度器加一个运行时的处理流程,不用再需要Spark或者Storm等框架的Standalone 模式自己来处理调度,只需要使用Mesos的API,实现自己的scheduler和具体启动停止运行过程的Executor就好, 而对于我们自己的应用如果要作为Framework存在也需要实现对应的Scheduler和Executor, 不过可以利用现成的比较好的调度器比如Marathon来托管我们的应用,减少开发Framework的工作量。使用Mesos这样的好处是资源的利用率更高, 因此我们在也不需要除了Mesos之外的long running 集群, 即使有Long running的服务,也是在Mesos分配好的容器内运行。
Framework = Scheduler + Executor
Mesos的安装过程稍微有点服务,虽然Docker镜像可以减少Mesos的部署复杂度,但是这样就存在了两层容器, Mesos在Docker 容器中运行,而Mesos里边的任务也是在自己的Docker容器里运行,如果有些长时间运行的任务需要暴露出端口跟外界交互,就需要先Expose port到Mesos slave级别的容器, 然后再Expose到最外层的物理机,复杂度增加且对性能有损耗,因此我们最终还是倾向于在物理机上部署Mesos, 只保留一层Docker容器不推荐嵌套,而且有了Mesosphere的DCOS系统,在AWS上部署Mesos就比较简单了。
Mesos看起来很完美,那我们为什么还需要Docker容器呢, 直接使用LXC标准Linux kernel支持的容器不就可以了,在这个解决方案中我们期望所有运行的应用或者分布式计算框架的任务的Executor都是在Docker容器中运行,也是因为Docker杀手级的功能,一个Docker容器就像一个集装箱,里边包含了需要运行一个服务或者任务的所有的依赖条件或者配置,都可以根据需求自身灵活的修改,并且一次装箱随处运行,不用关心外在环境,举个例子假如直接使用LXC来运行Spark的某个任务的Executor,需要提供Spark jar包的地址,相关的配置集成到ExecutorInfo中才能运行,而如果使用Docker container就简单很多,Spark executor运行需要的信息都在某个Docker image中,Mesos slave只要调用Docker client启动某个镜像就足以运行一个Framework的某个任务,任务的执行在Docker 容器中。对于我们自己开发的各种服务同理也是组织成镜像最终在Docker容器中运行, Scheduler依赖Marathon就可以。