如何加速软件部署?

2020 年 11 月 25 日

如何加速软件部署?

敏捷及其相关的实现技术,是亘贯过去十数年乃至更长时间的一个聚焦话题。尽管 CI/CD、DevOps 业已成为敏捷的关键特性,但在实际部署中还是免不了磕磕绊绊之处。每次部署都独具其神奇之处,影响团队在每次软件交付中的工作量。

为什么要快速部署?


无论系统架构是多么的漂亮,代码是多么的优雅,测试套件是多么可靠,但唯有真正地将代码部署到生产环境中,我们的工作才能对客户产生实质影响。代码在部署之前,只是一些与面试问答毫无二致的智力活动。部署才能将这些知识产权转变为经济活动。毫无疑问,代码应尽快得到部署。


然而,工程团队却在为此苦苦挣扎。大量有据可查的事实表明,绝大多数的宕机事故都是由部署新更改造成的。道理的确如此,如果什么都不改变,那么发生事故的可能性也就大大降低。因此,部署代码明显是存在风险的。


绝大多数当下流行的工程实践,目的都是在提高软件部署速度的同时,降低因部署加速而引发的故障风险。这些实践大多在小型企业畅通无阻,但是当软件或团队规模随着企业发展壮大而持续增长时,具体场景的重要性就愈发显现。从开发回溯到设计和实现的逆向工程,会暴露出大量的低效之处。


软件的高质量快速部署是现实的目标,基于这一认可,本文将针对软件交付过程提出流程化的观点,识别并疏通瓶颈,提高流程效率。约束理论(Theory of Constraints,TOC)在其中的应用,意味着可对软件的交付流程做逆向建模,进而识别瓶颈,并逐一做优化。

部署会导致服务宕机

正如前文提到的,在生产环境中部署变更的最大问题是会导致宕机,行业应用数据也充分说明了这一观点。由此,大家心照不宣地将部署视为某种神圣活动。但在问题的表面之下,存在大量值得深挖的潜台词。


尽管这一庞大话题本身就涵盖方方面面,但如果最终目标是快速而安全的部署,那么下面列出有效手段是不可或缺的:


  1. 确定问题的发生时刻:为减少宕机,首先需要具备检测问题的能力。为此,监控和报警工具是必不可少的,生产环境中运行的自动化测试工具也非常有效。无论是否经常做部署,这些工具都是不可或缺的,因此不妨未雨绸缪并从中受益。此外,机构最好签一家可靠的随叫随到服务商以应对报警。服务最好是由开发人员提供,至少也应是某种形式的运维服务中心。

  2. 降低问题的发生可能性:针对由部署导致的宕机,金丝雀部署和自动回滚是降低问题发生的最有力工具。结合监控工具使用,可提供一张相当牢固的问题防范安全网。特性开关(Feature gate)是另一种非常有用的部署风险管控工具,用它可以在部署后让代码在密切监控下启用,而不是立刻上线运作。

  3. 调试问题:无论是通过日志度量追踪实现可观察性,还是 通过基于事件的方法获得洞察,都需要有效的工具,帮助当事人和团队快速敲定问题的致因。缺少此类工具,就无法确定问题的来源和致因,而束手无策。

  4. 修复问题:一旦发现问题,产品和开发团队就需要制定出流程,确定修复的优先事项,尽快着手进行修复。需要注意的是,我们需要具备所有事物的快速部署能力,才能快速部署错误修复。

部署需要一定的时间

在深入问题细枝末节之前,需要指出的是由于功能切换(feature toggle)的存在,部署并不等同于特性发布。某个功能的实现代码可能已经得到部署,但因为有功能切换,它无需立刻生效。下面深入探讨功能切换的概念。


我曾经共事过的一些团队是反对做频繁部署的,因为部署会占用团队大量的时间。下面列出部署中通常涉及的各个步骤,并给出高效的实现方式。


  1. 合并所有代码到部署分支:无论出于何种考虑,合并通常(但并非总是)由正好要部署应用的工程师完成的。事实上不应该这么做。将代码合并到部署分支是开发周期中的一部分,但常常会作为部署周期中的一部分。因为部署分支中的所有代码一旦部署,就会变为“Active”状态。打破这种依赖关系,可使用上文提及的功能切换。实现代码合并时,开发人员应该适当使用功能切换。

  2. 部署的触发和监控:尽管 CI/CD 是放之四海而皆准的黄金准则,但更好的权衡做法是建立自动化部署流程,其中包括建立部署分支、运行自动健康性测试和集成测试、自动进行金丝雀部署回滚,进而实现滚动的增量部署。尽管这些过程在不同的基础架构上耗时不同,但它们无疑是为开发团队增添了帮手。只有在出现问题时,才需要开发人员参与。

  3. 部署后的系统稳定性验证:这在本质上属于在生产环境中对由部署所导致的宕机故障进行检测和调试,在上文中已经给出了高效实现的建议。考虑到部署并非功能发布,这里需要关注的另一个问题是交付代码的实际发布。功能发布应该由单个功能的开发人员负责,由他去判定何时使用功能切换将代码功能发布给用户。由此,稳定性测试并不属于部署周期中的问题。

测试需要一定的时间

我在供职于 Myntra 供应链团队时曾身体力行了端到端的测试。任一功能只有能够完整地通过从客户购物车到物流的一整套不同类型的订单时,才算通过质量检查。测试环境常常被未通过测试的代码破坏,结果让整个过程对开发人员来说过于漫长,对测试人员则过于痛苦。疏通测试中的瓶颈,通常需要从两个方面着手:


  1. 单个变更的独立测试:快速部署,意味着变更也会快速出现,每个完整的功能,可能涉及多个团队和系统的变更。沿着此技术路径,质量检查移交是非常低效的。我认为单个变更的独立测试应属于开发过程。开发人员无论是采用单元测试,还是采用自动或手动的集成测试,都应自行验证自己的变更对部署是否安全,特性是否符合设计(包括特性切换)。对于测试小的更改和独立组件,消费者驱动的契约(Consumer-driven contracts) 是一种强大工具,但令人惊讶的是其并未得到被广泛使用。

  2. 完整功能的测试:测试完整的用户体验,应该是由质量控制团队负责的,并严格地执行自动化驱动。尽管完整功能测试是测试过程的重要组成,但也应看到它是与主线软件交付路径平行的,运行时略微落后于最新的生产系统的。在 Myntra 时,完整功能测试是安排在交付路线中的,因为当时我们在测试单个变更上做得并不好。这项测试是非常强大的回归检测工具,并且可用于获取系统期望运行方式的信息。


要疏通测试中的瓶颈,需区分上述两种路线,让不同的团队负责不同路线。

功能的整体部署

功能开发通常以批处理方式进行。开发人员设计完整的功能,然后一次实现整个设计。在某种程度上,完整功能是开发人员的工作单元。这对于规模不大的功能尚且适用,一旦处理涉及应用中多个部分和层次的大型功能,就需要多名开发人员协同工作,而这一工作方式会带来巨大的风险。如果在部署前就汇总所有的变更,那么将会导致大量的冲突,而且很难预测是否所有的功能依然正常工作。


至此,本文尚未探讨敏捷过程中的 Sprint Story 问题,这是一个更底层的问题。如果代码变更本身就是独立的(例如在数据库中创建新表导致的模式变更、尚未通过 API 公开提供的核心业务逻辑),那么导致其无法部署的原因在哪里?阻止变更部署,会导致如下两方面问题:


  1. 协同开发代码的团队成员并未看到变更,导致开发中互相磕碰不断。尽快部署变更,可以避免代码在后期发生混乱。

  2. 如果变更合乎逻辑并可部署,那么为什么不做部署呢?毕竟部署的变更越大,部署时发现问题的风险就越大。


但是,实现细粒度功能需具备一些先决条件。


  1. 宏观设计,微观实现:前文提及整体看待功能会导致问题。但是要将此工作细化,需要哪怕是粗略地对整个功能做出设计,以确保每个细分可以正确地实施。这里的输出类似于“我们需要一个表存储这些数据点,需要 REST API 将单个乃至批量记录插入到表中,并需要集成外部服务 X,实现插入前检查 Y 条件”。我们需要大体上识别功能实现所影响到的系统边界,并识别将要应用的变更。这里所谓的大体上,其实是一个递归细化的过程。如果功能很大,涉及多个系统,那么需要继续细分直到具体的代码(如果系统需要实际编写代码)。

  2. 深入测试小变更:由于每个变更很小,开发人员不难确保每个变更按预期工作。实现此的最快机制是通过单元测试,去模拟外部依赖情况。任何能建立部署安全和变更发布预期行为的机制,均可适用。在代码审核中,应关注此类测试。

  3. 快速推进的小规模审核:pull 请求并不涉及功能变更。作为设计工作的产出,可以依照上面定义的设计边界,实现为一系列非常小的 pull 请求和代码审核请求。不同于小请求,大的 pull 请求并不会得到彻底的审查,因为需要耗费审查者过多的精力。从另一方面看,应制定一个团队流程,实现代码的快速审核。可以参考我已发布的一些分布式系统代码的审查准则:https://kislayverma.com/programming/code-review-checklist-for-distributed-systems/ 。


完成上述改进后,部署流程就转化为一些非常小的、经验证是安全的变更,并且其中大部分是已经自动化的,需要最少的人工干预。但是,如果我们坚持分批推送大变更,很难找到可以快速、安全地实施部署的自动化方法或工具。较大的变更集会导致不好的测试和审查,进而导致系统不稳定。

视软件交付为变更流

我的一次思想上的转变,就是将软件交付视为一系列针对实现功能的小变更,而不是将交付看成是将功能从开发人员笔记本转到生产服务器。并不存在所谓“已完工”的功能,所有功能都是不断地发展和变更的。因此,功能不应视为我们交付的一组静态绑定产品,而应视为一组需要在系统某处边界上进行的变更。正如一些观点提出的说法,功能是软件组织中的工作“流”。要实现这一目标,一种做法是在开发过程中采用敏捷技术,另一种做法是在部署和运营阶段基于现成的基础架构功能进行开发。

太长,不读(TL,DR)

尽可能频繁地部署。无论当前的部署成本如何,成本只会越拖越大。除非所要构建软件是生死攸关的,或是受到严格监管的,否则我认为应该尽快做部署。根据破窗理论,以快速部署为工程目标,会直接导致形成强大的工程文化和实践。从长远来看,这对机构也是十分有益的。


原文链接:


https://kislayverma.com/agile/how-to-speed-up-software-delivery/


2020 年 11 月 25 日 14:45995
用户头像
陈思 InfoQ编辑

发布了 555 篇内容, 共 189.6 次阅读, 收获喜欢 1064 次。

关注

评论

发布
暂无评论
发现更多内容

架构师训练营第1周_学习总结

chinsun1

架构总结

解决出海网络难题 融云保障 MiniJoy 千万印度用户流畅互动

Geek_116789

食堂就餐卡系统架构设计

dj_cd

极客大学架构师训练营

食堂就餐卡系统设计

LEAF

架构师训练营 No.1 周作业

连增申

架构师训练营-学习总结-第一周

ashuai1106

学习 架构师 极客大学架构师训练营

老当益壮的 Servlet

侯树成

Java Java 25 周年 Servlet

为什么建立自己的规则很重要

Neco.W

自我管理 行动派 执行力

低调的网易又要上市了

池建强

创业 网易 慢公司

架构课程心得

dj_cd

极客大学架构师训练营

剖析Golang Context:从使用场景到源码分析

伴鱼技术团队

golang 源码分析 并发编程 程序语言 Context

8000字长文让你彻底了解 Java 8 的 Lambda、函数式接口、Stream 用法和原理

古时的风筝

函数式接口 Lambda stream Java 25 周年

架构师作业一:食堂就餐卡系统设计

李锦

聊聊Java中的Thread类

geekymv

线程 Java25周年 Thread Runnable

架构师训练营-作业-第一讲

吕浩

极客大学架构师训练营

架构师训练营第一周总结

Kiroro

食堂就餐卡系统设计

Kiroro

架构师训练营第一课

于成

week01 UML 学习总结

李锦

第二章.软件架构设计

西柚

架构师训练营第一周 - 学习总结

Eric

极客大学架构师训练营

第1周 - 学习总结

大海

Hello World!

东哥

极客大学架构师训练营

第一周总结

LEAF

架构建模总结

任鉴非

架构师训练营第一周总结

Hugo

初步架构想法

极客大学架构师训练营

食堂就餐卡系统设计

于成

食堂就餐系统设计

Hugo

架构师训练营作业一:食堂就餐卡系统设计

sunnywhy

架构师训练营第一周(总结)

任鉴非

如何加速软件部署?-InfoQ