一键发布管理
假设您有一个大型软件、一个复杂的网站或一大堆小型网站。您还有一群程序员和一台服务器集群来部署最终产品。最糟糕的是,客户坚持关键变更的交付周期要短。可能为您提供系统化、统一的开发、测试和部署流程的专有产品通常价格昂贵,并且提供的部署选项有限。它们通常需要新的硬件资源和软件许可证,仅仅是为了支持系统本身的安装。这样的解决方案可能很难向关心成本的管理者和担心学习新的复杂流程的开发人员推销。
然而,在没有这种统一方法的情况下,在紧张的进度安排下管理端到端的开发流程可能会导致严重的效率低下、进度延误,以及总的来说,令人头疼的问题。如果您是这样一个项目的管理员,那么您很可能花费大量时间处理代码发布的管理。另一方面,您可能已经在使用昂贵的专有软件,它解决了您今天的所有问题,但高层管理人员却对不断增加的许可证续订费用犹豫不决。您需要向他们展示一种替代方案。也有可能您每年只发布一次代码,并且有比您知道的更多的空闲时间,但您碰巧是一个自动化狂热者。如果这些情景中的任何一个对您来说似曾相识,请继续阅读。
各种开源产品可以进行调整,以最大限度地降低成本和开发人员的挫败感,同时通过充当现有工具集之间的粘合剂来驯服您失控的发布流程。也许您甚至可以开始及时回家,在睡前玩一两轮拼字游戏。
本文中提供的示例基于一些假设,希望这些假设足够常见或通用,以便可以轻松地将这些原则外推以适应真实环境的具体情况。我们的开发人员可能已经在使用错误跟踪系统 (BTS),例如 Bugzilla、ClearQuest 或 Mantis,或者使用内部数据库解决方案来跟踪变更请求和错误。他们也可能正在使用版本控制系统 (VCS),例如 Arch、CVS 或 Subversion,来管理各种 BTS 条目中要求的实际代码变更。
如果他们没有为大型项目使用 BTS 和 VCS,那么这些开发人员可能要么拥有超人的组织能力,要么对情感创伤具有高度的容忍度。我们使用哪个 BTS 和 VCS 对于本次讨论并不重要,并且对一个系统与另一个系统之间的优缺点的任何探索都需要比我在此处分配的文本多得多的内容。简而言之,它们都应该支持我们希望采用的流程类型所需的构建块。也就是说,几乎任何 BTS 都可以
为其数据库中的所有问题或错误分配唯一的 ID。
允许您使用唯一的 ID 来跟踪问题的状态,并存储和检索它影响的任何源文件的列表。
任何值得称道的 VCS(对不起 VSS 粉丝们)都可以
允许中央代码层次结构的某种形式的分支和合并。
允许命令行客户端进程通过安全网络连接进行连接,以便执行更新。
我们使用 Subversion (SVN) 存储库,并启用 SVN+SSH 访问方法作为我们的 VCS,以及一个通用的 MySQL 数据库表作为 BTS。我们使用 Python 作为我们的首选脚本语言,即使对于新手程序员来说,Python 也往往非常易读。您的发行版很可能已准备好所有这些产品的软件包;配置它们将留给读者作为练习。目标机器是通用的 Web 服务器,所有这些服务器都支持 SSH 连接以及 VCS 客户端工具。
以下是我们可能开始使用的示例端到端流程的 10,000 英尺概览
在 BTS 中生成问题,并分配 ID 001 和初始状态“新建”。它包括或将包括 VCS 存储库中表示新的或已更改的文件路径的列表,并分配给相应的开发人员。
被分配的开发人员对其源代码的本地副本进行更改,将这些更改检入 VCS 存储库,并将 BTS ID# 001 的状态更新为“正在测试中”。
测试服务器已使用新的更改进行更新。
负责审查所有状态为“正在测试中”的 BTS 项目的 QA 测试人员验证代码的更改是否符合要求,并将 BTS ID 001 的状态更新为“准备生产”。
然后,发布经理将 BTS ID# 001 影响的所有更改打包到一个版本中,并将 BTS ID# 001 的状态更新为“生产中”。
实时服务器已使用更改进行更新。
在很大程度上,我们设法修复错误并向代码库添加新功能,而无需系统管理员过多地操心,除了偶尔的密码重置或 RAM 升级。但是步骤 3 和步骤 6 需要我们以某种方式将代码从 VCS 中取出并放到实时系统上。我们可以从 VCS 中剪切和粘贴文件到我们硬盘驱动器上的文件夹中,将其压缩,发送给系统管理员,并要求他在实时系统上解压缩它。或者,我们可以利用我们的 VCS 的结构及其实用程序为我们完成工作,并完全避免与管理员进行对话,管理员的时间往往非常宝贵。
如果我们将 VCS 构造成包含一个分支方案,该方案镜像了我们在 BTS 中的各种状态,那么我们很可能会得到一个 BRANCH,开发人员在其中添加新的、未经测试的更改,以及一个 TRUNK,其中仅包含“生产中”的代码,尽管它很容易是另一种方式。然后,使用 VCS 的分支合并功能将“准备生产”的代码从测试 BRANCH 移动到稳定的 TRUNK 就变成了一个相对简单的问题。因为我们的 TRUNK 上不会发生任何开发更改,所以从 BRANCH 合并到 TRUNK 不太可能导致任何冲突。管理将代码从 VCS 移动到实时系统的最后一步变得更加容易,因为更新仅仅是使用 VCS 客户端实用程序来拉取存储库 TRUNK 上发生的所有更改的问题。
因此,现在所有部分都已到位,可以实现快速准确的代码部署,但我们仍然需要请求我们的系统管理员在实时系统上运行 VCS 客户端工具。但是,如果他或她愿意向我们的发布经理提供具有在实时系统上运行 VCS 客户端权限的 SSH 登录名,我们可以进一步最大限度地减少对系统管理员时间的占用。
一旦我们有了支持通过我们的 VCS 执行内容更新的基础设施,那么下一步合乎逻辑的步骤是进一步消除发布时手动干预的需要。现在我们可以创建一个脚本,该脚本可以使用 VCS 客户端工具将代码更新拉取到实时系统。随着我们需要更新的机器数量的增加,此方法的实用性也随之提高。如果我们的脚本可以访问需要更新的所有目标机器的列表,我们可以一次性全部命中它们。
像示例一样,这个难题的一部分可以是一个简单的脚本,发布经理可以从他的工作站的命令行运行该脚本。或者,它可以是一个花哨的基于 Web 的 GUI,发布经理团队可以使用它从任何 Web 浏览器通过鼠标单击来更新任意数量的机器。在任何一种情况下,在客户端机器上创建一个用户 ID 都很有用,该用户 ID 具有连接回 VCS 系统而无需提示输入登录信息的权限。这可能需要在客户端机器上配置用户帐户,并使用允许其连接回 VCS 服务器的 SSH 密钥。
列表 1. vcs_update.py
#!/usr/bin/env python import os, sys clientList = ['host1', 'host2', 'host3'] sandbox = "/usr/local/www" def updateClient(client, sandbox): # ssh to client machines and update sandbox command_line = "ssh %s svn update %s"%(client, sandbox) output = os.popen4(command_line)[1].readlines() for line in output: print line if __name__=="__main__": for client in clientList: updateCLient(client, sandbox)
通过在客户端机器上安装此脚本,我们可以通过加密的 SSH 连接从中央位置更新 VCS 文件的客户端副本。
现在我们有了一个相当高效的流程,它几乎无缝地搭载到我们的开发人员在很大程度上已经使用的流程之上。它还允许通过单击按钮进行内容更新。那么,是什么阻止我们编写脚本来更新测试服务器,以便它们在固定的时间间隔自动发生,从而使开发人员有机会在无需请求更新的情况下在实时测试系统上看到他们的更改?我们只需要在测试服务器上作为 cron 作业运行客户端脚本即可。
此外,只要我们提出疯狂的问题,为什么不利用我们的 BTS 数据库后端的强大功能来驱动整个流程,并真正减少流程管理瓶颈呢?为此,我们的脚本通过查询所有状态为“准备生产”的 ID 来生成需要在分支之间合并的文件列表。该脚本使用生成的结果列表作为输入,用于执行合并命令并自动更新 BTS ID 状态为“生产中”的函数。
列表 2. bts_merge.py
#!/usr/bin/env python import os, MySQLdb TRUNK_WC = "/path/to/working_copy_of_trunk/" TRUNK_URL = "svn+ssh://vcs-server/project/trunk/" BRANCH_URL = "svn+ssh://vcs-server/project/branch/" def initDB(): # connect to database, return connection cursor connection = MySQLdb.connect(host='dbhost', db='dbname', user='user', passwd='password') cursor = connection.cursor() return connection, cursor def listUpdatedFiles(cursor): # return updated file paths and BTS ids. cursor.execute("""SELECT changedfiles FROM BugTable WHERE status = 'ready for production'""") fileList = cursor.fetchall() cursor.execute("""SELECT bugID FROM BugTable WHERE status = 'ready_for_production'""") idList = cursor.fetchall() return fileList, idList def mergeUpdatedFiles(fileList): # merge branch changes into the trunk. for fileName in fileList: cmd = 'svn merge %s/%s %s/%s'%(BRANCH_URL, fileName, TRUNK_URL, fileName) for line in os.popen4(cmd)[1].readlines(): print line def updateBTSStatus(idList, cursor): # update BTS ids to 'in_production' status. for ID in idList: cursor.execute("""UPDATE BugTable SET status = 'in_production' WHERE bugID = %s""" % ID) def stopDB(connection, cursor): # close the database connection cursor.close() connection.close() if __name__=="__main__": os.chdir(TRUNK_WC) connection, cursor = initDB() fileList, idList = listUpdatedFiles(cursor) mergeUpdatedFiles(fileList) updateBTSStatus(idList, cursor) stopDB(connection, cursor)
现在让我们看一下我们修改后的 10,000 英尺概览,现在我们已经整合了所有的花哨功能
在 BTS 中生成问题并分配给相应的开发人员。
被分配的开发人员对其源代码的本地副本进行更改,将这些更改检入 VCS 存储库的 TEST 分支,并更新 BTS 中的状态。
测试服务器内容通过 cron 作业自动更新。
QA 测试人员验证代码的更改是否正确,并更新 BTS 中的状态。
发布经理按下按钮以启动我们的合并脚本,该脚本将所有更改合并到稳定的 TRUNK 中并更新 BTS。
发布经理最后单击一下,生产系统通过我们的 VCS 客户端脚本更新到最新代码。
步骤 5 和步骤 6 也很容易组合在一起,从而使我们的发布经理需要执行的工作量减半。
在某些时候,我们很可能希望向我们的 VCS 存储库添加一个暂存分支,并使我们的内容更新系统能够从这个中间分支拉取更新到暂存服务器。然后,QA 可以在客户端看到所有更改之前在实时系统上看到它们。或者,可以向客户端授予访问权限以提供最终批准。一旦暂存获得批准,将更新移动到生产系统就像执行已自动化的步骤一样容易,即从暂存分支合并到稳定的 TRUNK,并针对生产服务器运行内容更新脚本。
尽管这些示例在某种程度上是对所涉及问题的过度简化——例如,我们尚未解决数据库结构更新的潜在需求——但我们已经涵盖了一些核心概念,这些概念可以扩展以构建真正实用、量身定制的系统。事实上,我们可能正在接近开发流程的理想境界,并且我们仍然没有在软件许可证上花费一分钱。相反,我们只是编写了一些基本脚本来将我们的错误跟踪系统和版本控制系统粘合在一起。因此,管理层现在储备基金中有更多的资金,并且心脏悸动更少了。我们的系统管理员有更多时间可以用来删除桌面上的间谍软件。最重要的是,我们已经及时赶回家,有时间玩一轮拼字游戏了。这就是开源的力量。
本文的资源: /article/8141。
Jake Davis (jake@imapenguin.com),IT 顾问和自称企鹅,是 Imapenguin, LLC. (www.imapenguin.com) 的联合创始人,该公司是一家雇佣摇摇摆摆、不会飞的鸟类的公司。