
1.2 重构心智模型
根据实际情况定义每个指标很重要。前两个指标是由CI流水线中发生的事情所构成的,而后两个指标需要跟踪服务的中断和恢复。
在执行这种心智重构时,请仔细考虑其范围。是要关注组织中所有软件的所有变更,还是只考虑那些与工作程序相关的?是要考虑基础设施的变更,还是只观察软件和服务的变更?上述所有情况都是合理的,但请记住:所考虑的范围必须在四个指标之间保持一致。如果在统计变更前置时间和部署频率时考虑了基础设施变更,那么由基础设施变更引起的服务中断也应被加以考虑。
1.2.1 流水线作为第一站
你应该考虑哪类流水线?你所需要的流水线是那些监听目标范围内的源码库中代码和配置的变更,执行各种操作(例如编译、自动化测试和打包),并将结果部署到生产环境中的流水线。你不会想对数据库备份这样的任务使用持续集成。
如果你只有一个代码库,并由一个端到端的流水线提供服务(例如,一个存储在单体库中的单体应用,它通过一组活动被直接部署到生产环境中),那么你的工作将会很容易开展。这个模型如图1-2所示。

图1-2:你能找到的最简单的源代码控制/流水线/部署模型
不幸的是,虽然这与我们的基础心智模型完全相同,但我很少在实际情况中遇到。我们很可能不得不对心智模型进行更广泛的重构,才能得到代表实际情况的心智模型。
除了理想模型以外,下一个最容易度量的心智模型重构,是这些端到端流水线的集合。每个制品(artifact)或库对应一个流水线(例如,每个微服务有一个流水线),每个流水线都完成自己的所有工作,且同样以生产环境为终点(见图1-3)。如果你正在使用Azure DevOps之类的工具,那么创建这些流水线就很简单[3]。

图1-3:“多个端到端流水线模型”非常适合微服务
前两种流水线形式很可能与你现有的很相似,但我猜在你的版本中,这个图会稍微复杂一点,需要再重构一次来将其分成一系列子流水线(见图1-4)。考虑如下的例子:它显示了三个这样的子流水线,它们端到端相互适配,将变更交付到生产环境。

图1-4:我经常遇到的“由多个子流水线组成的流水线”模型
也许第一个子流水线在监听到库的推送,并进行编译、打包、单元测试和组件测试,然后将之发布到二进制制品库。也许紧随其后的是第二个独立的子流水线,它将这个新发布的制品部署到一个或多个环境中进行测试。可能还有第三个子流水线,它由类似CAB[4]流程的东西触发,最终将变更部署到生产环境。
希望你已经明确了自己的实际情况。但如果没有,这里还有第四种主流类型的流水线。心智重构的最后一个步骤将带我们进入多阶段扇入型流水线,如图1-5所示。这里我们通常会在第一阶段找到多个独立的子流水线,每个代码库一个,然后将它们“扇入”一个共享的或一组子流水线,这些子流水线通过之后的流程将变更发布到生产环境。

图1-5:多阶段“扇入型流水线”模型
1.2.2 定位检测点
除了四个指标,我们还有四个检测点。无论心智模型采用什么形式,现在把检测点先定位到我们的通用心智模型中。到目前为止,我们一直专注于流水线,这是因为流水线通常能提供两个检测点:提交时间戳和部署时间戳。而第三个和第四个检测点则来自探测到服务降级时创建的时间戳,以及服务降级被标记为“已解决”时创建的时间戳。现在,我们来详细讨论一下这些检测点。
提交时间戳
当你开始考虑团队的实际工作实践时,微妙之处就不可避免地出现了。团队是按特性拉分支的吗?做拉取请求(Pull Request)了吗?组合不同的实践了吗?理想情况下(正如Accelerate的作者所建议的那样),无论哪里出现了变更,只要有开发人员完成代码编写并进行提交,创建了提交时间戳,计算变更前置时间的时钟就开始计时了。如果团队确实是这样做的,请注意:留存在分支(而非主干)上的变更不仅延长了反馈周期,还会给你的工作增加开销并提高对基础设施的需求。(我将在下一节介绍这些内容。)
基于上述复杂性,有些人选择当代码合并到主干时触发流水线,以此作为代理触发点或提交时间戳。我知道这听起来像是在面对非最佳实践时承认了自己的失败[5],但如果你的确选择了代理触发点,我也明白你会感到羞愧(因为你知道自己没有遵循标准的最佳实践)。即使你对自己放松了要求,选择在代码合入主干时再开始采样计时,无论是否将额外的等待时间包含在内,这些指标都会给你带来许多其他好处。当这些代理触发点真的成为导致非最佳交付实践的主要原因时,请参考Accelerate提供的相应改进建议(例如基于主干开发和结对编程等[6]),这些建议支持将变更合入主干的时间点作为计时起点,即提交时间戳。之后,你将开始发现这些指标的好处,并希望改进捕获指标的方法。
部署时间戳
有了提交时间后,你会愉快地发现“停止”计时要简单得多:当流水线完成在生产环境的最终部署时,计时即可停止。你可能会疑惑,这种计时方式不是让发布后进行手动冒烟测试的人提前放假了吗?的确是的,但我还是把这个问题留给你,如果你真的想把这个最后的活动包含进去,你可以在流水线的终点处放一个手动检查点,QA(或者其他验证部署结果的角色)只有在对部署感到满意之后才会按下这个检查点按钮。
多阶段扇入型流水线带来的复杂性
给定上述两个时间戳数据,即可从流水线中计算出需要的信息,即运行总时间—从计时开始到计时停止所流逝的时间。如果流水线是我们之前讨论过的没有扇入的简单流水线,那计算就相对容易多了。那些有一个或多个端到端流水线的场景是最简单的[7]。
如果你不走运,拥有多个子流水线(见图1-4),那么你将需要对从“开始”时间戳到“部署”到生产环境的整个过程中包含的变更集执行额外的数据收集。有了这些数据,才能通过一些处理来计算出每个单独变更的运行总时间。
如果你运行着一个扇入型流水线(见图1-5),那么这个处理可能会更加复杂。为什么呢?如图1-6所示,你需要通过某种方法来了解第264号变更部署的确切来源(库A、库B还是库C),以便获得变更的“开始”时间戳。如果一次部署聚合了多个变更,那么你需要单独跟踪每一个变更以获得它的“开始”时间戳。

图1-6:在“扇入型流水线”模型的变体中定位数据收集点
显然,无论在何种情况下,无论流水线有多复杂,你只想统计那些向实际用户部署服务更新的构建。一定要确保你只度量这些构建[8]。
在继续之前,关于从流水线中捕获数据还有最后一点要说明,那就是哪些流水线的运行需要被统计。不出意外,Accelerate在这一点上也没有明确说明,但实际上我们只需要关心那些运行成功的即可。如果一个构建在编译步骤失败了,那么它会把前置时间向一个人为的积极方向偏斜,因为你恰好混入了一个非常快速的构建。如果你想闹着玩(这四个关键指标的最大好处是它们会被严肃对待,至少据我所知的是这样),那么只需要提交大量已知会崩溃的构建,理想情况下,这样就会让前置时间变得真的很短。
监控服务故障
虽然围绕着流水线本身的准确度量相对简单,但对第三个和第四个检测点的原始信息的来源,其解释就开放多了。
难点在于如何定义“生产故障”。如果有一个故障,但它并没有被人发现,那它真的存在吗?每当我使用这四个关键指标时,我的回答都是否定的。我将“生产故障”定义为任何使服务的消费者无法甚至不愿意坚持完成他们试图做的工作的东西。表面的瑕疵不算服务故障,但如果“工作系统”缓慢到让所有用户放弃使用,那显然就是在经历服务故障了。这里有一个不错的判断因素:选择一个让你舒服的定义,坚持下去,并在这一过程中对自己尽量诚实。
现在你需要记录服务故障,这是我们的第三个和第四个检测点,这通常可以用“变更故障单”来实现。开启这张故障单时就创建了一个开始时间点,而关闭故障单时就提供了相应的结束时间点。开始时间和结束时间加上故障单的数量,就是你需要的所有剩余数据点。当服务恢复时,故障单应被关闭。虽然这可能与要解决的故障的根因不符,但也没关系。我们讲的是服务稳定性,只需对服务进行回滚,恢复在线服务,客户就已经能接受了[9]。
但是,如果服务不在生产环境中呢?首先,你还没尝试过持续部署吗?你真的应该这么做。其次,这种选择并非对每个人都有用。它不是最优的选择,但你仍然可以在这些场景下使用这四个关键指标。要做到这一点,你需要定义“最高环境”:它是所有团队都在交付的最接近生产环境的共享环境。它可能被称为系统集成测试环境(SIT)、预生产环境(pre-prod)或预览环境(staging)。关键是,当接受变更后,你得确信在这些变更到达生产环境的最后一步之前,不再需要做更多的工作了。
考虑到所有这些因素,你需要像对待生产环境一样对待这个“最高环境”。将测试人员和协作团队视为“用户”。他们可以定义服务故障。像对待真正的故障一样对待测试故障。假装这种环境是生产环境并不完美,但这总比什么都没有好。