持续部署,并不简单!

持续部署,并不简单!

感谢 @常新居士 投递此文 】

这几年,持续集成随着敏捷在国内的推广而持续走热,与之相伴的持续部署也一直备受关注。自前两年,持续交付这个延续性概念又闯进了国内IT圈,慢慢开始在社区和会议中展露头角。许多不明真相的群众跟风哭着喊着要“上”,而许多前CI的半吊子玩家换件衣服就接着干,有的甚至衣服都来不及换……。国内的这些土财主如果不巧请了某些所谓的战略家,除了建了一堆持续集成环境,以及每天嚷嚷着要这个要那个,混乱的状况在根本上没有得到改善。本文无意费力探讨持续集成和持续交付的概念,而是打算谈谈对于大型软件企业,以持续集成为基础实现持续部署(交付)时,所要面对的问题以及可行的解决方案。地主老财们,夜黑风正猛,山高路又远,注意脚下……

And God Said, Let there be light: and there wa— GENSIS, Charpter 1, King James

一、起步

先来讲个故事……

几年前,一对留美的夫妇通过朋友找到我,让我帮忙在国内组建一个开发团队,该团队负责为其开发一款基于社交网络的客户关系管理软件,(暂且称之为项目A)。这个项目除了尚不清晰的需求范围和很紧的期限外,作为业内人士的老公Richard根据眼下流行的软件开发过程还提了诸多额外的要求:

  • 功能要及早交付(以便拿去和潜在的投资人洽谈)
  • 功能在部署到生产环境前要先部署的一个测试环境(Richard要试用后给予反馈)
  • 功能必须经过测试(长期作为软件外包的甲方,对质量要求严格)
  • 要减少后期维护的工作(美国人精贵,少雇一个是一个)
  • 支持协同开发(以便维护人员及早介入)
  • ……

这正是持续集成所要解决的典型场景。针对Richard的要求,我们只要建立一个基于Hudson(现在叫Jenkins)+Maven +SVN 的持续集成环境(再加上持续集成所要求的测试和过程)就可以很好地满足上述要要求,此方案的结构如下:

对于上述方案,让我们近距离看看各个服务器的内部情况,以及人员在这种方案下的分工协作:

我们先谈谈上面的图中涉及的一些概念性问题:

1.1)编译时依赖运行时依赖

从字面上不难理解这两种依赖的类型。但要注意虽然编译时依赖常常也是运行时依赖,但并不能推断出一方必然是另一方。比如,在开发的过程中需要某些提供API的Jar包,而运行时可能是具体API实现的Jar包。再者,被依赖的包会有其自身的依赖,因此,项目对这些包产生间接依赖(运行时依赖),依此类推,最终形成一个依赖树。当项目运行时,这些依赖树上的包必须全部就位。

Maven在POM中通scope来界定依赖的类型,从而帮助开发和运维人员摆脱手动处理依赖树的工作,然而运行时所依赖包最终是要安装到生产环境的,这部分工作Maven并不能自动完成。因此,一个常用方式是将运行时所依赖的包拷贝到项目文件中,比如Java Web应用的WEB-INF/lib,然后将项目总的打一个包。在安装项目包后,修改环境变量,将这些包所在的路径加入相应的环境变量中,如ClassPath

再看个例子,现代的操作系统和其它系统框架都考虑到了运行时依赖树的处理问题,比如Ubuntu的apt-get,CentOS的yum,Ruby的RubyGem,Node的npm等等。

1.2)依赖时的复杂度

项目除了对程序包的依赖,对于运行环境也有些具体的要求,比如,Web应用需要安装和配置Web服务器,应用服务器,数据服务器等,企业应用中可能需要消息队列,缓存,定时作业,或是对其它系统以Web Service方式暴露的服务。这些可以看做项目在系统层面对外部的依赖。这些依赖有些可以由项目自行处理,而有些则是项目无法处理的,比如运行容器,操作系统等,这些是项目的运行环境。

总之,依赖的复杂度主要有两个:

  1. 依赖包间的版本兼容性问题。兼容性问题是软件开发的恶梦
  2. 间接依赖,或多重依赖问题。这个问题可以类比想像一下C++中的多重继续种出现的很多问题。
比如:A依赖于python 2.7,A还依赖于B,但是B却依赖于python 3,而Python 2.7和Python 3不兼容。这是依赖中最恶心的事。
1.3)任务分工

由于项目简单,因此并不需要专门的运维人员。以一个100人左右以交付为主业(恩,就是做外包)的公司为例,由于没有任何历史项目和代码的拖累,且各个项目间也没有任何关联,故而只需要配备一个IT支持人员进行资源方面的管理:分配机器,报修,初始化系统,分配IP地址等。各个项目的运行环境、数据库、开发环境等都由具体项目的开发人员手动完成。 环境出问题怎么办?很简单,凉拌——重装系统。实际的运行效果不错。

1.4)自动化部署

由于Hudson这样的持续集成环境提供了自动编译(定时或触发式)的功能,而且可以在编译过程中提供了一些扩展点,因此通过提供一个部署用的脚本,就可以非常容易实现简单的自动化部署。

毫无疑问,持续集成就是敏捷的魔法药,它见效快、副作用小、业界的争论少。每每运用在混乱的项目中时,几周内项目就开始持续的产出经过测试的功能。对于独立项目,以持续集成为中心的持续部署绝对是不二选择。

但是,我们有没有想过,这会是一个自动化部署的通用解决方案吗?持续集成应该位于持续交付的中心吗?

二、困境

回到我们的故事:项目A上线两年后,运营业绩不错,投资人第一轮注资后,Richard的公司进行了扩张,他们对项目进行了重构,而且随着用户数量的增长,公司分别在美国、英国和日本等地建立了运营中心,并且对亚洲市场进行的定制功能开发(项目A+),接下来,公司又投入开发了团购系统(项目B)。在获得了新一轮投资后,各条本来比较简单的业务和功能线上越来越复杂,需要不断地细分,于是公司再度扩张(开发人员达到了300人,国内200多人,而运维团队主要在美国),随后又为项目A/A+的高级用户开发了问答系统(项目C)。目前,他们正准备开发手机系统。 看看下面的图,公司增长的过程中,整个项目环境也变得复杂。(注意,这里是一种逻辑结构,而在物理层面项目B和项目A的生产环境可能部署在相同的机器上)。