为什么要做持续部署?
本文是《Lean Startup》一书的作者Eric 在2009年发表的一篇博文 ,他是IMVU的创始人之一。文中没有讨论如何做持续部署,而是讨论了一个更关键的问题:“IMVU为什么要做持续部署?”这也充分地表达了他关于“Learning from production and customer”的观点。
----------------------------------------
在我所倡导的Lean Startup 所有实践中,没有哪个实践比持续部署 更有争议(持续部署是指:让公司在几分钟内发布软件的过程,而不是几天或几个月才发布一次)。我之前所在的一个创业公司IMVU 使用这个流程,平均每天部署50次 。这也引发了一些争论,有些人说:这种快速发布流程会导致低质量的软件,或者阻碍公司的创新。假如我们能够接受由客户来评判,而不是专家的判定,那么,我想这些说法就很容易消散了。一个更为常见,且更为困难的问题是:如何回答那些只想知道这种持续部署是否可以用于他们自己的业务、行业或团队的人们。
IMVU的历史尤其引人关注。作为一个有数百万用户消费者的互联网公司,看上去可能与那些只有一小撮潜在客户的企业软件公司,或者那些客户要在软件发布前需要严格审核的计算机安全公司没有太多关系。我想,持有这种反对观点的人实际上没有真正明白持续部署的关键点,因为他们关注点都放在了具体的实现上,而不是通用原则。目前关于持续部署的文章都关注于“如何做” , 但我在这里想说的是“为什么做?”(如果你想了解如何开始做持续部署,请参见”五步实现持续部署 “)
持续部署的目标是通过减少批量工作的大小, 并加快团队工作的节奏,帮助开发团队在其开发流程中消除浪费。这使团队能够一直处于一种可持续的平稳流状态, 让团队更容易去创新、试验,并达到可持续的生产率。而且,这也很好地支撑了其它的持续改进系统,如5个Whys 。
在开发中,浪费的一个最大来源是“double-checking”。想像一下,在传统瀑布开发环境中的一个团队,没有持续部署、测试驱动开发或者持续集成。当开发人员想要提交代码时,就到了一个令人担心的时刻。他或她有两种选择:一是马上提交,二是再检查一下,确保不会出问题。两种选择都很有吸引力。假如马上提交的话,就可以说“提前完成任务了”。可是,如果提交后引起了问题,那么之前的工作速度必然要打折扣。他们为什么不再多花五分钟时间,确保自己的提交不会导致问题呢?事实上,开发人员如何应对这种问题是由他们的激励机制决定的,而激励机制是由团队文化决定的。导致问题后会受到多么严重的惩罚?谁最终会承担这些错误带来的成本?时间表有多么重要?团队是否以尽早完成工作来做评价?
然而,在这种情况下,我们应该意识到,其实并没有所谓正确的答案。被这种选择性所折磨的人最终很可能哪种都做不好。结果,开发人员会走向两个极端:一些人认为应该尽快地完成事情,另一些认为应该细心地做工作检查。从长远来看,二者之间的任何一个中间状态都无法长久。一旦出了问题,无论你怎么去细心解释当时是如何做决定的,都不会令人满意。毕竟,你要么可以做得更快一些,要么可以做得更细心一些。要是你能提前知道问题多好呀啊?!事后再回头看看当时的那些评判,它们好象都存在问题。然而,从另一个角度上看,每种极端的做法都很容易起到自我保护作用。两种方式都有借口:“的确是有几个bug,但我一直在以一个安排得非常紧的时间表来完成任务,有几个bug也是再所难免的。”或者,“我知道你想快点完成这个事儿,但你要知道,我要确保绝对没问题的情况下才能交付给你,所以等一等是值得的。”
这两个做法在开发团队中会发”派系”冲突,可能产生很多不愉快。经理开始注意到,哪类人在哪个小团队中,然后再依此来分派任务。当在最后一分钟拿到了某个新功能的请求,先找个牛仔完成它—— 然后,在下一个发布中再找个人(Quality Defenders)来”擦屁股”。两边都开始从他们各自的视角来思考:“那些家伙没有看到快速行动带来的经济价值,只在意他们那完美的架构图”,又或者, “那些家伙太懒了,根本没有专业精神。” 在我的职业生涯中,经常被找来调节双方的矛盾。在我看来,这些都真是太浪费啦。
【评注:文中提到的两种现象我很有同感,有些原本可以避免的问题却要靠人员的自觉性来完成,如果素质高当然好了,但是不免因为工期紧一着急就提交的情况。更为重要的是“导致问题后会受到多么严重的惩罚?谁最终会承担这些错误带来的成本?时间表有多么重要?团队是否以尽早完成工作来做评价?”,说到“惩罚”从操作层面来看确实不太好衡量(比如有些功能是因为开发者水平所限确实没能考虑到,你能因为水平不同就去惩罚吗?当然也不乏有些人是图省事);所谓“错误的成本”,这个太有感触了,改了一个Bug又引入一个新的等于没改;“时间表”这个就不评论了;最后如果仅以“速度”作为衡量标准未免有点太SB了吧(不过对于不太关注细节或者本身技术不是非常过硬的领导来说,这种现象确实是有可能发生的)】
【评注:实际上我感觉很多时候我们的开发有时候都是在“擦屁股”,最开始开发者图省事,很多因素没考虑到导致最终产品加上真实数据后出现一大堆Bug。我感觉这种事情干多了不但降低团队的工作效率,而且对士气也是有影响的(极端一点的想的话:你自己当初做的时候不仔细考虑全面弄出一对漏洞凭什么让别人给你擦屁股)】
其实,上述这种结果是完全符合逻辑的,因为对于那种大批量生产的开发流程来说,它迫使开发人员利用传统的“时间-质量-金钱,选其中两个的谬论 ”,在时间和质量之间进行权衡。因为得到的反馈很慢,所以由于某个错误引发的损坏和做出决定之间有很长的时间,这也使人们很难从中学习。因为每个人都在最后的某个时间点同时做整个发布的集成工作(并没有尽早集成的激励机制), 要在很大的时间压力下解决该集成过程中中发现的冲突。某些特性像泡沫一样,看上去做好了,很美,但不得不等到下一次发布才行。然而,一旦这些特性被推迟了,就会增加下次发布的工作范围。(“毕竟,我们有完整的发布周期,而这个特性几乎就要完成啦…”),这就会导致下一个时间压力等等。然而,在生产环境中代码运行的方式与其在测试或试运行环境中并不完全一样,这也导致每次发布之后马上就会有一系列的hot-fixes。这也会增加下一次发布的工作量,也就意味着每个发布周期从一开始就已经落后了。
有很多次,当我访谈一个遇到这种情况的开发团队时,他们想让我帮助“fixing people”。这种现象心理学上叫做“基本归因错误 ”,人们倾向于认为其他人的行为来源于其基本属性,如他们的个性,道德准则,或士气 –甚至当受到所在环境的影响时,我们也会为自己的行为找借口。所以,在这种环境下的开发人员,他们的心灵深处也会认为其团队的其他开发人员也是行动缓慢的老学究或邋遢的码农。事实并不是这样的,他们只是让他们的动机搞砸了。
你无法通过把所有工作都做得非常完美,来改变这种状况下的根本动因。做更靠谱的发布计划、估算、架构或者集成只能缓解症状。解决这种问题的一种传统做法是:在时间排期表中预留更多的空槽,增加额外的时间做集成,代码冻结,或类似的事情。事实上,大多数组织并不知道,在做时间估算时,开发人员到底已经为这种事情加入了多少Buffer。但是,这种缓冲没什么好处,因为,它只会让整个过程变得更慢。正象所有开发人员都会对你说:“时间太短啦”。实际上,额外的时间压力正是他们认为他们会遇到这些问题的原因。
【评注:在开发人员水平有限的前提下,临近的发布的时候不但要解决各种新提出的Bug同时还要花费精力去搞定验证更新和部署的话,出错的几率绝对要比平时大得多。而在领导眼里他会觉得“这么简单的事情怎么让你们搞得那么复杂,而且还中延期!”】
所以,我们要站在整个系统层面上找到解决方案,让团队冲出这种束缚。敏捷软件开发已经做出了巨大的贡献:持续集成帮助加快速了对缺陷的反馈速度;用户故事和看板减少了批量生产的大小;每日站会增加了节奏感。持续部署是另外一种类似的技术,它具有强大的动力让开发团队变得更好。
所以,我们要站在整个系统层面上找到解决方案,让团队冲出这种束缚。敏捷软件开发已经做出了巨大的贡献:持续集成帮助加快速了对缺陷的反馈速度;用户故事和看板减少了批量生产的大小;每日站会增加了节奏感。持续部署是另外一种类似的技术,它具有强大的动力让开发团队变得更好。
持续部署为什么会起作用?
首先,持续部署将两种不同的“发布”概念区分开来。一种是工程师所说的:将代码部署到生产环境的过程。另一种是从市场人员的角度来看的:让用户看到。在传统的“批量及排队”这种开发模式中,两种概念是相联的。一旦新版软件被部署了,所有客户也就能看到它了。这就要求在部署之前,我们要在特定的试运行或测试环境中完成本次发布相关的所有测试。这就使得从写完代码开始,到生产环境部署这之间的这段时间里,由于某些未曾预料到的问题而令本次发布变得不可控。在这些开销中,由于市场发布与技术发布的合并,也让交付活动的协作开销急剧增加。
而在持续部署环境下,一旦代码写完,就开始向生产环境进发。也就是说,我们经常只部署某个特性百分之一的功能,尽管客户可能要在很久之后才能看到它。事实上,一个新特性的绝大部分工作都是用户无法看到的。相反,这个特性与之前已完成的特性之间却有很多集成点。想像一下,当我们想要在系统中传递一个新增的参数时,就需要修改多个API。这些修改通常应该没有“副作用”,也就是说,它们不会影响当前系统的行为——这里要强调一下“应该”两个字。实际上,很多缺陷是由于那些修改中不常见或没有意识到的副作用引起的。 对于那些只是影响生产环境中的配置参数的很小的修改来说,也是一样。尽快得到这种反馈是最好的,而持续部署恰好提供了这样的途径。
持续部署也扮演了速度调解器的角色。每当部署流程遇到了一个问题时,就需要有人去诊断。在诊断期间,其他人就不能进行部署了。当团队已为部署做好准备,但部署流程被阻塞时,他们就可以马上去帮助诊断并修复部署问题。(相反的做法是团队其他人继续去写更多的代码,但是并不部署,那么这些新代码就会不断堆积,使批量变大,这对每个人都是一种损害)。对于那些通过度量个人效率来衡量其流程的团队来说,通常这个速度调解器是一种比较棘手tracky的调解。在这种环境中,每个工程师的主要目标是保持忙碌状态,写代码的时间尽可能地接近。不幸的是,这种视角忽视了团队的整个产出。即使你不采纳我所提倡的激进定义,比如“validated learning about customers ”,让每个人都保持忙碌状态仍旧是局部优化。当你正在追踪和修复集成问题时,其它人写的代码很可能会由于冲突而不得不回滚。配置不匹配或多团队之间的冲突都可能会怒导致同样的结果。在这种情况下,对于整体生产率来说,最好是大家停止编码,开始讨论。一旦找到如何进行协作,以便不会导致工作重来,再开始编码,这样生产率才高。
再回到我们之前讨论过的两种类型开发团队成员(牛仔和保质派),看看持续部署如何改变他们所处状况的症结。首先,持续部署对于双方来说,都能促进学习和专业地开发。双方不必争论哪种是写代码的正确方式,而是每个人都可以直接从生产环境上得到学习的机会。这就是“让错误成为你的老师”。
如果一个工程师倾向于快速交付,他们可能很快发现自己被集群免疫系统(cluster immune system ,由持续集成服务器和5个 whys 等组成)逮到。与传统团队的那种高风险问题相比,此时遇到的问题风险都很小,大多数仅影响自己或者比较小的范围。由于反馈迅速,牛仔们开始意识到,哪些测试、准备和检查工作的确会让他们工作得更快。他们就会知到原来还有这样的东西要可以这么快反馈。
但是,对于工程师来说,总是有一种在交付前希望等待很长时间的趋势。对于这样的人,批量工作越大,集成就越难。在IMVU,我们偶尔也会招聘到一些从非常传统的组织里出来的工程蚰,他们在那样的公司里已经养成了那里的“最佳实践”和习惯。有时候,他们希望自己使用自己的分支进行工作,并在最后再做集成。尽管我总是会尽最大的努力去说服他们不要那么做,但如果他们坚持要那样的话,我也会鼓励他们试一下的。最终在一两个星期之后,我很高兴地看到他们还是回到了我所谓的“code bouncing”的主流。这就好比向墙上扔一个橡皮球。在一个code bouncing的环境下,如果有人试图一次性提交一个大版本,他们首先就会遇到集成冲突,这就要与团队中的很多成员进行沟通,了解如何恰当地解决这些冲突。当然,当他们正在解决冲突的时间,又会有新的提交,所以新的冲突又会出现了。这个循环一直重复,直到解决所有的冲突,或者要求团队的其他人员先不要提交代码。 然后,更有趣的事情开始了。把这一大堆修改扔到了持续集成服务器上,第一次一定会令增量部署系统和实时监控系统趴窝。所以,这一大包修改就被回滚了。然而,当这些问题被解决时,已经有更多新的修改被提交了。除非我们冻结整个团队的工作,但这有可能会持续几天的时间。假如我们全面要求做这种提交冻结的话,那会令其他人的工作也形成堆积,这就会进一步导致连续的code bouncing。根据我的经验,只要一两次这样的事情就足以让那些想做批量提交的同学回心转意啦。
由于持续部署鼓励学习,随着时间的推移,使用这个实践的团队会越来越快。那是因为每个人的动机都与团队的目标一致。每个人都在工作中消除浪费,这种效率提升得到的收益将会大于那些因为要做持续部署而需要构建和维护的基础设施而增加的开销。事实上,假如你也经常使用5个Whys 的话,你也可以用一种完全增量的方式建立这种基础设施。这个过程真的非常有意思。
最后,还有一个收益,那就是“士气”。曾经有一个人问我:持续部署是否会对士气产生不良影响。他是一个经理。他担心做这种更快速的发布会令工程师感到更大的压力,让他们觉得自己一直在救火和发布,根本没有时间做“真正的工作”。有一个IMVU的工程师回答了这个问题,我认为,他给出的回答比我的更好。他解释说,通过减少做每次发布的开销,每个工程师都可以有他个人的发布时间表。也就是说,只要他们已经准备好部署了,就可以部署。所以,即便是在半夜,如果你的特性做好了,你也可以提交、部署并马上告诉客户这个新特性。没有额外的申请审批、会议,或者协作要求。只需要有你、你的代码和你的客户就行了,听上去相当不错。
而在持续部署环境下,一旦代码写完,就开始向生产环境进发。也就是说,我们经常只部署某个特性百分之一的功能,尽管客户可能要在很久之后才能看到它。事实上,一个新特性的绝大部分工作都是用户无法看到的。相反,这个特性与之前已完成的特性之间却有很多集成点。想像一下,当我们想要在系统中传递一个新增的参数时,就需要修改多个API。这些修改通常应该没有“副作用”,也就是说,它们不会影响当前系统的行为——这里要强调一下“应该”两个字。实际上,很多缺陷是由于那些修改中不常见或没有意识到的副作用引起的。 对于那些只是影响生产环境中的配置参数的很小的修改来说,也是一样。尽快得到这种反馈是最好的,而持续部署恰好提供了这样的途径。
持续部署也扮演了速度调解器的角色。每当部署流程遇到了一个问题时,就需要有人去诊断。在诊断期间,其他人就不能进行部署了。当团队已为部署做好准备,但部署流程被阻塞时,他们就可以马上去帮助诊断并修复部署问题。(相反的做法是团队其他人继续去写更多的代码,但是并不部署,那么这些新代码就会不断堆积,使批量变大,这对每个人都是一种损害)。对于那些通过度量个人效率来衡量其流程的团队来说,通常这个速度调解器是一种比较棘手tracky的调解。在这种环境中,每个工程师的主要目标是保持忙碌状态,写代码的时间尽可能地接近。不幸的是,这种视角忽视了团队的整个产出。即使你不采纳我所提倡的激进定义,比如“validated learning about customers ”,让每个人都保持忙碌状态仍旧是局部优化。当你正在追踪和修复集成问题时,其它人写的代码很可能会由于冲突而不得不回滚。配置不匹配或多团队之间的冲突都可能会怒导致同样的结果。在这种情况下,对于整体生产率来说,最好是大家停止编码,开始讨论。一旦找到如何进行协作,以便不会导致工作重来,再开始编码,这样生产率才高。
再回到我们之前讨论过的两种类型开发团队成员(牛仔和保质派),看看持续部署如何改变他们所处状况的症结。首先,持续部署对于双方来说,都能促进学习和专业地开发。双方不必争论哪种是写代码的正确方式,而是每个人都可以直接从生产环境上得到学习的机会。这就是“让错误成为你的老师”。
如果一个工程师倾向于快速交付,他们可能很快发现自己被集群免疫系统(cluster immune system ,由持续集成服务器和5个 whys 等组成)逮到。与传统团队的那种高风险问题相比,此时遇到的问题风险都很小,大多数仅影响自己或者比较小的范围。由于反馈迅速,牛仔们开始意识到,哪些测试、准备和检查工作的确会让他们工作得更快。他们就会知到原来还有这样的东西要可以这么快反馈。
但是,对于工程师来说,总是有一种在交付前希望等待很长时间的趋势。对于这样的人,批量工作越大,集成就越难。在IMVU,我们偶尔也会招聘到一些从非常传统的组织里出来的工程蚰,他们在那样的公司里已经养成了那里的“最佳实践”和习惯。有时候,他们希望自己使用自己的分支进行工作,并在最后再做集成。尽管我总是会尽最大的努力去说服他们不要那么做,但如果他们坚持要那样的话,我也会鼓励他们试一下的。最终在一两个星期之后,我很高兴地看到他们还是回到了我所谓的“code bouncing”的主流。这就好比向墙上扔一个橡皮球。在一个code bouncing的环境下,如果有人试图一次性提交一个大版本,他们首先就会遇到集成冲突,这就要与团队中的很多成员进行沟通,了解如何恰当地解决这些冲突。当然,当他们正在解决冲突的时间,又会有新的提交,所以新的冲突又会出现了。这个循环一直重复,直到解决所有的冲突,或者要求团队的其他人员先不要提交代码。 然后,更有趣的事情开始了。把这一大堆修改扔到了持续集成服务器上,第一次一定会令增量部署系统和实时监控系统趴窝。所以,这一大包修改就被回滚了。然而,当这些问题被解决时,已经有更多新的修改被提交了。除非我们冻结整个团队的工作,但这有可能会持续几天的时间。假如我们全面要求做这种提交冻结的话,那会令其他人的工作也形成堆积,这就会进一步导致连续的code bouncing。根据我的经验,只要一两次这样的事情就足以让那些想做批量提交的同学回心转意啦。
由于持续部署鼓励学习,随着时间的推移,使用这个实践的团队会越来越快。那是因为每个人的动机都与团队的目标一致。每个人都在工作中消除浪费,这种效率提升得到的收益将会大于那些因为要做持续部署而需要构建和维护的基础设施而增加的开销。事实上,假如你也经常使用5个Whys 的话,你也可以用一种完全增量的方式建立这种基础设施。这个过程真的非常有意思。
最后,还有一个收益,那就是“士气”。曾经有一个人问我:持续部署是否会对士气产生不良影响。他是一个经理。他担心做这种更快速的发布会令工程师感到更大的压力,让他们觉得自己一直在救火和发布,根本没有时间做“真正的工作”。有一个IMVU的工程师回答了这个问题,我认为,他给出的回答比我的更好。他解释说,通过减少做每次发布的开销,每个工程师都可以有他个人的发布时间表。也就是说,只要他们已经准备好部署了,就可以部署。所以,即便是在半夜,如果你的特性做好了,你也可以提交、部署并马上告诉客户这个新特性。没有额外的申请审批、会议,或者协作要求。只需要有你、你的代码和你的客户就行了,听上去相当不错。
【评注:我觉得最好的效果应该是让发布不再由某个人或某几个人负责,而是完全的自动化。只要你有权限向代码库提交代码你就有“一键部署”的能力,系统应该达到这种水平。当然了,难度肯定是有的,不过作为开发者,我们应该更关注业务而不是开发过程本身,如果我们每天工作那几个小时大部分时间都纠结在过程中,你有再多的好想法恐怕也会被慢慢磨掉甚至是忘掉】