配置管理是一个被广泛使用的名词,往往作为版本控制的同义词。为了陈述清晰起见,在这里我们给出本书中对配置管理的定义:
配置管理是指一个过程,通过该过程,所有与项目相关的产物,以及它们之间的关系都被唯一定义、修改、存储和检索。
配置管理策略将决定如何管理项目中发生的一切变化。因此,它记录了你的系统以及应用程序的演进过程。另外,它也是对团队成员协作方式的管理。作为配置管理策略的一个结果,虽然第二点至关重要,但常常被忽视。
虽然版本控制系统是配置管理中最显而易见的工具(团队规模再小,也应该使用版本控制系统),但决定使用一个版本控制工具仅仅是制定配置管理策略的第一步而已。
假如项目中有良好的配置管理策略,那么你对下列所有问题的回答都应该是“YES”。
最后这一点非常重要。因为我们常常遇到这样的情况:配置管理策略完全满足前面四个要点,但这恰恰成了团队间协作的一个巨大障碍。事实上,如果我们能够给予配置管理策略足够的重视,那么最后一点与其他四点之间是可以不对立的。我们不可能在本章中解决所有这些问题,但当你读完这本书后,问题的答案就显而易见了。在本章中,我们将讨论三个问题。
版本控制系统(也称为源代码控制管理系统或修订控制系统)是保存文件多个版本的一种机制。当修改某个文件后,你仍旧可以访问该文件之前的任意一个修订版本。它也是我们共同合作交付软件时所使用的一种机制。
一般来说,包括Subversion
、Mercurial
和Git
在内的开源工具就可以满足绝大多数团队的需求。我们会花更多的时间来探讨版本控制系统和它们的使用模式包括分支与合并
本质上来讲,版本控制系统的目的有两个。
那么,为什么要这样做呢?理由可能很多,但最关键的是它能回答下面这些问题。
这是版本控制的基本原理和根本目的。现在,大多数项目都使用版本控制系统。
下面是我们对高效使用版本控制系统的几点建议
我们所讨论的有关加快发布周期和提高软件质量的所有实践,从持续集成、自动化测试,到一键式部署,都依赖于下面这个前提:与项目相关的所有东西都在版本控制库中。除了存储源代码和配置信息,很多项目还将其应用服务器、编译器、虚拟机以及其他相关工具的二进制镜像也放在版本控制库中。只要能从版本控制库中取出所需要的一切,就能保证为开发、测试,甚至生产环境提供一个稳定的平台。
这种策略在控制和行为保障方面建立了基础。对于在这种严格配置管理策略约束下的系统来说,根本不存在整个流程的后期还会出错的可能性。
使用版本控制时,有两点需要牢记在心。
在一些团队中,这种限制很可能导致开发人员需要几天甚至几个星期才能提交一次代码。这种很长时间才提交的做法是有问题的。除非是第14章提到的那三种例外情况。在这一点上有一些争议,尤其是在使用ClearCase以及相似工具的用户中。我们认为,这种方法存在以下几个问题。
一个更好的解决方案是尽量使用增量方式开发新功能,并频繁且有规律地向版本控制系统提交代码。这会让软件能一直保持在集成以后的可工作状态。
为了确保提交代码时不破坏已有的应用程序,有两个实践非常有效。
每个版本管理工具都提供“写注释功能”。但这些注释很容易被忽视,而且很多人习惯于忽略它。写描述性提交注释的最重要原因在于:当构建失败以后,你知道是谁破坏了构建,以及他为什么破坏了构建。
我们喜欢的一种注释风格是这样的:第一段是简短的总结性描述,接下来的几段描述更多的细节。简短的总结性描述怎么写呢?它就像是报纸的标题一样,要给读者足够的信息,以便让读者知道是否还需要继续读下去。
这个注释中还应该包括一个链接,可以链接到项目管理工具中的一个功能或缺陷,从而知道为什么要修改这段代码。
在软件项目中,最常见的外部依赖就是其使用的第三方库文件,以及该软件需要用到的正由其他团队开发的模块或组件间的关系。库一般是以二进制文件的形式部署,不会被你自己的团队修改,而且也不经常更新。然而,组件和模块会被其他团队频繁修改。在这里,我们只讨论依赖管理中的几个关键点,因为它会影响配置管理。
外部库文件通常是以二进制形式存在,除非你使用的是解释型语言。即使是解释型语言,外部库文件也通常会安装在全局系统路径中,并由包管理系统来管理,比如Ruby的Gems和Perl的modules。
对于“是否将这些库文件放到版本控制库中”这个问题,业界还有一些争议。例如,Maven(Java的一种构建工具)允许指定应用程序所依赖的jar文件,并会从因特网上的代码库下载(如果有本地缓存库的话,也可以从本地缓存中取得)。
是否一定要把外部依赖库文件放在版本控制库中呢?其实,放与不放,各有利弊。如果放了,那我们更容易将软件的版本与正确的库文件版本相关联,但它也可能使源代码库的体积更大,并且签出时间也会变长。
将整个应用软件分成一系列的组件进行开发(小型应用除外)是个不错的实践。这能让某些变更的影响范围比较小,从而减少回归缺陷。另外,它还有利于重用,使大项目的开发过程更加高效。
作为关键部件之一,配置信息与产品代码及其数据共同组成了应用程序。软件在构建、部署和运行时,我们可以通过配置信息来改变它的行为。交付团队需要认真考虑设置哪些配置项,在应用的整个生命周期中如何管理它们,以及如何确保这些配置项在多个应用、多个组件以及多项技术中的管理保持一致性。我们认为,应该以对待代码的方式来对待你的系统配置,使其受到正确的管理和测试。
每个人都希望使用的软件非常灵活。为什么不呢?可是,灵活性也是有代价的。
对于软件灵活性的期望常常导致一种反模式,即“终极配置”,而这种反模式常被表述为对一个软件项目的需求。如果做得好,它没有什么坏处,但是如果搞不好的话,它会毁了一个项目。
现代计算机语言已经采用各种各样的特性和技术来帮助减少错误。在大多数情况下,配置信息却无法使用它们,甚至这些配置的正确性在测试环境和生产环境中也根本无法得到验证。我们认为,对部署活动的冒烟测试(参见5.3.3节)就是一种缓解配置验证问题的方法,我们应始终使用它。
我们可以在构建、部署、测试和发布过程中的任何一点进行配置信息的设置。而且,我们也的确会在多个时间点对应用软件进行相关的配置,如下所示。
一般来说,我们并不赞同在构建或打包时就将配置信息植入的做法,而是应使用相同二进制安装包向所有的环境中部署,以确保这个发布的软件就是那个被测试过的软件。根据这一个原则,我们可以推出:在相临的两次部署之间,任何变更都应该作为配置项被捕获和记录,而不应该在编译或打包时植入。
在管理应用程序的配置这个问题上,需要回答三个问题。
通常配置信息以键值对的形式来表示。有时可使用系统提供的配置类型来有层次地组织这些配置项。比如Windows 属性文件的键值字符串就是以不同的heading来组织的,而YAML文件在Ruby领域非常流行,Java中的属性文件虽然在格式上相对简单,但在大多数情况下还是能够提供足够灵活性的。将配置信息以XML文件的形式来保存可以对其复杂性起到较好的限制效果。
将应用软件的配置信息保存在哪里呢?显而易见的选择包括数据库、版本控制库、文件目录或注册表等。版本控制库可能是最容易的,只要将配置文件签入就可以了,而且你可以随时拿到任意时间点上的历史配置信息。像源代码一样,将配置选项列表也保存在同一个代码库中是非常值得的。
将那些特定于测试环境或生产环境的实际配置信息存放于与源代码分离的单独代码库中通常是非常必要的。因为这些信息与源代码的变更频率是不同的。不过,当使用这种方法时,需要注意:配置信息的版本一定要与相应的应用软件的版本相匹配。这种分离方式特别有利于重要信息的安全性,对于这些重要信息(如密码和数字证书等)的存取需要施加限制。
数据库、文件目录和注册表是比较方便存储配置信息的场所,它们可以被远程访问。但是,为了审计性和可回滚性,一定要将配置项的修改历史保留下来。你可以通过某种系统自动地实现这一功能,也可以让版本控制系统充当这一角色,写一个脚本,根据需要将适当版本的配置信息加载到数据库或文件目录中
获取配置信息
无论配置信息是什么样的存储形态,我们建议使用一个简单的Facade类,让它提供与下面类似的接口:
getThisProperty()
getThatProperty()
将应用的技术细节与外界相隔离,这样就可以在测试代码中模拟它,并在需要时改变其存储机制。
为配置项建模
每个配置都是一个元组,所以应用程序的配置信息由一系列的元组构成。然而,这些元组及其值取决于三方面,即应用程序、该应用程序的版本、该版本所运行的环境。无论你使用哪种方式来存储配置信息,放在源代码控制中的XML文件也好,或REST式Web服务中也好,都要能够满足不同的要求。下面列举了一些在对配置信息建模时需要考虑的用例。
在不同环境之间管理配置信息的一种方法是,把预期的生产环境中的配置信息作为默认配置,而在其他环境中,通过适当的方式覆盖这些默认值(确保你有预防措施,以防生产环境受到配置失误的影响)。
系统配置的测试
与应用程序和构建脚本一样,配置设置也需要测试。对于系统配置测试来说,包括以下两部分。
在大中型组织中,通常会同时管理很多应用程序,而软件配置管理的复杂性也会大大增加。这类组织中一般都会有遗留系统,而且很可能某个遗留系统的配置项让人很难搞得清楚明白。这种情况下,最重要的任务之一就是,要为每个应用程序维护一份所有配置选项的索引表,记录这些配置保存在什么地方,它们的生命周期是多长,以及如何修改它们
如果可能的话,运行每个应用程序的构建脚本时应该自动生成一份这类信息。即使无法做到这一点,也要把它记录在Wiki上,或其他文档管理系统中。我们的目的是:系统运维团队可以通过生产系统的监控平台了解每个软件应用的配置信息,并能看到每种环境中所运行的软件到底是哪一个版本。像Nagios、OpenNMS和惠普的OpenView都提供了记录这类信息的功能。
如果应用程序之间有依赖关系,部署有先后次序的话,实时存取配置信息的能力就特别重要。很容易因配置信息设置不当而浪费很多时间,甚至导致整套服务无法正常运行,而这类问题是极难诊断的。
每个应用程序的配置项管理都应该作为项目启动阶段的一个议题,纳入计划当中。需要分析当前的运维环境中其他应用程序是如何管理配置信息的,考虑在新开发的应用中是否能够使用相同的配置管理方法。
我们要把应用程序的配置信息当做代码一样看待,恰当地管理它,并对它进行测试。当创建应用程序的配置信息时,应考虑以下几个方面
没有哪个应用程序是孤岛。每个应用程序都依赖于硬件、软件、基础设施以及外部系统才能正常工作。
在做应用程序的环境管理时,我们需要记住的原则是:
这里把不良环境管理可能带来的问题总结如下。
环境管理的关键在于通过一个全自动过程来创建环境,使创建全新的环境总是要比修复已受损的旧环境容易得多。重现环境的能力是非常必要的,原因如下。
需要考虑的环境配置信息如下:
其实高效配置管理策略的两个基本原则是:
如果应用了这两个基本原则,你就能将“在系统不停机的情况下,创建新环境、升级系统部分功能或增加新的配置项
”等工作变成一个
简单的自动化过程。
所有这些都需要考虑。尽管把操作系统也提交到版本控制库中的做法显然不合理,但这并不意味着将它的配置信息提交到版本控制库中不合理。远程安装系统与环境管理工具(如Puppet 、CfEngine)的结合使用让我们可以直接对操作系统进行集中管理和配置。
对于大多数应用来说,将这些原则应用于其所依赖的第三方软件更为重要。好的软件应该有一个能通过命令行执行的安装程序且不需要任何用户干预。应用程序的配置可以通过版本控制系统来管理,而且不需要手工干预。如果第三方软件依赖无法满足这样的要求,你就要设法找到替代品。使用第三方软件时,这应该是一个重要的评估依据。当评估第三方产品或服务时,应该问自己如下问题。
我们要将处于某个正确部署状态的环境作为配置管理中的一个基线。自动化环境准备系统应该能够从项目部署的历史中找到任一特定基线进行重建。只要对应用程序所在环境的任何配置做修改,就应该把这个修改保存起来,并创建一个新的基线版本,将此时的应用程序版本与这个基线版本关联在一起。这样就可以保证下次部署应用程序或创建新环境时,这些修改也会被包含在内。
实际上,你应该像对待源代码一样对待环境,增量式地修改,并将修改提交到版本控制库中。对每个修改都要进行测试,以确保它不会破坏在这个新版本的环境中运行的应用程序。
在以自动化方式管理操作系统配置的工具中,Puppet和CfEngine是两个代表。使用这些工具,你能以声明方式来定义一些事情,如哪些用户可以登录你的服务器,应该安装什么软件,而这些定义可以保存在版本控制库中。运行在系统中的代理(agent)会从版本控制库中取出最新的配置,更新操作系统以及安装在其之上的软件。对于应用了这些工具的系统来说,根本没必要登录到服务器上去操作,所有的修改都可能通过版本控制系统来发起,因而你也能够得到每次变化的完整记录,即谁在什么时候做了什么样的修改。
虚拟化技术也可以提高环境管理过程的效率。不必利用自动化过程从无到有地创建一个新环境,你可以轻易地得到一份环境副本,并把它作为一个基线保存起来。这样一来,创建新环境也就是小事一桩,点一下按钮就可以搞定。虚拟化技术还有其他好处,比如它可以整合硬件,使硬件平台标准化,即使你的应用程序需要一些不同的环境也没有问题
最后要强调的是,对环境的变更过程进行管理是必要的。应该严格控制生产环境,未经组织内部正式的变更管理过程,任何人不得对其进行修改。这么做的原因很简单:即便很微小的变化也可能把环境破坏掉。任何变更在上线之前都必须经过测试,因而要将其编成脚本,放在版本控制系统中。这样,一旦该修改被认可,就可以通过自动化的方式将其放在生产环境中。
这样,对于环境的修改和对软件的修改就没什么分别了。它也和应用程序的代码一样,需要经历构建、部署、测试和发布整个过程。
在这方面,应该像对待生产环境一样对待测试环境。测试环境所需的核准流程通常会简单一些,应该由管理测试环境的人来控制。但在其他方面,其配置管理应该与生产环境中的配置管理没什么不同。
配置管理是本书其他内容的基础。没有配置管理,根本谈不上持续集成、发布管理以及部署流水线。它对交付团队内部的协作也会起到巨大的促进作用。我们希望读者清楚地认识到,这不只是选择和使用什么样工具的问题,尽管这非常重要,但更重要的是,如何正确地使用最佳实践
如果配置管理流程比较好的话,对于下面的问题,你的回答都应该是肯定的
如果回答是否定的,那么你的组织正处于风险之中。我们建议为下面的内容制定出一个保存基线和控制变更的策略: