方进 分布式实验室
一切要从2013年4月开始说起,当我4月份从委内瑞拉回来之后立即投身到国内一个运营商的大型后端建设项目的尾声中(项目历时3年多,当时已经接近尾声),这个项目涉及100多台主机,包含数十个集群,除了传统的WEB应用外,还用到了流程引擎、ESB、规则引擎、搜索引擎以及缓存和日志,是当时比较复杂的体系结构(当然不能跟现在的云平台相比,但在项目开始的年代这还是一个很不错的架构),整个项目当时一两百号人占了局方整整一层楼十几个办公室。
我到了项目组之后成为了一个小组的小头目,管个四五个人,小组美其名曰“平台组”,干的都是打杂的事情,包括编译、打包、部署,日常监控以及系统优化等工作,说起来简单,做起来还是很复杂的,当时所有的工作基本上是靠人工的,可想而知,100来台机器的环境一台一台的部署环境,还得靠人工监控,手工检查,四五个到处救火忙得不可开交,当时我虽然还不知道CI为啥物(压根儿就没这个概念),但也下定决心要改变忙乱的状态,累一点不要紧,但是累得跟狗似的还干不好那就白辛苦了。
在2013年的4~8月份,我们主要研究的是自动编译、打包和发布,采用的基本方式是各种脚本,包括windows下的批处理bat、Linux上的shell甚至Python,虽基本上完成了自动从SVN取代码、自动编译、自动打包以及将应用发布到WebSphere上的这些工作(如下图):
但也明显存在一些问题:
自动执行靠的是Windows任务计划,执行过程、执行情况只能通过检查脚本执行时写的日志文件,不直观。
代码只作了编译,没有做代码走查,对代码质量的提升作用不大。
发布过程利用IBM提供的wsadmin脚本,只能进行全量的发布,发布过程较长。
9月份之后,项目基本稳定后我也离开了项目现场,但自那之后对这块工作更加着迷,我从项目现场回来之后也组建了一个科室,还是四五个人,当时我查阅了一些资料,尤其是看到了一本书《持续集成,软件质量改进和风险降低之道》,从此学到一个名词:CI(持续集成),一发而不可收拾,逢人就鼓吹CI。我组织科室人员一起研究了CruiseControl、Apache Continuum、QuickBuild、Hudson等业界CI常用工具,最后决定以Hudson为框架来逐步实现CI.。一早采用的是Hudson,后来为便于作二次开发切换到其社区版本Jenkins上。
Jenkins提供了一个管理界面,并且有丰富的第三方插件,支持定时任务,支持从SVN取代码,支持Ant编译和Maven编译(我们产品编程框架逐渐从ANT转向maven模式),支持向tomcat、JBoss、Weblogic和WAS发布应用(Jenkins的WAS插件不支持集群模式,我们仍然沿用了wsadmin脚本),支持用PMD、Checkstyle、findBugs进行代码走查并以图形化方式展现走查结果(包括趋势图和结果详情),支持调用Windows批处理bat、Linux的Shell等。
在采用Jenkins框架的基础上,我们作了一些二次开发,实现了:
-
根据任务单增量从SVN取代码(有一些奇葩的项目现场要求挑任务单升级,因此我们修改了jenkins的svn插件以支持这种需求)。
支持增量编译(采用两个Jenkins的JOB,一个做全量编译,作为首次编译并产生一个jar包给增量编译使用,此全量编译JOB只使用一次;另一个JOB就引用全量JOB产生的jar包,只对变更(或新增)的代码编译产生class等文件,并将它们按部署目录放好以便于作增量发布,同时将这些class文件再打入到全量JOB下的jar包中以备下次增量编译使用)。
支持增量发布,通过调用lftp脚本实现快速的应用部署(在比较了cwRsync、unison、wget、lftp、ftpsync、csync、Syncrify、DeltaCopy、tar、bacula等工具后,最终lftp胜出,我们采用:
lftp -c 'open -e "mirror --allow-chown -x vssver.scc -R --parallel=10 --use-pget-n=10 --log=%LOG_FILE% %LOC_DIR% %REMOTE_DIR%" sftp://%USER%:%PASSWORD%@%IP%'
这样的脚本来进行增量发布,将编译后的结果与部署环境上的进行自动比对更新)。支持基于Ant和基于Maven的代码走查,编写Ant脚本和Maven脚本以支持PMD、Checkstyle、findBugs进行代码走查(由于jenkins中代码走查插件生成界面时会消耗大量系统资源,对机器性能影响很大,后面我们改成了通过脚本方式生成并将走查结果打成压缩包发邮件给相关人员)。
支持基于Maven的代码的单元测试(采用TDD编码方式)。
支持自动化测试(调用ZTP,ZTP是我司自产的一个自动化测试工具,支持自动化脚本录制、回放等工作,其工作原理与robotframework比较类似,ZTP工具支持批处理脚本调用,故可以集成到jenkins中)。
当时,我们还想在Jenkins上集成更多的功能,包括:
改进websphere-deploy插件,支持界面部署WebSphere应用包(这个计划后面搁浅了,主要是脚本方式已经能支持绝大部分WAS集群的部署了)。
应用环境迁移,通过将应用环境迁移过程自动化为Jenkins中的任务,实现应用环境迁移过程的自动化和可视化(目的就是想实现研发、测试及生产都是一套环境,测试通过后的环境能直接迁移到生产上去,当时只是做运维的一种本能的想法,但也由此引发了对Docker的关注)。
业务监控系统相结合,形成流程化的跨多个Jenkins任务的、整体的应用环境部署自动化和可视化,为将来生产环境部署的自动化和可视化作准备(曾经研究过一段时间jenkins的FLOW插件,当时FLOW插件的版本还比较低,功能还很弱,引入第三方的工作流工作量会比较大,而事实上这种流程化的编译部署过程实用性也比较低,这事就慢慢搁浅了)。
目前我所在的产品线所有的项目都已经采用Jenkins进行编译、打包、代码走查及自动部署到测试环境和准生产环境(运营商项目的生产环境发布后面会逐渐由我司自主开发的另一利器“云应用管理平台”来支持,后面还会讲到)。
前面讲了,关于应用环境迁移的想法引发了我对Docker的兴趣,实际上这时已经是2014年的6月份了,于是就跟一些同事自学鼓捣一下,当时Docker 1.0才刚刚发布,当时也就把官网的例子都做了一遍,参考官网作了Hadoop的镜像,自己又作了WebSphere的镜像,搭建了Registry,断断续续的作了一些东西,算不上很深入,而Docker本身也在不断发展,感觉隔几天就有一个新版本发布出来。
Docker大潮来势汹涌,到9月份的时候,我一开始说的那个巨大项目的运营商中有个技术专家提出了要用Docker来作应用发布平台,当时简直是不谋而合,于是有了一个项目,也就可以明正言顺的进行Docker研究了,不过既然已经是一个正式的项目了,那光有几个爱好者是不够的,需要有正规军了,于是请出了公司技术委员会下属的一个研发团队,大约有六七人,也就是“云应用管理平台”的开发团队,一起进行相关的研究。给生产环境用的发布平台跟我们前面讲的用Jenkins作的自动部署还是有些不同的,生产环境上版本的发布一般是有严格限制的,包括版本要求、时间要求(升级时间、故障率和故障解决时间)等,这一点是与现在的互联网企业升级自家的系统是完全不同的。
“云应用管理平台”围绕着Docker进行了大量的开发工作,制作了主机管理、容器管理、集群管理、版本计划管理、版本执行管理等等,其架构如下图:
1)Jenkins打包镜像
自动获取SVN代码版本
使用Dockerfile打包镜像,并自动上传到Docker Registry。
2)集群配置
应用基本信息配置
集群应用绑定
环境变量配置
固定端口号配置
3)制定发布计划
选择镜像版本(Docker Registry API获取镜像列表)
选择主机,并设置启动的容器实例数
4)执行发布计划
使用Docker Java API连接docker daemon启动容器
记录容器ID
5)容器管理
主机检测(能检测主机上是否安装了docker,如果没有可以自动安装Docker)
容器节点增加、缩减,用户选择的应用镜像版本和实际运行版本一致时执行伸缩
容器状态监测
一开始我们在测试环境上将所有容器都放在一台主机上,测试过程很顺利,但在移到准生产环境上时,由于要模拟生产环境只能将容器部署到不同的主机上,这时候就发现了一个奇怪的现象,应用之间调不通了,这里要说一下,我们应用程序是由20多个服务组成的、通过Dubbo【阿里提供的一个服务框架】作为服务总线串连起来的,Dubbo提供了一个方便的服务发现机制,各个服务(称为服务提供者)只要向Dubbo注册中心注册过,注册中心就会将服务的地址发送给同样在注册中心注册的服务调用方(称为消费者),之后即使dubbo注册中心挂了也不影响服务的调用。
当服务提供者部署在容器中时,这时候发现其在Dubbo中心注册的是容器的IP地址,而对处于另一个主机上的消费者来说这个IP是不可访问的,我们当时也参考了多种方式,想让消费者能够连接上服务提供者的IP,查阅资料总结起来大概有两种做法:
设置容器的IP与主机IP在同一网段内,使容器IP可直接访问【会占用大量的IP地址,且IP会限制在同一网段,在生产环境中往往不可能】。
通过复杂的iptables路由规则,通过多层桥接方式打通网络【此法是可行的,也是我们今后要考虑的,但当时一堆开发人员对网络这块都不是太熟悉】。
考虑到当时公司技术委员会下属另一个研发团队正在做dubbo的研究和改造,于是拉他们进来讨论,结果他们说这个很容易解决,由于主机之间是连通的,而容器在创建时也映射了主机和端口,只需要在服务注册时注册的是映射的主机IP和端口就可以连通了,该研发团队的效率很高,讨论的当天就给出了实现,考虑到局方要求严格管理容器和主机间的映射,我们将主机IP和端口作为环境变量在容器启动时传入【扩展了dubbo protocol配置,增加了两个配置项 publish host、 publishport,对应主机的ip port,并且在注册服务时将主机的ip port写到注册中心】,果然解决了这个问题。
当然这是一种特殊情况下的跨主机容器连接方式,更为普遍的方式目前我们正在讨论当中,基于ovs的连接方式是正在考虑的一个方案。
目前我们对Docker的使用还比较初步,虽然基本满足了项目的要求,但考虑到将来云平台要求自动扩展、服务发现,这些还有待我们进一步研究。