在前文《依赖管理》中,咱们讨论了如何在代码变得庞大,组件增多的状况下,作好外部库和内部组件依赖管理,从而提升构建效率。能够应用的实践包括:一次生成,屡次复用;创建统一依赖库,外部依赖库可使用像Maven或Ivy这样的工具进行统一管理;对架构进行调整,使一个大的代码库分红多个组件;每一个组件有本身的持续集成体系;对多个组件作持续集成。然而,解决一个问题后,总会有另外一个问题等在那里,须要你来解决。此次Joe的团队遇到了部署问题。数据库
星期一早上,Alice一进办公室,就看到一脸倦意的Joe坐在椅子上,喝着咖啡。浏览器
“今天怎么来得这么早?看样子,你没睡好啊?”Alice问道。服务器
“固然啦,昨天晚上我就来了。”Joe无精打彩地回答道。网络
“怎么啦?”架构
“还不是由于新版本上线出了点儿问题”,Joe说道。“看来咱们要把部署这件事好好讨论一下,再这样下去,不仅我要来,大家也要和我同样啦!呵呵!”app
当天下午,Joe邀请了运维团队的主要负责人Tom和Steven,召开了一个关于部署问题的讨论会。运维
Joe说道:“先请运维部门的Tom介绍一下上周末的新版本上线过程和发现的问题吧。”ide
Tom描述了上线部署全过程。svn
“上周末上线部署时出现的状况是:在本次部署以前,咱们的集群中,有两台机器因HotFix,其程序配置被修改过,与其它机器不一致。所以,该机器上的部署失败,致使部分服务不可用。运维人员查了很长时间没有发现问题,星期日打电话把Joe叫来帮助咱们查问题时,Joe才回忆起有那么一次HotFix,但当时负责的运维人员已经离职,没人其它运维人员知道这件事情。”Tom说道,“咱们对问题进行了分析,认为应该增强咱们的上线流程管理,对于那种HotFix也应该发起一个审批流程,而且在该流程中不但要主要负责人审批,并且要对相关人发出周知通报。另外,咱们的运维人员应该对上线单进行严格审核,并对部署中所涉及的机器进行更详细的验证,对生产环境中的任何修改都要进行登记。即便很是紧急,也要在过后补充记录一下。”工具
“这些方法当然很好,但其实咱们能够采用更好的办法来解决。”Joe接着说到,“假如咱们在部署运维工做也可以借鉴持续集成的作法,利用一些最佳实践,那么此次部署事故根本就不会发生。好比(1)将部署操做脚本化;(2)进行持续部署验证测试;(3)部署脚本通用化,环境变量等使用配置方式传入;(4)让测试环境尽量与生产环境一致,至少在成本条件容许的状况下尽可能保持类似;(5)对环境配置进行版本控制;(6)任何人不得直接对生产环境进行直接的手工操做,等等。”
Bob说道:“嗯,其实那些上线步骤中所描述的内容均可以进行脚本化,以前也讨论过这一问题。目前上线步骤中的内容基本均可以写成自动化脚本,即便如今不行,也能够经过少许改造,使其能够自动化。但问题是... ...”Bob犹豫了一下,接着说道,“如何来验证这些脚本是正确的呢?”
Joe 说道:“保证运维人员是如何验证上线申请单上的上线步骤是正确的呢?一样,咱们也能够作一些部署验证就好了。这些部署的验证也能够经过脚本方式来进行,好比在安装以前验证程序所用端口没有被占用,安装以后验证该端口已被该程序所使用;好比安装以前验证程序日志中记录了该程序已中止运行,在安装以后验证程序日志中刻录该程序已从新启动;等等”。
Alice问道:“那咱们还要调试这些部署脚本呀?没有线上生产环境,咱们怎么调试呢?”
Joe 回答道:“首先咱们应该增强基础设施这方面的投入。在力所能及的状况下,让测试环境与生产环境类似。好比,生产环境可能有100台机器的集群,那咱们至少要找两台机器的集群作测试环境。生产环境中使用Tomcat,咱们的测试环境和开发环境中也应该使用相同的Tomcat,而不用Jetty。”
Joe 停下来,喝了一口咖啡,接着说道:“这样一来,咱们的部署脚本就能够在开发环境、测试环境进行测试了。当开发人员进行本地测试时,可使用这个脚本进行单机的部署。当测试人员进行集成测试时,可使用一样的脚本进行多机部署。与机器数量无关的配置能够统一放在某配置文件中。而与机器数量等相关的配置能够放在另外的配置文件中。因为在真正上线部署以前,开发人员和测试人员已经使用同一个脚本进行屡次部署,就是对该脚本进行的测试。当咱们上线部署时,只有与机器相关的配置文件会有变化,其它配置基本相同,因此上线部署时脚本出错的概率已经比较小了。并且,这种自动化没有人工干预,也不会发生手工误操做。”
Tom问道:“那这些脚本由谁来写?由谁维护呢?”
Joe回答道:“谁最了解状况,就由谁来写。其实,咱们也应该像对待产品代码同样,来对待这些脚本和配置文件,把它们放在咱们的代码库里,进行版本控制。不管是运维人员仍是开发人员,或者测试人员,对这些脚本的修改都应该提交到版本控制库中,除非他所作的修改只是为了测试他本身在本地的程序,那就不用提交了。这样一来,‘谁在何时对什么进行了修改,为何作修改?’这个审计问题就能够直接由版本控制系统来回答,也就作到了全部内容可追踪了。”
“听上去,对于配置文件、脚本等进行版本管理的确是解决了运维部署的不少问题。但如何对环境管理进行版本控制呢?”Tom问道。
Joe想了想,说道:“环境管理比较复杂。通常来讲,环境包括几个层次,包括硬件及网络配置、操做系统、咱们的应用程序所依赖的软件堆栈及其配置、以及咱们的应用程序运行时所需的数据及其配置。目前对咱们来讲,对于硬件及网络配置、操做系统这两层来讲,有两种方式进行管理。一种是利用一些专用软件进行自动化的远程配置,即只要给机器加电,就能够经过一些技术对一台机器进行系统的安装与配置。另外一种是使用虚拟化技术来进行系统配置管理。对咱们如今的游戏平台来讲, 使用后者便可。只要将基本的环境作成虚拟机镜像文件,并将其做为环境基线进行版本管理。固然,因为镜像一般较大,因此最好不要使用常见的版本控制工具(如subversion,Git等)进行,而使用某种简单的机制便可。”
Joe停了一下,看看你们没有提问的意思,因而接着说道:“至于基于其上的软件堆栈及堆栈中各软件的配置管理彻底能够利用相似于CfEngine,Puppet或Chef的工具进行。这些软件环境管理工具 都提供某种领域专属语言来描述软件堆栈配置,并保存在文本文件中。这些工具通常经过服务器/客户端的工做方式运行,客户端向服务器发送请求,验证本机器节点的软件配置是否与服务器中的设置相符,若是不符,就会自动更新。尤为重要的是,这些更新操做都是幂等的,即不管这些配置在该客户机上执行多少遍,每次的结果状态都是相同的。另外,它们一般能与版本控制工具集成。因此,只要将咱们的软件堆栈配置管理信息放到版本控制库中,就能够同时管理数台机器。”
“oh, 对不起,Joe,我想打断一下,”Tom问道:“你能画一个图来解释一下你刚才所说的这种软件环境配置管理工具吗?”
“固然没问题。”Joe拿起笔在白板上画了一个Puppet的工做示意图,以下图所示。
“看上去清楚多啦。”Tom笑道,“经过这种方式,咱们就只须要将版本控制库中保存的配置信息检出到本地,进行相应的修改,再提交到版本控制库中,这种工具就会自动帮咱们完成必要的配置更新了。是这样的吗?”
“对,”Joe点了点头,说道,“若是咱们的部署脚本也是经过这种方式来作的,那么咱们就根本没有必要登陆到生产环境的机器上,进行手工操做了。并且,Puppet还提供一种Try Run功能,能够进行配置变动的模拟,让你可以对比一下变动先后的不一样之处。”
Tom说道:“你说的这些听上去都不错。但并非全部人都可以修改生产环境的配置信息的。因此咱们仍是须要一个软件平台来管理上线的申请审批流程。”
“在任何企业中,这种申请审批流程和生产环境变动的受权都是必要的,但这仅仅是审核流程的操做。而真正与软件部署相同的具体操做都不该该在这种审批流程当中。”Joe回答道。
Tom接过话来,说道:“嗯,这样的话,咱们仍旧可以作到:有权限的人才能真正修改生产环境的配置文件,同时达到了无人真正直接操做生产环境的目的,避免了手工误操做带来的问题。”
参加本次会议的测试人员和运维人员对这种作法产生了浓厚的兴趣,并要求开发人员给予配合,将目前游戏平台的部署自动化。Tom说道:“这就是咱们运维工做的一个方向。让枯燥易出错的重复性手工操做变成受控的自动化,从而解放运维人员,让咱们能够关注于更加有价值的运行监控等工做中。”
Alice说道:“这看上去仍是有必定的工做量啊。”
“固然,咱们可能须要作一些工做,但我想这些投入是值得的。”Joe回答道。“同时,还须要各类角色之间更紧密的配合,而不是像以前那样,经过一个表明上个世纪八十年代先进技术的办公自动化平台来描述部署上线步骤这类关键的业务操做信息。”
Tom也点了点头,说:“嗯,应该使用版本控制方式。但咱们仍是须要一个上线审批的流程,只不过,这个流程中再也不保存上线步骤这类与实际部署相关的业务信息,而只是为了部署人员的资格审核与信息周知的目标。”
通过一番讨论,开发、测试和运维团队在这件事情上达成了一致,并按计划开始实施了。
http://www.infoq.com/cn/news/2011/08/ci-software-self-recognition
在前文《自动化部署》中,咱们讨论了自动化部署。经过对部署操做脚本化、部署验证自动化、部署环境版本控制、生产部署全自动化等诸多实践,可让部署彻底处于受控状态。然而,做为运维人员,是否曾经有人走过来问你这样的问题:“当前生产环境上部署的是哪一个软件版本?”你是否遇到过这样的情形,即便手里拿着一个jar文件或dll文件,也没法知道它究竟是哪一个版本。也许你可能认为,这算不了什么,到某个管理平台上查一查部署记录就好了。但是,若是发如今生产环境的集群服务器上,不一样机器上部署的同一个程序文件(好比.war文件)的大小却不相同,哪个的大小是正确的呢?做为运维人员,你当时的心情会是什么样呢?
下午五点钟,运维人员Steven习惯性地打开线上运维监控平台,一页一页地快速查看着监控数据。这已经成了他的一个工做习惯,只要有新版本上线,他老是常常打开监控页面看看。“好像没什么问题。”他暗自庆幸,“能够正点下班了”。
突然,他的目光停在了一个流量曲线上。“为何波动会这么大呢?”他又查看了其它几个相关曲线,没有什么问题,但直觉告诉它,可能新版本部署有问题。因而,他开始了更详细的跟踪分析。
时间随着电子表的跳动,一分钟一分钟的过去了。终于,Steven站了起来,深深地吸了一口气。此时,时钟已指向晚上八点啦。他发现,在生产环境的集群中,有五台机器的应用程序文件包与其它机器上的文件包相比,文件大小不同。到底哪一个是正确的呢?
Steven首先查了一下部署管理日志系统,文件名为abc,正确版本是1.21。但是这两种文件的文件名都是abc.war。从文件名上没有什么区别。无奈,Steven操起电话,把开发人员Bob从家中叫了回来,一块儿解决这个问题。
又通过三个多小时的忙活,两我的终于找到了正确的版本。原来,只有那五台机器上的二进制包是正确的,其它机器上的都不对。这两个版本都属于1.21,只不过,其中一个是快上线前修复bug的一个测试版本,而另外一个是正式上线版本。它们在版本库中的revision只差一个。多是谁部署时不当心搞错了。
次日一大早,Steven在站会上把这个Case通报了一下,引发了Joe的注意。Joe说道:“咱们站会以后讨论一下,怎么避免这类问题的发生吧。”
因而,站会后,Joe、Bob、Alice和Steven在一个角落里坐了下来,并叫来了运维主管Tom。
“咱们都使用了自动化部署,怎么还会出现这个问题呢?”Tom不解的问道。
Steven答道:“我查看了一下脚本,这部分没有作验证,我今天把它加上。不过,这两个版本的文件名称是同样的,只能在部署前拿到它们的MD5,进行比对验证。”
Alice说道:“咱们还能够对文件名进行规范,在文件名上加入版本号,好比appname-xxx_xxx。”
“这也是解决问题的一个办法。可是,你知道,文件是很容易被重命名的。”Joe说道。
Tom又说道:“咱们能够把MD5和一些元数据信息,好比revision等放到一个无数据描述文件中,并打包在应用程序中。好比,在Java领域,全部的.jar, .war 和.ear文件都容许将这些信息放在 META-INF/MANIFEST.MF文件中。”
“嗯,这也是种好办法。可是,若是能让应用程序自我识别,不是更加直接吗?”Joe说道。
你们一脸迷惑地看着Joe,不明白他在说什么。
“咱们让应用程序告诉咱们它是什么版本,不就是自我识别嘛!”Joe笑道,“其实,这也不是什么新鲜技术,大家一看就明白了。”
Joe打开笔记本,接上了21寸的显示器,把他们使用的持续集成和发布管理工具Cruise的界面打开了,如图1所示。
图1 软件自我识别版本信息
接着说道:“这是咱们用的Cruise服务器的信息页面。从这里,咱们能够很清楚地看到这个应用程序的运行环境信息,好比Java虚拟机的版本、操做系统类型与版本、服务器存储空间信息、应用程序的数据库版本、license信息等等。更重要的是第一行的服务器版本信息。(1)表示其对外发布的版本号;(2) 和(3)可能对应着其revision号。”
“我历来没有看过这个页面。”Bob和Alice同时说道。
“这里面的信息不少啊。”Steven有点儿兴奋的说道,“若是咱们的应用程序也能够这么作的话,我在这方面的运维工做会轻松不少,由于我能够把自动化部署脚本和自动化部署验证作得更强大一些。”
Alice说道:“咱们的不少组件都以库文件方式存在,没有界面,那怎么办?”
“没有关系,”Steven说道:“界面对自动化运维来讲是次重点,最重要的是能够经过一些API以命令行的方式来执行。”
Joe点头说道:“让咱们来总结一下吧,看看接下来作什么。”
从它的元描述文件(metaData)中查看它来自于哪里。大多数现代二进制格式支持以某种方式作到这一点。好比,你使用Java时,全部的.jar, .war 和.ear文件都容许将这些信息放在 META-INF/MANIFEST.MF文件中。若是你是在Windows上,创造性地使用versioninfo resources也能达到一样的效果。注意:并非说把这些信息放在文件名中,由于修改文件名的可能性很大。
最容易的方法是可以访问一个已知的URL或者服务调用API,它可以告诉你任何你想知道的信息。究竟是什么样的UR或服务调用并不重要,只要须要知道这些信息的人知道怎么调用它就好了。
固然,API方式还有更多的用途。
好比,当你使用持续部署实践时,你在部署以前能够验证一下将要部署的二进制是否是正确的版本。
而后,你能够在部署以后,使用这个服务调用去验证应用程序的正确版本是否是启动并运行了。
若是有一个动态更新的系统信息显示板,你就能够快速且方便地看到哪一个软件安装的是哪一个版本,而不用去更新文档,由于文档很容易忘记更新。
最后,Steven和开发团队一块儿,商定了一些细节。