在前面提到云原生完整技术解决方案的时候,已经提到了公司DevOps技术支撑平台和容器云解决方案。但是DevOps绝对不是一个简单的开源技术集成或者技术解决方案,而是结合本身的微服务架构优化,敏捷开发的企业研发过程改进和持续优化。
敏捷研发和CI/CD持续集成过程脱节,往往才是当前最大的一个问题点。
今天谈的重点不是DevOps具体的底层开源工具链和技术,而是对于敏捷研发,微服务如何更好的和DevOps过程形成一个高度协同的整体。在这个过程实践中,可能会形成多篇文章,都是对我们实践过程的一些问题总结,思考和复盘。
先从单体微服务拆分谈起
最早的时候,我们自己的DevOps管控治理平台拆分为20多个微服务,20多个微服务每个都是独立的项目,独立设计流水线,部署在独立的容器里面。可想而知,整个后续集成,部署和运维管控的复杂度有多大。
在去年,团队进行了重构,将微服务进行了合并,在合并完成后包括基础组件和能力中心微服务,一共在10个左右的微服务模块。合并到这个粒度后基本才处于一种可控的状态。
微服务拆分的颗粒度实际上还和你团队规模有关系,当你团队规模本身就不大的时候一定不要拆分的太细,一个人如果就管理多个微服务,实际当初进行微服务划分,希望进行的边界和解耦往往根本就无法做到。
在合并完成后仍然存在两个问题。
其一是数据库仍然没有拆分是一个大数据库。如果从理想的微服务架构来说,并没有做到完全的微服务化,数据库层面没有解耦。
但是当你数据库本身就没有海量并发和数据量大幅扩展的压力时候,你的数据库为啥要拆分?数据库本身不要为了拆分而拆分,数据库的拆分更多是为了扩展性的需求。当构建的应用在DB层本身没有太大的性能压力的时候,实际没必要马上就去做数据库的拆分。
其次,我在很早就提出了微服务域的概念,即使上层是10个微服务,你的数据库本身也不需要就一定拆分为10个,而是应该根据微服务域的划分来进行数据库的拆分。比如上层的编译,构建,流水线,交付管理等微服务,完全可以合并在一个数据库里面。
其二是在前后端分离情况下,整个平台的BS前端合并在一个项目里面。这个我个人认为并不太合理,也就是说一个功能如果涉及到前端应用有改动,那么整个前端应用都需要重新部署。比如资源管理部分的前端界面变化了,实际整个应用前端都需要重新部署,那么对于流水线,资产库这些微服务模块对应的前端是否造成影响并不清楚。
也就是说前端并没有做到完全的解耦。
如果你是开发一个APP应用功能,那么前端整合为一个项目无可厚非,但是如果是传统的企业级的PC端的BS应用,最好的方式仍然是前端需要进行分离。
从需求用户故事到任务拆分
在敏捷研发里面我们强调基于用户故事进行全流程的跟踪。我们将收集的需求进行分析,将需求定义为用户故事或需求点,同时将需求规划到具体的项目版本中。这个是最基本的产品-项目-项目版本的分解过程。
当需求规划到项目版本后,一个重点就是将需求转变为具体的任务。你采取的不同研发过程,不同的管控颗粒度下,实际上任务的分解本身是有标准可以遵循的。如果从传统的方式下,任何一个需求点往往包括了如下任务分解:
1.资源配置功能需求
1.1 资源配置需求文档编写
1.2 资源配置功能开发
1.3 资源配置测试用例编写
1.4 资源配置功能测试
这是最常见的一个任务分解安排。
在敏捷开发和前后端分离下,你可以看到一个功能的开发同时涉及到前端和后端,后端开发完成后输出的是接口,前端基于接口进行集成和联调。在这个过程中测试人员又需要接入进行测试,其一是针对后端开发完成的接口测试,其二是针对前端完成的功能做黑盒测试。那么基于这个思路,你会看到古董任何一个功能的实现都可以分解为如下:
1.资源配置管理功能实现
1.1. 需求开发-》需求人员
1.2 后端功能和API接口开发-》后端开发
1.3 API接口测试-》测试人员
1.4 前端功能开发和集成-》前端开发
1.5 功能整体测试-》测试人员
当思考到这里的时候,实际我们希望的是,对于敏捷研发项目管理工具,在你的开发模式确定后,基于某个需求点的子任务拆分应该是自动化进行的。或者说可以基于标准的开发任务分配模板进行自动的子任务生成。
如果做到这点,实际上还是没有办法解决问题。因为我们任务的跟踪实际上还是按照单个任务的方式,按未开发-进行中-已完成等任务状态进行看板跟踪。
但是我们实际需要的是按照开发模式关键技术进行任务跟踪。比如前面谈到的我们跟踪的是用户故事或需求点,我们关心的状态是当前用户故事处于需求开发阶段,还是后端开发,还是前端开发集成阶段。这个才是关键的看板跟踪点。
也就是说传统看板你看到的是类似下图:
但是实际上我们希望看到的是基于需求或用户故事点为核心的看板。这个看板并不是现实具体的子任务,而是只到任务基本,子任务影响到的是看板面板卡片的状态。
比如上图,我们可以很清楚的看到当前迭代版本一共规划了11个功能点,同时每个功能点当前在哪个阶段或状态。其次,对于某个岗位角色的人上来,也可以很清楚的看到他当前自己的关键todo事项,他要做的是尽快完成自己泳道的事情,将任务状态转移到下个阶段。也就是说当做了如上改进了时候,才能够更好的做到敏捷研发模式和敏捷任务看板管理的一个融合协同。
从需求变更到项目版本规划
在这里我们将已有功能的需求变更和新增的小需求都纳入到需求变更的范畴。当前谈CI/CD持续集成和持续部署,更多的都是应用系统上线后的缺陷修改,需求变更引发的迭代版本开发和部署操作。
因此需求变更才是后续软件应用持续集成的一个基础输入。
在前面谈产品和项目两级流水线设计的时候,我就谈到了一个产品拆分为了多个微服务模块,每个微服务模块都相对独立和解耦。
但是用户最终看到的仍然是整个应用系统。
一次需求变更过来后,我们通过分析最终需要确定的就是涉及到哪几个微服务模块需要变更。在分析清楚后,最好的方式就是仅仅变更的微服务模块需要重新进行持续编译,构建和部署集成,而对于没有变更的模块不应该进行重新的编译构建操作。
微服务下对传统单体应用解耦后,最基本要做到的就是某个微服务如果没有变更,就不应该去重复地进行编译和部署,任何重复多余的编译部署操作往往都容易引入新的缺陷或问题。
比如上图的例子,一次需求变更过来我们规划V2版本,但是实际上只有绿色的三个微服务模块需要进行版本升级和变更,而灰色的三个并没有变更,不用进行重新的编译构建等操作。在这个时候容器管理部分的功能在应用发版后不需要进行回归测试,即使容器管理部分功能出现问题,我们也应该追溯容器暴露接口相关的外围消费和调用。
当这个思考清楚后,你会看到需求变更纳入到项目版本,那么我们实际最关心的是当前的项目版本整体进展,这个进展不是只是需求,任务和缺陷的研发管理过程和任务,同时也应该包括了整个CI/CD过程进展。
简单来说就是:研发管理过程和CI/CD过程应该基于项目版本主线形成一个完整的类似看板一样的管理视图。这个视图就是整个敏捷团队的工作界面。
对于这个可视化看板,简单构思应该如下:
也就是说我们希望看到一个完整的基于项目当前版本的看板视图,在这个视图一个是可以看到当前需求,任务的直接进展情况;其次是可以清楚地看到涉及到当前项目版本的编译构建和部署情况。
这个也是我们进一步将研发任务管理的状态和CI/CD过程进行集成的基础。
比如我们在前面很多文章里面谈到的。
开发人员对一个开发任务反馈完成,这个时候任务本身的状态应该是在待部署状态,这个是一个看板上的隐藏状态并不需要人工去关心。而只有流水线执行成功后相关的任务才会从待部署状态转移到待测试状态。
也就是说开发完成任务,这个功能本身仍然在开发人员的看板中,只有后台的流水线执行成功,完成了功能的自动部署后,该需求功能才会自动转移到测试中这个看板。
这些关键环节必须系统自动衔接。
否则一个功能开发反馈已经完成,但是测试人员上去发现并不能测试,最终追溯才看到流水线任务实际执行失败,导致开发完成的功能并没有成功部署。这些都势必会导致大量无效的人工沟通和协同工作。
流水线触发和构建频率
在DevOps实践方法论里面,始终在强调随时随地的触发构建,一天不需要去约束构建次数,只要代码一check in就应该触发流水线编译构建流程进行构建。
在讲这个问题前,我想先谈两个例子。
一个是在游乐园里面做一个旋转的游乐设施,刚开始的时候速度不快,完全能够接受。但是后面速度越来越快,最后感觉就是脑袋一阵眩晕。实际上这个设施可以转得更加快或者说频度跟高,但是就个人来说,你始终会有一个你能够接受的极限值,超过了就晕。个人由于各自的身体素质差异,实际上这个极限值本身存在不同。
还有一个我们写文章的例子。
比如我写文章,我专门找了一个编辑帮我进行审核和修订,我既可以是每写一小段就发给对方让他修改,也可以是我每天或每周发送一次给对方让对方统一修改。如果我每写一个小时就发送一次,那么对方马上反馈问题后,我实际的写作过程都在随时被打断,这个显然是对我正常写作造成影响。
回到我们构建频率这件事情上。
实际上构建方式或频率包括了代码check in就自动构建,也可以是人手工发起构建,还可以是每天或每半天定时构建一次。如果按DevOps最佳实践方法是代码提交即构建,但是我要说的是这种方法并不适合大部分的团队,什么原因呢?
其一是团队和个人本身的成熟度和敏捷度就无法适应这种高频率构建,工具高频率容易,但是人要高频率需要的是高度的自律。其二是当随时都在构建的时候,你发现开发人员随时都在解决构建过程中出现的问题或依赖冲突,导致开发真正专注在编码上的时间越来越少,也就是是高频率构建极其容器导致我们开发时间碎片化,这个显然是开发的大忌。
当做了上述思考后,最佳的方式仍然是按天或半天定时构建,同时对于关键bug的解决根据业务驱动由测试手工发起流水线运行。
很多互联网应用每2到3天就再发布和迭代版本,但是对于企业内部信息化应用,实际上远远不需要如此敏捷和高频,因此我们持续集成和构建的频率也无须如此。
当团队和个人自身的能力和成熟度达到后,我们可以进一步缩短构建频率,比如从半天到每2个小时一次构建。同时对于构建频率的缩短往往还伴随着PMS任务颗粒度的细化,你原来的任务颗粒度是1周或2到3天,那么新的颗粒度则可能是2小时或4小时。
父子流水线和环境迁移
首先再次强调一个关键点,即:
对于CI/CD的价值一定体现在跨环境的自动迁移部署能力,而不是单个环境的自动化编译构建和应用部署。编译构建的过程只有一次,形成的是二进制文件包;而环境迁移可以多次并灵活编排,环境迁移不需要重新编译构建,最终确保基础依赖的一致性。
也正是如此,环境迁移一定是我们流水线设计编排的一个重点。
比如最常见的业务场景,我们准备了SIT集成测试环境和UAT用户验收测试环境,当某个版本的软件开发在SIT环境完成集成测试,所有的Bug都修复后。我们需要将软件部署到UAT环境,并通知用户进行验收测试。
如何确保用户验收测试的版本就是我们SIT测试通过的版本?
即前面谈到的基于二进制和镜像文件的迁移,对于UAT环境部署重新编译部署,而仅仅是镜像文件在UAT环境的部署,这是CI/CD强调的一个重点。
那么问题的复杂度在哪里?
即前面谈到的传统的一个单体应用系统以及拆分为了10个微服务模块,每个微服务都可以独立编译构建,打包和部署。每个微服务都有自己各自的流水线设计。
但是实际上就一个项目版本来说,我们只关注这个项目版本的完整性。比如这个项目版本仅仅涉及到3个微服务模块要变更,那么在SIT测试通过后就应该将这三个微服务模块的最新测试通过版本迁移部署到UAT环境。
也就是说应该在微服务流水线基础上增加一个对应产品或项目版本的流水线。编译或构建是以微服务为最小颗粒度单位,但是环境迁移构建,是以产品或项目版本为单位进行。
我们还是回到前面的场景,比如DevOps平台研发规划了V2版本,这个版本涉及到门户管理,研发管理,持续集成三个微服务模块的变更和发布。
对于上面三个微服务模块本身就已经有自己的独立编译构建部署流水线任务。
那么现在重点就是基于本次规划的项目版本V2,构建一个父流水线,同时将已经有的三个微服务模块流水线挂接进来,形成一个完整的父子流水线模式。
也就是说在父流水线上我们只会编排环境迁移的关键节点,比如:开发测试环境-》SIT环境-》UAT环境
我们会在流水线上设置由测试人员参与的手工审核和处理节点,当测试人员确认SIT测试通过的时候,我才进行自动化的环境迁移动作。同时父节点必须是在各个子流水线运行成功后再跳转到下一个活动节点状态。
比如前面谈到发起一次父流水线运行,那么先去执行各个子流水线,子流水线如何全部执行成功则将父流水线状态转移到待测试状态。测试人员在完成一轮测试后,如果不通过则进行不通过处理,将流水线退回到初始状态。
同时在进入流水线视图的时候,可以清楚地看到当前整体流水线的执行情况,即当前项目版本涉及到几个子流水线,各自对应哪个微服务模块,每个子流水线是否允许成功,如果失败可以进入到详细的流水线任务查看界面查看原因等。