周良伟
网易云信系统架构师
负责云信IM平台的架构设计和服务器研发团队
作者简介今天和大家分享的主题是如何用Docker/Ansible来做轻量私有化的技术方案。首先,简单介绍一下所谓轻量私有化到底是怎么回事。
我们这几年在做公有云平台,过程中也接触到很多客户。有许多客户提出来说,你们的云平台服务很好、线上也很稳定,但希望能把云平台在自己的环境里部署起来,于是就有了今天要分享的题目。
今天分享的内容,大致分为三个部分:第一部分,做轻量私有化面临什么样的问题;第二部分,面对这些问题,我们如何去解决,即方案;最后是关于实践的部分。因平台没有办法做实时Coding的交互的过程,所以我简单录了一段视频的教程,大家可以对照这个视频看一下我们具体是怎么做的。
因为这个过程会涉及到代码的部分,所以我把sample的代码已经提交到GitHub,后面跟大家分享一下,大家可以对照着一起看。
●●●
问 题
大概2011年左右,我刚开始参加工作的时候,其实很少听到XX云的说法,大家讲的更多的是分布式系统。那个时候分布式系统非常火,我第一个工作就是一个分布式的文件系统。
分布式系统的分类
有工作经验的同学都知道,我们说的云其实就是一种规模化的分布式系统,这里我为分布式系统简单做了一个分类,当然这里的分类不一定准确,只是我个人对分布式集群系统的理解。
1.面向特定场景的服务集群/云
这个场景是什么?比如面向存储场景,我们知道有HDFS/HBase系统;面向计算型,MapReduce/Yarn/Spark/Storm这样一种计算平台;还有一些像Mesos这种资源调度型平台。这些就是面向特定场景的云。
除了这些大家耳熟能详的平台,在网易内部,我们有DDB(分布式数据库)以及RDS、NOS。NOS是一个分布式的对象存储服务,也是属于特定场景的服务集群,或者称为特定场景的云。
2.面向通用场景的资源型云服务
比如现在非常热门的OpenStack这样一个IaaS层的云平台,或者像Ceph这样面向存储的云平台,其实它们都属于通用型。
像Ceph就是把存储资源云化,还有像Vita-Line这样的网络虚拟化的一种云服务,我们把这些称为面向通用场景的资源型云服务。
3.独辟蹊径的容器型的服务
这和今天讲解的内容有关系了,独辟蹊径的容器型的服务其实就是Docker容器+K8S,对于Docker这种容器进行一个资源调度的云平台。
4.最近非常火的FaaS平台,就是函数及计算的云平台,这个和今天的内容不太相关,就不发散开了。
所以,有那么多不同类型的云服务,我们针对专门的场景提供PaaS层的平台服务,但我们要把这样一个平台服务搬到一个企业内部或者一个独立的环境里面。如果把整个云搬进去,可想象这个代价会非常大,而且从复制性上来说,这不具有非常高的可复制性。
在线上运营那么多年的云平台后,我们也总结了一些经验、积累了一些技术栈,开始思考能不能用更加简单的方式实现轻量级私有化服务。
轻量私有化面临的问题
所以我们要的模式是什么样的?这是我们的服务面临的问题:
1.单机不够用。在任何一个环境里,没有单体的程序或者单机软件可以满足一个应用场景,这种产品非常少。
2. 规模化不高。我们规模化还没有公有云上做得那么高,在私有云环境里,我们的用户大概是5万或者10万的量级。
其实对于现在的企业来说,5万到10万算是非常大的规模,但和互联网相比,还是远远不够的,所以企业私有化经常面临的用户规模就是几万这个量级,这个规模化程度要说高,也不是那么高。
3.技术栈太复杂。我们公有云平台涉及到的模块非常多,在这些模块里,每个服务实现的技术栈是不一样的,语言是不一样的,依赖的软件和网络环境也是不一样的。
面对这样一个技术栈,我们如何把这个技术方案尽量简化,做到可复制性,这就是我们面临的挑战。
刚刚说整个技术栈太复杂,我们的集群也太复杂,规模没有那么大,但也不小。
这里以网易云信为例简单看一下。下图是我们现在涉及到的一些服务架构,当然这里面没有列全,但是主要模块都已经列出来了。
从这张图可看到,我们的服务涉及到非常多的不同的服务模块,比如说负责长连接的服务,负责做业务调度的服务,包括一些Web类的API的服务,还有对象存储、文件上传、文件下载以及媒体服务,还有一个分布式的文件系统,再往后还有处理消息漫游的服务、推送的服务等各种各样的服务。
在这些服务里面,涉及到的技术栈不只是Java,有些其它的比如消息队列,我们今天演示的就是RabbitMQ ,它的技术栈是在Erlang 层面,和我们之前熟悉的Java是完全不一样的东西,像数据库或者说缓存服务,那又是别的技术栈的东西。
所以,从这张图里我们发现,想要解决集群的快速部署 ,我们面临的问题就是:这么繁杂的一个集群,我们要怎么通过技术让它变得简单。
于是,进入今天的重点分享环节,轻量集群部署应该怎么做?
●●●
方 案
轻量集群服务部署
如何部署轻量集群服务?我们总结了一下,有三句话。
1.标准OS提供计算资源。
因为是标准OS,所以能够兼容物理机/云主机这样不同的环境。
2.Docker实现程序包封装和运行时资源隔离。
前面说到,我们整个集群的搭建过程中,服务模块是基于不同技术栈上来做的,可能有的是Erlang,有的是C,有的可能对 Linux的内核有要求, 比如有的要求4.x的版本,有的要求2.x的版本。
这样不同程序包的整个依赖环境会非常复杂,对此,我们可以用Docker的形式来将这些程序包全部封装起来,同时还能在运行时实现资源隔离, 我们对CPU和对内存都去做限定。
3.用Ansible实现集群分布式部署。
为什么要在这里用Ansible?我在前面说Docker时,很多同学自然而然会想到,那你用Docker,是不是应该用容器云、K8S呢?非常对,本身容器服务或者微服务,其实非常适合K8S这样的容器。
但在轻量私有化这样一个限定的场景里,如果为了在几台机子上部署一套云平台,专门去搭建一个K8S,代价是非常大的。因为本身K8S需要做镜像库、资源调度、做控制节点,对成本的要求本身就比较高。
那么既然要把这个东西脱离,我们就必须要有一个可以替代的东西,在这里就是Ansible。
Ansible本身是用来做协同的多机同步控制处理的框架,可能做运维的同学会比较了解,它是大家日常做集群化的操作中非常有用的东西,我们就把它引入进来,解决我们的问题。
常用的技术栈工具
1.Docker
Docker是什么?Docker现在被称为Moby,2017年4月在DockerCon上,官方做了这样一个动作,后面就被商业化了,这里说到的Docker是Docker CE或者叫Moby。
Docker是一个开源的应用容器引擎,让开发者可以打包它们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。
那么在Docker里面有几个术语,这里简单说一下:
Docker镜像。Docker镜像非常像管理源代码用的GitHub,了解GitHub的同学都知道GitHub上可以用来做版本控制,对我们代码可以做提交、开分支,可以做commit, 各种各样的操作,非常简单、非常快速。
镜像仓库。Docker的镜像仓库类似于GitHub这样一个的产品形态,可以帮助我们管理镜像,那么Docker镜像是把我们所有程序包和我们的应用以及程序包依赖的依赖包全部打包成一个镜像的形式、一个Image用来管理,我们可以理解为,它也是一个代码,它是一个可执行的、二进制的代码。
Container实例。Container实例就是把一个Docker镜像给跑起来,在运行过程中,我们称它为一个容器实例。
数据卷Volume。我们可以把它理解为一个目录,或者磁盘上的一个空间。
因为Docker本来用了沙箱机制,所以它在运行时所需要用到的这些磁盘、本地文件或者产生的一些数据,如果在不做任何配置的情况下,它是在这个沙箱里面的。
而通过数据卷的形式,我们可以把数据加载出来,加载到宿主机上,把宿主机上某个特定的目录,或者是某个网络上特定的目录,加载到容器里。在容器里面这个数据卷就可以当作一个硬盘一样,快速使用。
Docker网络。Docker网络是用来把实例和宿主机之间做一个相互的隔离或者做一个桥接。同时在Docker网络里面,比如我们在容器内部,开一个TomCat服务是8080端口,但我们宿主机上面同时运行的有三个实例,大家知道3个8080端口不可能同时在一台宿主机上开始,如果要去改容器镜像,那有点复杂。
通过Docker网络,我们就可以把这个端口直接映射出来,比如把3个容器的8080端口分别映射到宿主机上,分别为8080、8081、8082,它能够做到快速的网络映射转换的过程。
Dockerfile。Dockerfile是用来描述Docker镜像的整个构建过程,可以理解为Docker的一个代码,通过这个代码,可以构建出一个二进制的镜像。
2.Kubernetes
讲到Docker的话,必然要讲到K8S,K8S全名是Kubernetes,它是Google开源的容器集群管理系统,用Go语言开发,提供应用部署、维护、扩展机制等功能,利用K8S能方便地管理跨机器运行容器化的应用。
其主要功能如下:
使用Docker对应用程序包装(package)、实例化(instantiate)、运行(run)。
以集群的方式运行、管理跨机器的容器。
解决 Docker 跨机器容器之间的通讯问题。
Kubernetes 具有自我修复机制,比如Docker在这个宿主机上,这个容器死掉之后,可以在另外一个宿主机上将这个容器迅速地拉起来,保证服务的计算容量和高可用,这个非常重要,使得容器集群总是运行在用户期望的状态。
3.Ansible
然而K8S比较重,引入轻量方案中不合适,于是我们找了一种替代品——Ansible。
Ansible是一种自动化运维工具,基于Python开发出来,它集合了众多运维工具的优点,可以实现批量系统配置、进行程序部署、运行命令等功能。
Ansible 是基于模块工作的,我们知道Eclipse也是基于模块,基于模块代表它可以扩展,社区也贡献了非常多的模块来扩展Ansible的能力,所以Ansible现在是非常强大的。
它本身没有批量部署的能力,但模块可以实现非常多的功能,主要有:
Host inventory:用来制定需要操作的被控机的列表,它可以在配置文件中快速修改列表,来调整需要操作的环节,下面实践的部分会讲到这些。
连接插件Connection Plugins:负责和被监控端实现通信,被控机无 Agent,所以不存在初始化,或者Agent下线后导致被控机没有办法去执行命令这样子的问题。
Host inventory:用来制定需要操作的被控机的列表,它可以在配置文件中快速修改列表,来调整需要操作的环节,下面实践的部分会讲到这些。
借助于各种模块、核心模块、Command 模块、自定义模块来实现最多的功能。
Playbook:剧本执行多个任务时,可以让节点一次性运行多个任务,可以帮助我们快速集群化部署的效果。
方案呈现
简单给大家科普了一下我们需要用到的三个技术栈上的工具,接下里看一下网易云信把这么多的模块、这么复杂的架构全部打包起来,会变成什么样子。
如图所示,最底层就是一个OS层,在OS层上面运行的是一个一个Docker容器,这些Docker容器运行状态需要被监控,所以我们在OS层的宿主机上部署了MetricBeat,它是 ELK中用来做监控的Agent,可以帮助我们监控各个容器是否正常。
那么在OS层中还需要解决的问题是什么?一是权限的管理,比如主机和主机之间需要互通;二是基础性能管理,比如文件句柄数是否开够了,还有文件目录或者数据盘的权限是否开放出来,包括网络层的一些参数需要进行一些性能调优;三是Docker环境的初始化,还有Supervisor的服务,因为我们任何一台机器或任何一种服务都有当机、故障重启的风险,一旦服务器重启,你需要手动拉起服务,或者它挂掉以后没有一个自动恢复的机制,这个在高可用上可能体验不是那么好,所以我们引入一个Supervisor,通过Supervisor作为一个后台,帮我们把这些服务保持一个健康的状态。
最重要一点是,在OS层还需要做一个CPU和内存资源的隔离,这个也是借助于Docker本身基于Cgroup做资源隔离的能力实现的。
在OS层之上,就是Docker容器,我们在每个Docker容器里面写的时候不是单个的进程,而是我们用的多个进程,因为我们理解任何一个服务其实都有可能不是一个单体程序,有可能是一个主程序加上N个辅助程序来完成,所以我们在这里面把N个辅助程序通过Supervisor关联到一起。
像图上这个示例里面有几个程序,一个是我们的主进程,就是NIM Proc,还有处理日志用的Rsyslog 或者Filebeat, Filebeat也是ELK里面做日志采集用的,还有Metricbeat,这是用来做指标采集的,把这些主进程或辅助进程都通过Supervisor管理起来。
在Docker里还要解决的一个问题是对依赖环境的差异化,比如:当前需要执行的程序需要JDK 1.7的版本,那么我们可以把1.7的JDK封装到这个镜像里去,对于另外一个程序或者另外一个软件如像ELK里面的ElasticSearch,它需要的JDK版本是1.8以上,如果我简单地把进程跑在宿主机上,可能需要去安装多个JDK的版本,然后在应用里面去单独指定,这个过程可能会比较复杂。
如果没有软件冲突还好,一旦你依赖的是内核层或者底层库,就会产生软件冲突,代表你不能在同一个机器上运行。这种情况就可以用Docker去达到这样一个目的。
再往上层就是一个个模块被我们打包成一个个镜像,这里一个个方块代表着单个的镜像,这些镜像都是用Dockerfile来描述,通过Dockerfile我们可以把这些镜像一个个程序化地构建起来,构建完了之后,我们要把镜像跑起来,跑起来之后变成了容器实例,一个个实例之间相互组合之后变成了整体的服务。把这些镜像拉起来变成容器服务,这就是Ansible要做的事情。
我们看到最上层有非常多的文件,这些文件代表一个个YAML文件,代表了一个个Playbook。我们看到这里一个个Playbook,比如DB_cluster,这个脚本是专门用来处理构建DB数据库的集群。又如NOS.yml,NOS是我们网易对象存储服务的简称,它通过这个脚本,可以在多机上部署起来一个对象存储服务。
其它的也是类似这样的服务,可以通过脚本化的形式管理起来。通过这张图大家也可以看到我们整个技术方案,通过借助这样一个工具,把它做到一个可复制的程度。
●●●
实 例
下面就是实践的部分:先简单搭一个RabbitMQ的集群, 我们需要把单节点给布置起来,需要决定这个MQ的节点的角色,然后做不同的配置、cookie 的同步,做各种各样的事情,最后还需要做一些用户的初始化,整个部署的过程非常复杂。
对于一个经验丰富的运维人员来说,部署一个集群也需要花费一段时间,过程中有可能踩到很多坑。我们希望部署一个MQ集群就像把大象关进冰箱一样,只需要三步:打开冰箱门——放进大象——关上门。这个就是我们的目标。
我们今天实践的部分——怎么快速搭建一个MQ集群,这里已经把相关代码放到GitHub上:https://github.com/williamleung/lite-deploy-sample。
具体过程也已经录屏,大家可以参考(iframe 代码嵌入文章内):
https://v.qq.com/x/page/z05343zmcdj.html?__t=1&ptag=1.qzone&_out=101
参照这个代码怎么用Docker和Ansible把整个过程自动化处理,这里还涉及到一个多机部署的问题,在示例代码中,使用Vagrant来创建虚拟机模拟了一个多机部署的环境。
Vagrant今天在分享里没有讲太多内容,感兴趣的同学可以自己了解一下,简单来说,Vagrant可以帮助你用代码的形式快速维护虚拟机的集群环境。
实践过程中有一个开发部署的模型,这个模型分两个部分角色,研发人员做的工作和部署人员做的工作,完成后帮助大家解读示例的代码部分。
研发人员主要负责开发打包镜像、开发部署脚本以及镜像和脚本的分发;现场实施人员主要负责创建服务器、目标环境初始化以及集群部署。
最后提供给大家几个参考资料,以供大家学习:
Vagrant地址
https://app.vagrantup.com/boxes/search
网易云的容器服务地址
https://c.163.com/hub#/m/home/
Ansible资源库
https://galaxy.ansible.com/
Daocloud容器加速镜像地址:
https://download.daocloud.io/Docker_Mirror/Docker
●●●
问答环节
【问题】:Docker 是必然的趋势吗!
答:是不是“必然”的趋势我觉得每个人都有自己的看法,其实对于任何一个技术或者工具,我觉得都有自己的应用场景,如果能结合自己的业务把Docker用好更加重要。
比如在今天分享的这个case里,我们更加关注的是Docker对应用程序及其依赖库进行封装的能力,通过dockerfile可以将其固化下来支持不断迭代;其次就是容器支持在运行时资源隔离,帮助我们更好地利用宿主机上的资源;但借助K8S实现的容器服务调度的功能就没有用到,这也是我们基于自己的业务场景做出的选择。