上线:准备和部署软件包时开发和运维的角色
这篇文章里,我们会探讨开发团队、运维团队和其它相关方如何通过协作来准备一个“好”的部署软件包。“好”的软件包能减少部署中出错的可能,并在需要自定义环境时提高部署的透明性。
此外,我们还会检视为何一个结构良好的部署包更易于转为自动化部署,提升生产率和可靠性,同时减少软件开发和维护生命周期中的错误和等待时间。
区分部署过程中的担忧:为什么 vs. 如何做
显而易见,部署包需要包含应用程序的所有组件。而不仅仅是你自己的二进制包——EARs,WARs之类——通常这些包由集成构建产生,还应包含静态内容、配置文件、共享库、防火墙配置等等。特别还应包括应用服务器的参数和设置,比如消息队列、连接工厂、数据源或类环境变量。实际上,软件包应包含软件生命周期中的所有内容,也就是那些需要一起被部署、升级和取消部署的内容。
确保软件包是“完备的可部署单元”对于一次可靠的部署来说是至关重要的,特别是在大规模环境中。指望目标中间件栈(middleware stack)已经用“正确值”设置好是导致部署失败的一个常见原因,这通常导致耗费很多时间来找出不同环境中的细微差异,这往往是令人沮丧的过程。
部署包中只包含配置信息,比如共享“服务”,例如为多个应用所共享的消息队列或数据源,这种情形不鲜见。这些配置显然应该按照有版本管理的、可部署的对象来处理,意味着这些配置文件按照和“普通”应用程序一样严格的流程来发布。实际上,平稳、可靠的部署共享服务通常更为关键。
除了确保软件包最完整的描述了包含什么使特定版本的应用程序或配置正确工作,软件包还需要指出它们能够运行所需的其它东西。换句话说,部署软件包需要明确界定他们的先决条件和依赖,以便在匹配的环境上部署。
准确的说,哪个部门负责部署和组织结构有很大关系。多数情况下是开发团队,他们通常能使用持续构建工具使某些发布流程变为自动化,但发布过程还可能涉及其它团队。举例来说,让DBA在发布前确认SQL脚本,或要中间件竞争中心(Center of Competence)检查中间件配置的情况并不少见。
请不要增量式发布(Delta Release)
通常,开发的一个目标就是尽量减小对目标环境的影响。显然,如果你的应用需要“不间断运行”或多个应用程序共享一个集群,不必要的重启服务器是要尽量避免的。
为了达到这个目标,一个通常的做法是要求开发团队提交一个“增量”发布包,其中值包含新的或修改过的系统模块,并且只部署这些模块。经验显示,其实这是一个冒险的策略,应尽量避免,原因如下:
- 准备一个增量发布通常都是手工任务,完全依赖开发人员判断哪些是修改过的模块而哪些不是。这是一个耗时的又容易犯错的过程,因而这个过程几乎不可重复 。
- 某个应用程序部署包的版本不适合实际部署——在最坏的情况下;可能所有的组件都需要上一版本。部署到一个“干净”的环境(设想需要快速设置几个干净的镜像来重现一个压力问题)则更加复杂,失败的风险也因所有早期版本的包必须还要保留、并按照顺序保留而增大。这是因为,当然,简单来说就是数据库增量备份的问题。
- 除非应用程序的每个版本都部署到了每个环境,否则在升级到相同版本时会产生问题,因为需要从不同的版本升级。这很快会在选择了错误的增量发布包时导致混乱和产生错误。
- 不包括完整的包,而只是一些碎片的发布仓库不是一个好的最终软件库的候选。
上述各点都无法让减小部署影响的目标失效,这是显而易见的。但部署包不能解决问题 。实际上,部署流程才能应该通过在部署时推知哪些组件应增加、哪些被修改和哪些应移除,并据此实施来满足这个需求。
在时限压力下手工实现上述部署流程是非常困难的(开发人员总是首先要做增量发布包),而一个好的自动部署系统能够很容易地实现上述目标。实际上,这是引入自动化部署的一个主要好处。
在上述情况下,通常会设立专门的发布管理小组负责协调各种交付物,部署包按照便于恰当的人更新、修改和批准部署包的内容来组织是非常重要的。
构建部署包是一个需要多个步骤的工作流,需要很多部门参与并耗时很长,也会产生准备“不完整”部署包的情况。这种情况下,发布管理系统不产生这种“不完整(draft)”发布包非常重要。
我们建议由恰当的人员对已批准和已发布的包进行数字化签名,这样所有的包在部署前都通过验证,为部署安全提供更进一步保障。
深入到比特和字节
选择部署包格式主要是从方便起见。当然,部署包应该:
- 便于移动——最好是个单一文件
- 便于检查
- 可压缩,因为文本文件,如SQL或配置文件压缩率很高
- 支持某种错误修正
- 可移植(比如开发人员在Windows环境开发,生产环境是UNIX)
- 可进行数字化签名,目的是可以验证待部署的包是通过审批的包。
所以通常来说,选择一种压缩文件格式,比如ZIP、TGZ或RPM,作为发布包的格式,但具体选择哪种完全取决于你自己。
目前关于build文件格式没有多少可说,因为除了WAR就是WAR。然而对于配置文件,情况则大不相同。目前,如果消息队列和数据源的定义,甚至包本身(在发给运维团队的成员的电子邮件中碰见这种情况很常见)通常都只是由人们可读的文档来描述的,比如发布注记(release notes)或按模板写的Word文档,这些文档非常容易引起歧义并难于检查修改。
基于上述原因,配置文件也应该由机器可识别的格式来定义,比如XML。不幸的是,在这个方面目前还没有什么标准可推荐。事实上,这方面也是我们非常希望和用户及供应商合作的。
最好避免使用某种特定中间件定义的格式,比如WebSphere的XML配置。有时直接从已有的配置文件中抽取配置项看上去很方便,但这样会限定于某个中间件供应商,在部署到不同的服务器环境时可能会遇到困难。通常,这是完全可以避免的,因为应用程序通常不会用到特定中间件的某个特定特性,因此可以在自己的配置文件中很容易的定义更“一般”的JMS队列,数据源等等。
最后, 声明文件或清单应该能描述包的内容。应该在这里列出包的依赖关系,给操作者的描述和任何其它不容易找到的信息,比如开发人员的联系信息,项目名称,等等。声明文件还需指出包内项目内容的类型,这些内容往往不容易通过名字判断出来:EAR文件显然是EAR但是一个ZIP文件可能包含HTTP服务器的的静态内容、应用程序配置文件等等。
再次指出,文件格式应该是机器可读并可验证的,同时也应该是容易自动生成的(比如从构建系统生成)。
我们现在有了方便的、可移植的并能自动部署的软件包,这个软件包也是待部署应用程序要包含什么内容的完整描述。到现在,我们还没说任何如何部署的内容。发布注记在哪儿?部署和回滚计划呢?
理想情况下,应该没有这些东西。更准确的说,标准的Java EE应用程序的部署流程应该,以部署包内容为基础,是足以完成正确部署的。应用程序特定的部署步骤意味着满足该应用程序的些特殊要求,这些特殊要求在之前只有开发人员了解。当凌晨2点钟时需要进行一次紧急部署,而无法联系上开发人员,运维人员犯错几乎是不可避免的。简单来说,开发人员应该知道什么应该被部署,他们不应该为如何部署而操心。
当然,组织内有标准的Java EE部署流程是实现上述目标的先决条件,当你的组织有几类不同的应用时可能有几个流程。这个流程要足够复杂,以便能覆盖在部署时所需的各个组件和配置,但限制可选项的数量以保证流程的可维护性、测试性和可靠性也同样重要。
这必然会给开发技术加上一些限制条件。然而,相比在当前的情况下维护成本是运行应用程序中最大的日常开支这一事实,减小犯错可能性、更可靠的部署流程带来的好处胜过最大化开发灵活性。当然,在每个组织都需要依据自身的要求来平衡这两者。
标准的部署流程不只是更容易维护。它也使运维人员在压力下更可靠的执行操作,相比于依照大量针对不同应用程序的不同版本的发布注记手工操作容易得多。标准部署流程也会让自动化变得更简单,让运维人员从重复任务中解脱出来,同合适的访问控制相结合,可以让开发人员自己执行部署。如果有适合的集成点,将构建和发布系统连接起来实行持续部署以达到真正的端到端自动化也是有可能的。
此外,实现更多的自动化部署流程来支持更复杂的场景,让开发团队在避免临时流程时更加灵活性更容易。不过随着自动化流程变得越来越复杂而难于实现和测试,是否采取复杂的自动化流程需要仔细考虑。但是相对手工流程明显的优势是,一旦自动化流程通过测试和校验,你可以确保流程的所有步骤是执行结果是可预测的。对人们来说,不断成功的执行复杂的、步骤众多的流程的目标是非常难以达到的。
从部署包到运行中的应用软件:为目标环境定制化软件包
目前为止,我们探讨了将所有内容打为一个自描述的包,这是发布管理中的一个好实践。这样做,我们默认为一个软件包可以“原封不动”地部署到不同环境。然而在当前的部署、测试、验证和生产过程中,我们其实还须针对不同的目标环境“调整”应用程序、相关配置和资源——设想一下配置文件或者数据源、用户名及口令等配置项。
有些公司在虚拟化和虚拟设备的道路上更进一步,这会让上述情况有所改变,但当前构建部署包和部署流程应简单、可靠并在部署时透明定制应用程序组件和配置文件。
在缺少已有标准,甚至是指导原则的情况下,为了解决上述问题人们尝试了很多解决方案,从如JMX等非常优雅的方法到简陋的方案如字符串搜索替代 。
此外,不同的中间件平台有各种不同程度的定制化支持:通常,门户、ESB和过程服务器提供某些解决这些问题的“本地化”解决方案,但是应用服务器倾向于让用户自己弄好这些配置。
时常,结果定制化方法因不同项目、目标平台和部门而异变成大杂烩。像以前一样,这会导致维护开支增加和提高潜在的混乱和错误。
那什么是“标准“定制化解决方案应提供的呢?当比较不同的方案时,下面这几件事应牢记在心:
- 方便:定制化过程应便于设置和快速实现。对有时需要在部署到开发环境时进行定制化操作的开发人员来说这很重要。
- 可视化:便于授权用户查看特定部署环境上的定制项(应用程序中可定制的部分)和赋给这些定制项的值。
- 安全的:当缺少定制项的值或设置了错误值(比如忘记设置超时值或在生产环境上设置为测试环境上的值)时,应该能够快速检测到。宁可在部署时检查到定制值缺少或错误也要比在运行时出现模棱两可的错误好。
- 有版本控制和访问控制:只有授权用户才能查看和修改应用程序定制项的值。最好能记录这些值的变更历史。这有助于比较同一应用程序不同版本的定制值,也允许用户比较同一应用程序在不同部署目标环境上的定制值。
无论使用那种技术,定制化方案主要分为两类:基于指针的定制化和基于符号的定制化。
基于指针的替换只是“搜索并替换”的一种时髦的说法,它稍微先进一些的同类,基于XPath的替换,通常能在门户或ESB环境中见到。这种方法是脆弱的,因为部署包包含有效的定制值,因此存在隐蔽错误的风险很大。更进一步,部署包的定制值基本上不可见。讽刺的是,这种方法在为那些不能定制化的包打“补丁”时很有用。
总的来说,准备基于指针方式定制化的包的确更容易——简单来说就是将开发环境或其它“创建(authoring)”环境上的设置导出,或者做一个快照。当然,这个导出过程可以是“符号化(tokenized)”的(用符号来替代需要定制的值),但这是易出错的并且引起高昂的手工开支,特别在开发过程中经常需要导出时是个问题。
显然,基于指针的定制化仅在需定制的设置或定义用某种方式结构化的情况下才可行——否则不可能构造一个指向待修改项的指针。XML和资源定义(比如properties文件)归为这一类,但类似纯文本文件则不属于这一类。
基于符号的替换,是另一种方式,主要指占位符——部署包包含(构件、资源定义 等)特殊的符号,在部署时这些符号被给定的值替代。基于符号的定制化要求部署包提供者,通常是开发人员,专门准备部署构件。
这意味着,首先,所有待替换的符号在交付时要清楚,应用程序有了一个正确定义的定制项集合,这是额外的好处。符号的特殊语法也让验证替换值是否都给出提供了可能。
即使校验失败,应用程序通常因符号的语法错误而终止——符号一般来说不是合法值——这也时额外的安全机制。
当然,在开发人员来看这种错误保护机制也会是麻烦的。因为除非符号被替换否则应用程序无法正确工作,这要求必须建立构建流程来实现它,同时这个流程还要是快速的,当然是在开发环境。
基于指针的替换方案对于那些没设计成可定制化的应用程序也适用,然而基于符号的替换方案在错误保护和可视度方面有明显优势。另外,基于符号的替换方案便于传播通过特定环境的只有运维人员才能访问的的定制值(想一下如生产环境数据库口令)来得知应用程序在哪些地方需要定制(开发人员应该知道并详细列出)的知识。
因为部署通常都是关键任务,这些实实在在的好处应使基于符号替换的方案在任何可能的时候作为首选。
事实上,应用程序的定制项应列在包的声明文件中。基于特定的符号语法,通常能够(并推荐)依据这个定制项列表对包内容进行检查。在部署时,也可用同样的方式验证,为安全的部署提供了额外的保障。
请不要区分开发、测试、验收和生产环境的部署包
任何尝试过,在开发过程中,按含糊的说明来判断要替换什么、这些定制项在哪个配置文件中以便让应用程序运行起来的人,可能会纳闷为什么不能用不易出错的方法做事。如果没有合适的自动化部署方案,一个常用的替代方法是尝试将定制作为构建和发布流程的一个环节。
从技术角度看,一个容易想到的选择是:目前有许多好的持续构建和发布产品,开源的和商业软件都有,并且大多数组织已经有了。很多工具都支持“profile”这个概念或类似区分build细节的机制,并且如果没提供这类机制的话也提供了让你加入自己的搜索替换功能的钩子。
然而,有些严重的缺陷导致这个方法作为合适的定制化方案并不合适:
- 在构建过程中需要访问环境相关的细节。这不应只是觉得“错误”,有些信息可能是高度敏感的——比如生产环境数据库的口令——让这个方案从安全角度来看不可行。
- 持续构建工具一般不能提供恰当的有版本控制的、有安全机制的仓库存储环境相关的定制值,很容易犯臭名昭著的复制粘贴env.properties文件的错误和其它错误。
- 有了环境相关的build,几乎不可避免的是,在某些时候你会不小心在生产环境上运行测试build。
结论
顺畅和可靠的部署流程从简单的第一步开始:以一种自动的、可检查校验的方法整合并发布一个结构化的,完整的部署包,该部署包定义了特定版本的应用程序所有的组件、配置和依赖关系。这能显著的减少因不合法或缺少定制值、组件或必须的服务而产生的错误。
计算机可读的包也是引入部署自动化的第一步,这里提到的自动化不仅是检查校验自动化,还包括按照自定义的标准流程部署这些包,并按照环境自动定制。除了确保一致性和提高可靠性之外,这可显著提高效率并可以免除开发和维护生命周期内的一个最常见的瓶颈。
关于作者
作为一位早期就相信Java有能力去交付“企业级”软件的人,Andrew Philips很快关注于高产能、有弹性和可扩展的J2EE应用软件开发。特别是在并发和高性能开发方面,Andrew通过不断在多个国家从事复杂的并有挑战性的企业应用环境工作积累了丰富的经验。他一直关注有效的集成有前途的新Java开发到公司软件开发,Andrew于2009年3月加入XebiaLabs,在那儿他作为公司部署自动化产品Deployit开发团队的一员。和其他人一起,他也为开源Java STM实现Multiverse和领先的Java云计算库jclouds贡献代码。
更一步阅读
[1] Hamilton, James, "On Designing and Deploying Internet-Scale Services", Proceedings of the 21st Large Installation System Administration Conference (LISA '07) pp. 231-242
[2] Phillips, Andrew, "Customize This: Tailoring deployment packages to your target environments"
[3] Kumar Yadav, Vivek, "Structuring a Deployment Package, part 1: Understanding the complexity"
[4] Partington, Vincent, "Incremental deployments vs. full redeployments"
[5] van Loghem, Robert, "So what is a deployment really?"
查看英文原文: Going Live: The Role of Development and Operations in Preparing and Deploying Software Packages