文章
主题列表

最新资讯
QAD1 持续交付 Continuous Delivery

每小步验证

很久以前,工程类的本科生都要在最后一年做个毕业项目。当时,导师建议我们尝试参考一些常用的语言发声算法,在专门做信号处理的芯片上写程序,读出一些英文字或句子(与现在不同,当时这技术还未成熟,很多大学还在研究),虽然预计有不少技术难度,但觉得很先进,很感兴趣,便与另一位同学合作,开始制定项目计划。

我们很努力,全情投入,一方面要研究语言发声的算法,还要并行设计电子线路和软件等。我们用了2、3个月做好整个电子线路板硬件,同时设计了整个软件架构,并使用计算机模拟,看每个字实现算法后的发声效果,因为最后一年课程很多,时间过得很快,从9月开始准备一直到次年3月,软硬件终于都完成,但是不知道什么原因就是没有声音,更不要说能发出一些字和句子了,最后项目以失败告终。

在之前一年,我很幸运,在大学第三年去大东电报局实习(当时香港的所有国际通讯都是经过大东),在我实习的部门正如火如荼地开发一套新电脑系统取代本来基于UNIVAC的电报系统,总工程师让我用半年时间,在一个微机上编程,做一个系统,以从那些电脑上收集重要信息,如果发现异常就警报。头三个月都是花精力做整个系统设计,也买了一些展示电子版,准备用来展示,但由于经验不足,半年后最终什么都展示不出来。

到了2000年,兼读软件工程硕士课程时,我开始接触敏捷开发,才了解到两次项目失败的主因:不应该花大量时间去做前期设计,希望做一个完美的设计,而是应该一步一步迭代,先做一些最基本简单功能,逐步优化。例如,在我的毕业项目中,应先做出最基础的硬件、软件,先起码发出声音,因为没有前人做过,整个项目是从未做过的实验。

敏捷大师Dave Thomas 先生在2015 演讲里提出敏捷软件开发的核心是:

  1. 向你的目标迈出一小步

  2. 从反馈调整你的理解

  3. 重复

  • 当两种或以上选择的价值大致相同时,选一条让未来更容易修改(软件)的路径

Agile Software Development:

  1. Take a small step towards your goal

  2. Adjust your understanding based on what you learned

  3. Repeat

  • When faced with two or more alternatives that deliver roughly the same value,take the path that makes future change easier

(Source: Mr Dave THOMAS, 2015 goto)

为了避免最后发生严重问题,应每一步都验证,从反馈立马修正。

这原则不仅仅适用于软件开发, 例如在评估最后一天小组都需要利用有宏(macro)的xlsm 表 记录弱项并且评分,各页之间有很多依赖关系。例如,xlsm 表会自动从每个过程页汇总成总的报告页。

所以我会提醒小组每一步都要保存并验证:第一步,按美国随机抽样,勾选过程选择后,先保存一个版本,让我先查看出来的每个过程页是否正确。然后对两个最复杂过程打分,也保存一个版本让我查看。

先前的经验教训:因没有每步确认, 最后标注两个最复杂的过程时发现严重错误,尝试修正,但不成功。最终没办法只有从零开始重新做。

学生时期项目的失败可以谅解,但不明白为什么还有不少“成熟”开发团队出现同类问题,比如有些中型项目,时长6-8个月,发布前最后1-2个月才集成/系统测试,导致一大堆问题到最后才暴露,很多缺陷都难以解决,导致无法按时交付。

软件设计编码有误,只是引起部署交付失败的其中一类原因。下面是一些其他常见失败例子:

1) 手工部署 (Deploy Software Manually)

要准备一个很详细的文档,描述每步部署如何做,哪一步容易出错,也依赖人工测试来确保程序可以跑通,实施人员遇到问题解决不了,只能去问开发人员,导致发布时间延迟,因为现在的应用软件都比较复杂,包含很多中间件,很多配置,任何一步都可能出错。如果人工部署,发布耗费的时间会较长,整个发布部署风险也会比较大。(反过来如整个部署都是自动化,就能减少这类问题。)

2) 开发完成后,才开始部署生产环境(Production-like Environment)

测试人员一直到最后阶段还是在开发机器上测试软件,因为测试都是在内部机器做,做验收的客户代表,都没见过软件真正操作情况,他们在发布阶段才第一次触新开发的软件。

然后开发团队把所有的安装、文档配置、数据库迁移、部署文件等,交给安装工程师,但是一切都未在真正的投产环境测试过。开发团队和部署的工程师也没有沟通。

(好的做法就是把测试、部署、发布涵盖在整个开发过程中,不要等到最后,避免以上问题。)

3) 手工配置管理生产环境(Manual Configuration Management of Production Environments)

手动更改生产应用服务器的配置参数,并手工把更新记录在变更管理系统中。

这可能导致一直在测试的环境都没问题,但到生产就失败;也会发现在不同的部署环境,表现出来的性能不同,人工配置管理操作,要准备整个部署也很耗时。

因配置管理没做好,如果某个发布测试失败,无法倒退到前一个成功的状况;也难以看到不同操作系统的不同版本,包括一些程序包,一些补丁,一些软硬件的配置对不上。

当整个从测试到投产环境的配置是手工管理,会有很多未知之数。(如果使用自动配置管理系统来管,就可以避免刚才的问题。)

---===---

所以若要成功发布代码,必须注意部署过程、环境与配置。早在90年代,XP(极限编程)的创始人Kent BECK先生带领瑞士某保险公司的团队做软件开发,他们当时已经可以做到每晚发布。现代,越来越多软件团队已经按这思路,尽量缩短交付时间(cycle time),尽快得到反馈。已经有不少团队可以做到一到两周发布。DevOps 要求这个频率更快,持续交付(Continuous Delivery)要求每天都可以到一个测试好可交付状态。要缩短循环时间,需要多方面的配合,比如要做到持续集成、自动版本/配置管理等。

持续集成是要做到每天可交付的基础 - 每次代码变更都要做好集成,不要等到系统或验收测试时才出问题。

因为软件从完成编码到可以在客户投产环境成功部署,中间很多地方可能出问题, 编码后必须经过构建(build),单元测试(unit test),集成/系统测试,最终在接近现场客户环境完成验收测试,才有信心能成功部署, 这过程叫部署流程(deployment pipeline)。

图片

从代码提交构建,到最终最终交付并在生产环境使用,把整个流程分成很多小步,每一步必须验证通过才做下一步。要做到持续交付(continuous delivery), 上面那些部署流程的步骤就不能再依赖手动,必须自动化, 一个命令,让自动集成系统(e.g. Jenkins) 自动跑命令脚本(script)执行。版本 / 配置管理也非常重要, 不仅包括代码,也包括例如数据库(DB schema),脚本(script),环境配置(configuration)等 因这些都会可能有变动, 如果出现问题,配置管理可以帮我们立马回滚到之前某个稳定的状态。

软件系统会分成多个子系统/模块,分到几位开发人员并行开发,各模块单必须整合,并通过集成/系统测试,所以持续集成是持续交付的基础。

不要以为每个开发人员每天提交(commit)代码到版本管理系统就算做到持续集成, 也需要:

  • 每次提交代码要确保已经测试过,没有问题

  • 所有部署发布后的问题都能在10分钟之内解决

不然还未算达到持续集成的基本条件。所以每天提交只是持续集成的第一步。

你可能会觉得持续交付太理想,难以达到。实际上有些面对全球客户的互联网公司(e.g. Amazon)已经做到每天部署, 但不要误会持续交付很容易做到,这些成功案例都已经经过多年的不断过程改进,并配合自动化工具,才能做到持续部署(continuous deployment)。

追求持续交付的努力,可以带来以下好处:

  1. 更快速收到客户反馈(因客户可真正使用新功能),不要等到最后才知道功能并非客户所需要

  2. 不仅仅依赖开发人员自己测试通过,开发人员也可以每天收到反馈,不需要等几个月才发现问题,开发团队与其他干系人更有信心软件没有问题

  3. 步伐越小,后面改错的痛苦就越低。也因为每次部署的功能变动不大,可避免几周后一次性先系统测试,然后部署时才发现大量复杂问题的风险 (问题发现得越晚,越需要花更多时间解决)例如,某家专门服务电信供应商管理各种媒体(如:电影、视频、游戏)播放的软件公司,他们每次发布(他们叫割接上线)系统新功能都是在半夜。如果在发布前未能通过验收测试,就都要切回本来版本,不能发布。然后他们会回顾失败,为什么常常出现这种问题。持续交付能减少或避免割接问题。

  4. 更好监控项目进展 –不仅仅听软件工程师说自己已经测试好,而是软件已经能在应用环境运行。例如,某家专门做IT项目的公司发现项目开发的延误很严重,导致项目的利润越来越薄。他们希望能有办法管理好软件开发的成本,保持公司利润。持续交付便可更好监控项目时间进展。

附件

A1: 持续集成(Continuous Integration CI)

基础条件:

  • 要有版本管理

  • 要配合自动化工具,e.g. Jenkins

  • 每当我听到团队还是手工构建,我很奇怪为何不把它自动化,如果我们要持续集成,每天都要构建,包括自动测试

  • 要有团队的赞同

  • 持续集成不仅仅是工具,需要整个团队高度付出参与与纪律,每个人频繁每个小步小步交付他的开发部分。James SHORE 先生在 "Continuous integration on a dollar a day"文章里提出不一定依赖自动集成工具(e.g. CruiseControl 另一种类似 Jenkins的集成工具 ) 只要团队各人都做好自动单元测试,每次提交代码都使用版本管理系统合并, 也能做到持续集成。

如何开始

有很多开源的软件可以下载来用,选好你要用的持续集成(CI)软件后,你就开始要安装使用,希望在利用工具来做自动构建,跟安装其他软件一样,开始的时候会遇到困难,你可以在你项目的wiki记录下来,让其他人知道,避免以后重复错误。接下来,大家可以开始用服务器持续集成:

  1. 当你准备好提交(check in)你的最近更改,要确保它是否能正常运行

  2. 它可以正常运行并通过测试,你应该把你的代码从你的开发环境提交到你的版本管理(Version control repository)系统去,也看有没有更新

  3. 跑构建脚本和测试,确保所有都可以在你的电脑正常操作

  4. 如果你在本地构建成功通过,就可以把你的代码提交到版本管理系统

  5. 让持续集成工具自动构建你提交的更新

  6. 如果不通过,你要尽快在自己的机器上修改问题,返回第三步

  7. 如果构建成功,你就可以进入下一步:完成

如果各成员都按照以上简单步骤,团队便有信心软件在任何电脑,同样配置,能成功运作,一些持续集成的注意点:

  1. 定时不断提交(Check in regularly),可以想象你写了很多代码才发现问题,后面你会花更大精力来找出问题。

  2. 创建全自动化测试套件(Create an Automated Test Suite)。以单元测试为例,很多编码人员觉得写脚本式自动化单元测试浪费时间,宁愿手工单元测试,因他们以为只须要在写完代码后跑单元测试,”证明“代码没有问题。当后面集成/系统测试发现缺陷,只要修改代码的错误部分,并能通过集成/系统测试便可。但修改后的代码还能通过本来的单元测试吗?不一定。所以任何代码修改后,必须再通过整个部署流程(deployment pipeline),里面包括单元测试。如果利用流程自动化实现持续集成(包括单元测试),便可节省用于手工测试的工作量。如果编码人员了解这道理,便不会再说自动化测试浪费时间。(注1)

  3. 保持较短的构建和测试过程(Keep the Build and Test process short),如果可以把测试和构建简短的话,就不会等到一大堆问题才要解决,就像我们把复杂的系统分成几个子系统,模块逐个开发的道理一样。

  4. 管好自己的开发工作区(Manage your development workspace),不要只做好代码的版本管理,配置管理应包括你的测试数据,数据库脚本,构建脚本,安装脚本等,因为每一块都会导致你的软件运作不成功。

(注1: 如想多理解为什么单元测试很重要,可参考“过程改进”公众号分享文章 《TDD 测试驱动开发与精益》)

References

Humble, Jez: Continuous Delivery
Shore, James: "Continuous integration on a dollar a day" (www.jamesshore.com)