背景 bg

目前公司研发团队的代码仓库和集成工具,已经全部从私有化gitlabjinkens迁移到了阿里云云效,与之前基建对标的工具分别是codeup[Flow流水线](https://flow.aliyun.com/)

但我们仅仅是做了代码和发布的迁移,对于这套基建的标准操作流程和使用规范,没有进行重新定义。

之前基于私有化gitlabjinkens的旧版本研发操作规范已经过时,不能贴合研发的实际工作需求。
这导致即使我们使用了更现代化更便捷的收费工具,却无法发挥其全部威力;
而基于这套新基建的配置还不够完善、各个团队的日常操作流程不够标准,不仅没有降本增效,反而无形中增加了研发的工作、繁琐了操作步骤、造成了研发精力的浪费,去频繁的在本地捏分支、解冲突,这种原始的操作,同时增加了迭代和发布的风险。

因此,结合测试团队的配合,给予新基建和新工具,制定了新的操作标准和研发规范。

目前这套基建的操作标准已经逐步在团队中落地推广,在内部收获了广泛好评。我们的前后台团队基本告别了手动捏 test分支和r-分支的痛苦。(对于镜像部署的环境,我们正在进行改造发布流)

我们相信,我们团队经历的痛苦,在公司其他团队中应该也会有类似的问题。因此在对这套流程进行梳理和内部检阅后,我们决定向公司其他研发团队进行输出和推广。

曾经 before

在介绍新东西之前,我们来复习一下之前我们是如何管理feature并发布到test、release,最终到master的。

进入内部环境

进入test环境,我们要先准备test分支,整个过程大致如下图:

如果有若干个分支被“捏”成了一个test分支之后,突然PD说,feature A不上了,不用测试了;那恭喜你,请再执行一次上图的过程,只不过是N-2次的重复合并过程。这是在corepaas前台研发们频繁遇到的问题,每天为小心谨慎的完成这种低级操作,耗费大量的时间和精力。

上述的过程,如果是命令行选手,可能会敲出如下的过程:

而如果是喜欢用codeup操作的同学,就要先新建分支,然后频繁的(N次)执行页面merge过程。

feature进入release环境,也是一样的,唯一不同的是我们要捏的分支名字叫r-分支。R分支我们之前还强行规定了代码review,这其实没有必要,代码只有在进入生产前才有必要人肉卡点+review。

这里显而易见的问题是:

  1. 多个开发者对同一个环境的使用,是基于一个共同分支的。若干个研发合力“捏”出来这么个分支,要大量的“隔空喊话”,疯狂沟通。
  2. 频繁的merge,翻车概率很大
  3. 解决冲突的次数越多,引入bug的风险就越大
  4. 在不同环境上演这个*N的解决冲突,要保证每次解决的结果都一样,也是很幸运的事情;feature进入的顺序、每次解决conflict的人、每个冲突怎么解决的、命令行操作;
  5. 每次发布后,test、release分支,都要新拉master重新制作,然后再重复一次上述过程。
  6. 在大宝贝分支里到底有什么,要么你翻commit里的“merge xxx into xxx by xxxx”,要么得靠记忆力超群。完全没有一个可视化的面板来以分支为维度管理一个环境里的feature。

进入下一个环境

当若干feature从环境A测试完成,进入环境B时如何处理呢?
这里分两个情况讨论,一个是内部环境,一个是从release进生产。

内部环境

直接捏下一个环境的分支

从release进生产

当R分支在release环境测试通过之后,我们会直接发布release环境到生产环境。
在CorePaaS前端之前的实践中,是在codeup里执行一个从R分支到master的mr。
然后到Flow正式环境的发布流水线,目标分支选择merge过的master,执行线上发布。

这里就会引起更多问题:

  1. 如果发布失败,Flow可以执行回滚(如果你配置正确,是可以秒回滚的);但是你的代码已经merge了(写入基线),如果你对git不熟悉,代码回滚是很麻烦的;codeup有revert commit的功能,但原理是diff这个commit然后做相反变更,并写入一个新的commit;这个commit会让代码变得非常肮脏,之前的提交记录也在,revert的记录也在。如下图:

针对上面的第三点,有必要说一下有多恶心;当你先merge后发布,一旦发布失败、回滚,就必然伴随着仓库的回滚。下面有一个示意图。

除此之外,下次进入时做过的脏事儿再来一遍。所以这是非常难以接受的。

升级之后 after

首先看一个示意图:

基于feature分支的自动集成。

  1. 减少人肉操作次数,从N次变为最多1次操作。
  2. 解决冲突的次数,在1个环境中最多1次。
  3. 代码可以低级环境测试通过后,无缝衔接到更高级环境,无需人肉操作,且0风险。feature代码不发布,就不写基线,根本上防止了仓库回滚。

进入内部环境

当我们点击“运行”时,会出现下图弹窗。

然后将想要带入这个环境的 feature分支 进行on stage,如下图



上图里,我添加了3条分支。

进入下一个环境

进入下一个环境有两种情况,一个是公司内部环境之间的进出;一个是进入生产环境,一般来说你应该是从release进生产。

内部环境

一个feature从低级环境(较不稳定)进入高级环境(更加稳定)的时候,只需要在新环境里,把这个feature分支on stage

从release进生产

日常迭代 work everyday

release是公司内部最稳定的环境(我们假设他是),也是上线前最后一道保障环境;
一个feature在release测试通过后,我们大概率会在约定的发布窗口期(corepaas在每周二和周四晚上7-10点)将这些feature发上线;

  • 如果预发环境的feature都要上线,可以直接把预发环境的集成分支在正式环境发布流里on stage,然后触发部署。
  • 当然,如果你开心,你可以完全可以用feature分支重新在正式环境重新集成。至少如果在预发环境有冲突的话,你可能要再resolve一次,最好是有冲突的情况下尽量发集成分支,也省得麻烦;如果单一上线一个feature分支,那我还是建议用这种重新集成的方式,这样git里创造的垃圾commit比较少,让commit历史更好看一点。
  • 如果预发环境的feature只发一部分(PD们决定的:),直接把要上线的feature分支,在在正式环境发布流里on stage,然后触发发布。

这里我们看出,这种灵活性可以节省超大量的日常繁琐操作时间…

线上热修复 hot fix

热修复一边是从master切分支,fix,然后至少预发验证,最后紧急发布的过程。

其实和普通feature的发布没有太大区别,只不过你的feature的名字可能不太一样,以及在热修复时,在预发环境和master环境,都为单独的hotfix分支让路,只有这个分支on stage。

怎么整活儿?how to

如何对各组各工程进行改造升级呢?

流水线与环境对应 Optional

首先我认为每个组的现状是不一样的,比如在metadataapp这个工程,之前的test4环境和release竟然是共用了一条发布流,如下图:
这个情况就比较麻烦,你得先根据环境拆分发布流,让一个发布流对应一个环境。
但这个改造也不难,教程如下:

step1. 选择一个分组,新建一个流水线

step2. 选择一条流水线模版
Flow给用户预设了一些可以开箱即用的流水线模版。
我们公司目前大部分后台应用的技术都是基于Java的;
前台应用大部分都是静态资源构建,少部分会有NodeJS工程;
如果你是Java,见下图,右侧选择一条符合你实际的Java模版,

如果Flow提供的模版里没有符合的怎么办,因为里面的每个节点都是可以自定义配置的,如下图:

如果你是一个前端开发,你应该选择NodeJS。
如果你是前台页面资源,用来打包html、js和css,一般来说是如下图的两个其中一个;

第一个仅帮你构建,执行npm run build,其他不管;这里要具体工程具体分析,比如在metadataapp旧的打包脚本里,是通过webpack的plugin把打包后的结果,直接上传了cdn(在构建机里竟然能允许上cdn,这种操作属实让我意外),最终构建会产生一个json文件,分别是metadata.json和boss.json,这两个json里写了其他资源的配对关系,比如"a.js": "adfkja;lsdfj;asdf.js", 而这个json里的每个value又在打包时候上传了cdn,这样每次发布,我们只是将线上的那份json文件做替换就好(其实这里有风险,如果linux里文件句柄引用,会造成资源无法释放,直到打满内存而宕机),所以我们最后加了一个链接宿主机的deployment节点。如下图:

第二个帮你构建好了以后,顺便帮你上传到阿里云OSS;这种适合纯粹静态托管的网站。比如你的域名解析到了阿里云的OSS的CDN域名上。客户访问xinheyun.com,被CNAME到阿里云CDN,后面对接的就是你的html(很多小型博客都是这么架设的)。目前被搞成微前端子应用的前台工程,属于这一种。但这个在发布时,还有存在有很多暂时无法解决的问题:

  1. 无法实现秒回滚,一旦前台出现严重线上bug,回滚发布也要重新走一遍全部的build过程(除非你直接操作oss文件,而这种操作人为风险极高)
  2. 构建过程无法放在业务准入和测试准入之前完成,这样比较浪费时间,如果构建和上传分离,那么当最后一道卡点结束后,发布者点击一下部署,瞬间就上传了CDN,就不用干等十分钟了。(测试准入和业务准入是我们CPS组设置的发布卡点,只在release和生产环境的发布流中存在,代码进入这种严肃环境前,发布者的主管需要同意准入,测试同意准入,之后变更才会进入该环境)


上图说的是,这个OSS上传,是和构建绑定的。目前我还没仔细研究绕过的方式,等有了新结论,会及时给大家同步。

【重点】打开分支模式

上面演示的,是如何制作一条发布流,为了解决发布流耦合问题。但这不是本篇分享的主题,下面说一下开启分支模式,仅需要简单的一步,如下图:

当分支模式打开后,首先确认你默认分支是主分支(当然在某些特殊情况下,尤其是)
然后确认出现了一个“分支管理器”的新节点”,都确认无误后点击右上角“仅保存”,然后点击左上角返回,如下图

返回改发布流后,其实并不会有什么改变,但当我们点击“运行”时,会出现下图弹窗。

然后将想要带入这个环境的 feature分支 进行on stage,如下图



上图里,我添加了3条分支。

【重点】添加基线节点

前面已经讲过,先合并r分支再发布主分支,容易造成git污染,而且在顺序上是错误的;我们通过分支模式进行了发布准入的改造,聪明的你一定想到了,发布完成后,我们还没有合并分支;这个步骤也是可以自动化的。

在你发布流的最后一个节点之后,再加一个节点,选择 代码 -> 合并代码,如下图

然后配置里选择这个工程的主分支,如下图

这样当你从release环境进入生产环境的分支,一旦发布成功后,会自动合并到主分支;你也可以像我一样,前面再加一个打tag的过程,不过我图片里的tag配置错了,应该先合并代码,后打tag,聪明的你一定能看出来。
上图我选择了删除分支,因为那个分支已经没有意义了;
目前大量工程留着一堆r分支,其实没必要,git是通过commit来玩转的,只要你的commit还分布式的在任何一个同事的电脑上存着,就不会丢失;留着一堆branch很占用你本地电脑的磁盘。

更详细的请阅读Flow分支模式的官方文档,虽然这里面并没有给出最佳实践。

云效Flow分支操作标准流程 std

标准分为Flow设计标准Flow发布标准操作两部分。
Flow设计标准是定义如何设计一个标准稳健的发布流。
Flow发布标准操作是定义在一个标准稳健的发布流中,如何操作来进行安全生产。

Flow的核心发布节点

注意事项

  • 尽量少用${PIPELINE_NAME} 这个变量作为重要配置项的值。如下图:
  • 构建机选北京集群,如下图如果你选了flow-cluster,可能会遇到如下报错:

标准操作

我们来梳理一下,基于这种feature分支的on off stage发布模式的日常标准操作流程,从需求开发到发布上线。

step1. 切feature分支,这一步就不赘述了,分支起名规则请遵循各团队规范和习惯。

step2. 开发功能,并及时rebase master分支,尽量多的在开发时,本地提前解决冲突;建议使用rebase,而不是直接merge,这一步就不赘述了。

step3. 内部测试(uat、eden、test1234567、release),在上面进入内部环境的章节已经有过描述,你只要关心自己feature分支,并把自己的feature分支on stage,然后触发流程运行,that’s all。

step4. 上线。如果release环境测试无误,bug都得到修正,那么你有两个方法将这些验证无误的功能带入线上:

  • 【无冲突】在正式发布流水线,on stage那些feature分支,集成发布
  • 【有冲突】把自动集成并测试过的release分支,on stage到正式发布流水线,进行发布;获取集成分支的方法如下图:

这两个选项区别不大,可能唯一的区别是直接发release的引入的变量会小一点;但commit信息会多一点,毕竟你集成的时候也有commit;另外就是,如果在之前环境里这几个feature分支有过冲突,直接发release版本的集成分支会更好一点,你不用再次解决冲突了;但我相信,很多人在之前的发布经历中,早已熟能生巧了。

step5. 写入基线。这一步【非!常!重!要!】,这一步一定要执行完毕,才算发布成功,发布彻底结束。请看下图:

图里演示了,不写基线是如何造成功能衰退的。但我们要意识到,这不是分支模式发布造成的,分支模式发布确定无疑是更高级更科学的分支管理模式,而图中所示的功能衰退,是由于不规范操作造成的,大家只要严格遵守发布规范,按照标准流程操作,祝大家一万年不翻车。

解决冲突的标准操作

new了两个分支,分别是txt/conflictA/rxtxt/conflictB/rx,在readme.md里的同一行,一个分支删除了,一个分支了,如下图:

这样这两个分支是必定会冲突的。我们把他们on stage,如下图,然后运行流水线:


他们果然冲突了,如下图

点击解决冲突,


Flow会提示你,按照流程一步步的傻瓜式操作。这里说一下原理是什么,代码合并的时候commit会一个一个的patch到集成分支上,当一个commit在patch到集成分发生了冲突,云效会将状态回退到上一个commit(集成到这一步了,下一步就会冲突),然后把冲突的commit和集成的这个分支告诉你,你在本地解决好,然后推上去,点击继续集成,后面的云效会继续自动完成(当然也可能继续冲突)

若有冲突,在一个环境内,仅需解决1次。Flow在分支集成时,若有冲突,会给用户明确的操作指引(命令行都给你写好了,黏贴复制,傻瓜操作),然后会引导你执行一次commit;这次commit就保证了在当下feature分支的组合case里,之后的部署和on off stage不会再冲突,仅需解决1次。

若feature需要退出某内部环境,off stage,然后出发流程即可。

可量化 visualization

可量化的降本增效
本来想统计一下大家每天捏多少次分支,每次捏平均花费多久,敲几行命令,明天平均解决几次冲突;甚至可以卡个表来比比谁捏的更快。

可量化的研发风险规避
分支管理发布,属于流程再造了,整个过程中减少了“人”这个因素的参与。众所周知,任何系统里,“人”这种生物永远都是最大的变数。我们确实的减少了“每次进入同一个环境”、“从一个环境到另一个环境”所引入的变量。最终减少了进入生产环境的不可控变量。

参考 refs

参考:

云效Flow分支模式:https://help.aliyun.com/document_detail/202380.html