本次分享分三个部分,分别是中小公司构建部署之痛 ,自研web构建部署平台,追求极致、赋能业务,下面是对这三个部分进行详细展开描述。

    • 中小公司构建部署之痛:

    首先介绍一下自己的CI/CD历程 , 见下图:
    如何重新构思前端构建部署的平台 - 图1
    2014-2015这一时间段 ,我在当地的一家小公司做web前端开发,前端规模是2人,前端构建是没有的,当时web使用的技术是jQuery + HTML , 上线的方式是web打一个zip 包给后端工程师,后端进行上线。
    2015-2016年这一时间段,我来到了北京,去了一家院线排片的公司, 这时的前端业务会经常改动,每次改动都会打包给后端上线,后端闲过于麻烦,于是后端的leader 就将后端的java代码提供给前端,前端修改之后自己上线;
    到了2016-2018年,我入职了创业型公司,该公司的前端规模已经达到了20+人的规模,当时的构建环境也是没有的,构建主项目的话,构建一次需要花费40分钟的时间,后来引入了Jenkins 进行项目的构建,但是发布的话,还是需要登录到跳板机执行部署的命令。
    2018-2019年,我来到了一家新的公司,该公司的web前端规模已经达到100+人,构建和部署已经实现了标准化的流程。构建的话通过Drone ,发布的话通过K8S和docker 进行发布的,但是在2019年的中期发现,因为项目中涉及到很多活动页,这样发布的越来越多,占用的k8s实例无法释放,导致前端占用的实例达到80%左右,已经是一个很浪费的状态了。基于此事实,前端牵头做了一套研发平台,但是因为疫情的原因,很遗憾该平台没有上线。所以我本次分享的是当时的一套技术方案以及部署实现的流程;
    到了2020年,我来到了字节跳动抖音电商部门,因为字节跳动是一家大的互联网企业,所以拥有完善的构建部署体系,前端的规模是有几千人的样子;
    接下来我演示一下当时做的系统:

    如何重新构思前端构建部署的平台 - 图2
    构建和部署功能已经实现;构建部署平台有一个项目的概念,可以新建项目,项目创建完成之后,点击可以进入项目,里面有比如说缺陷管理、文档管理、迭代管理和项目设置;里面的应用对应的是前端的项目,因为内部有很多的脚手架工具,在创建应用的时候,可以选择应用的模板;在填代码仓库的时候,如果该仓库下没有项目,这时候会提示使用何种模板来创建项目,平台推荐的是两种,一种是中后台的,另一种是活动营销页的;
    创建完成之后,点击可以进入应用里面;前端的构建的实现是比较简单的,需要有一个构建配置,因为不同的环境使用的构建配置是不一样的,比如说开发环境和生产环境,所以在这里面可以创建配置,选择不同的node版本,还有选择工作目录以及其他的一些配置;构建的时候还有一些限制,比如说选择在哪一个分支下面去构建、哪个环境去部署;创建完构建配置之后,可以进入列表页来触发构建;触发构建的话,是以分支构建的,不是以tag构建的;触发构建之后,可以鼠标单击查看构建日志以及取消构建;
    构建完成之后会把构建产物放到版本管理里面;部署管理的话,这里会区分不同的环境,这里还添加了一个附加功能,会动态的给HTML文件注入一些脚本,比如说一些统计分析和大数据性能监控的一些东西,包括一些CDN的缓存日志和CDN的查询; 我这里主要展示项目的构建和部署功能的实现;
    下面我展示一下项目的整体架构图:
    如何重新构思前端构建部署的平台 - 图3
    首先从底层服务说起,要实现构建的话,底层需要有一个支持构建的服务,类似于流水线(Pipline)的概念;OSS主要是对构建产物的管理,它会存到对象存储里面,还依赖Mysql服务,所有的应用的编排都是基于Mysql 去做的;最右边是ETCD的服务,主要的作用是配置共享,最右上角有一个static server 的服务,通过监听EDCT配置的变化,来实现部署不同的版本;中间部分是功能模型,分了几个大块,分别是应用(Application) , 构建(Build) , SCM主要是对构建产物的管理。研发平台涉及到项目管理、应用管理、迭代管理、缺陷管理、文档管理和构建分析;上述就是整体的架构图;

    • 构建

    前端的构建流程是简单的 ,通过选择对应的node版本以及编译命令,构建好之后,发布到CDN就可以了;入下图所示:
    如何重新构思前端构建部署的平台 - 图4
    但是要考虑到通用性的话 ,需要做很多的通用性的考虑,对于小公司来说,不建议做这些通用性的考虑;构建需要考虑流水线的选型,首先看一下整个行业的生态:
    如何重新构思前端构建部署的平台 - 图5
    2011年Jenkins 发布,风靡了CI/CD的圈子,Travis是收费的,只关联Github;Jenkins是基于Java开发的,对于前端来说,部署的话上手成本还是比较高的;2013年的时候docker 发布了,2018年Jenkins X发布了,Jenkins X是基于K8S做的一套CI/CD的解决方案;这一套对前端来说也是比较重的,成本也是很高的;在2019年的时候,Drone CI发布了第一版,v1.0.0,我们的选型是基于Drone 去做的;首先说一下Drone 的优势,Drone 支持市面上的大部分存储库,包括github 和 gitlab , 因为是基于Docker生态的,所有是支持跨平台的,同时也是支持任何的语言环境的,这里给大家看一下Drone的使用方法:
    如何重新构思前端构建部署的平台 - 图6
    使用的方式是在项目中创建一个.drone.yml 的文件,文件的内容就是上图的内容,配置的内容和Jenkins是非常相似的,同时也支持条件的一些判断,上图主要是做了一个npm包发布的实现;我在这里补充一下用户交互的示意图:
    如何重新构思前端构建部署的平台 - 图7

    首先用户在应用平台触发构建,然后构建模块会发布构建命令给Drone , Drone master 会发一个命令给Drone runner , 执行构建;runner 执行构建的时候,回去Gitlab 去获取源代码,并在这个时候回去获取Pipline page , 因为Pipline page 是存在仓库中的,所以构建的时候需要对整个构建的page进行一个线上化的改造;所以,在获取源代码,构建配置之后,需要到Pipline 模块获取,最后构建完成,同步到构建模块。整个流程的难点在于如何通过服务获取自己项目的yaml文件。下面是我用nuxt.js 对Drone api 进行的二次封装的代码片段:
    如何重新构思前端构建部署的平台 - 图8

    使用Module 注册其子模块就可以获取到Drone service 的方法,Drone Service 实现的是一些基础的功能,下面是Drone Service 的代码片段:
    如何重新构思前端构建部署的平台 - 图9

    这边要注意的就是说,我们如何就是说当我们把我们的构件配置转换成我们的最终流水线要执行的一个EMAIL文件,通过就是说可以看到第46行,就说我这边有个getpipline的一个方法就是说,应用构建配置实体的时候,我们进行一个转换,把当时应用构建配置的字段,然后转成然后通过 lodash的company的方法把默认的配置进行一个替换。

    具体我们每次构建的话,其实还是要进行一个数据库的操作的,首先上面是一些边缘条件的判断,我这边会创建一个就是构建的数据插入一条构建的一个配置。
    截屏2021-05-09 下午4.09.04.png

    然后当我们拿到构建配置,首先可以看到一第一个绿色区域就是我们先创建一个构建配置,拿到我们的构建ID之后,我们调用service,然后触发一次构建,并且通过参数的方式把我们当次构建的应用ID和构建ID传到service里面,然后drone service去我们的服务去拿,构建配置的时候,它会把我们的应用ID和构建ID再传给我们,这样的话就是说我们知道应用ID和构建ID之后,我们就可以确定就是说我们要把哪一个pipeline的配置告诉drone,这样的话就是能实现就是说我们不需要在我们的drone,在我们的仓库里去写我们的drone yaml文件,可以看到在我们最左侧可以看到pipeline的一个配置,就是说每次执行构件的话,我这边都会动态的去把pipeline的配置插到数据库,当下一次从我们这边数据库拿我们的pipeline配置的话,我们这边只要把这边的pipeline直接下发到drone就可以了。

    然后这边drone的话其实也是需要做一些改动的,就是说我们怎么可以告诉drone去我们的服务去拿我们的配置,就是说在我们的drone master启动的时候,我们可以指定drone yaml endpoint,就是说他去哪个服务去拿我们配置文件,并且有个密钥去验证这个服务的请求是否合法。

    然后我这边说一下版本管理,其实上面就已经把我们的构建已经说完了,然后版本管理的话,我们怎么把通过我们构建完,怎么把我们产物去放到我们要放的地方。

    然后版本管理的话就是这边我们做了一个技术选型,就是用一个minio的对象存储的一个开源服务,它是一个开源基于阿帕奇开源2.0的一个协议进行。 简介看下图:
    截屏2021-05-09 下午4.18.33.png

    然后我们讲一下就是说当我们构建完怎么把我们的构建产物传到我们对象存储上,我们还是基于我们的默认的pipeline,在我们的最后一步去加一步,就是说上传到存储,可以看到绿色区域,就是说当我们执行完构建之后,把产物路径,按照我们的规则去传到我们的对象存储。

    然后版本管理的话其实也有很多种可选方案,目前当时的话也是基于就是说哪种实现起来效率最快,或者说成本最低的一种方案,当然也是有比较其他多的一种方案。
    截屏2021-05-09 下午4.24.48.png
    你比如说像左侧的harbor,它是一个docker镜像,就是说我们可以就是说把每次构建的产物都打成一个docker镜像,发到一个docker镜像源去管理,当然也可以有其他方案,像右侧的方案的话,它是一个收费的方案,比较成熟的方案,当然价格也是比较贵的,他如果是免费的话,功能是比较少的,并且是不太好用,就是说如果说你要是中小公司的话,还是不太建议就是说用这种收费方案,然后说到我们部署,部署的话,其实整个架构的话也是比较清晰了,最上层有一层tlb负载均衡层,然后在下边的话会有一个static server 的服务。
    截屏2021-05-09 下午4.27.00.png

    然后我们服务的话会监听etcd的一个配置的变更,配置的变更是说每次当用户发布新的版本的话,他会把当前你要发布的哪个版本,哪个小流量版本,哪些具体的一些配置下发到我们的是 static server下面,然后具体我们static server 的话会监听,就是说你哪个应用的配置,当有配置变更的话,我们会做一些相应的策略和决策。

    我这边说一下就是我们的部署的一个交互示意图,就是说用户在部署就是我们开发在部署的时候会做哪些操作。

    首先我们在用户在触发部署,然后发到我们的部署命令,然后具体发到我们的部署model的话,会同步把部署的命令同步到我们的 etcd,然后etcd有变更的话,如果有变更,然后我们做一些相应的策略,然后把对应的我们的oss的一些版本拉到我们的对应的static server 下边就可以了。
    截屏2021-05-09 下午4.30.45.png
    然后用户的一个访问流程的话,这边看到的一个流程,我这边也是介绍一下,就是说当我们用户访问到我们的tlb的话,就发出一个请求,然后我们static server 的话会不断的去监听我们的etcd的一个配置,然后拿到我们的配置之后,他会把配置存到我们的内存中,就是说当请求来了之后,通过我们的配置去匹配,就是说当前请求 host就是这个域名,然后匹配到哪个我们的,哪个应用,然后确定完应用之后,然后我们确定它的小流量标识,然后确定它要访问的具体的版本是哪一个,然后再做一些我们的动态的处理,然后最终处理完之后,我们拿到我们的data,然后最终返回给我们的用户,就是把这个方式返回到我们的用户端,这就完成了一次用户的一个访问的流程。流程见下图:
    截屏2021-05-09 下午4.32.49.png

    这边的话整个流程的话其实已经介绍完了,然后最终也看刚刚我们看到的最终我们演示的一个样子,然后当然不局限于就是这些构建和部署,当然我们后面会做一些比如说配置中心,包括动态注入,还有一些构建耗时的分析,包括因为我们前端现在前端的话也是在做一些微服务相关的东西,就是说其实也是对我们刚刚基于那套东西也是做一些改进的话,就是说我们前端微服务,比如说我们的注册,还有就是微服务注册,包括我们的菜单的一个发布,都是基于这个东西可以去不断去去加这些功能,去完善我们的,平台配置,包括我们的应用配置,还有一些人工卡点,就是说我们还有一个一些上线时间窗口,比如说我们在发布的时候,我们要向我们的leader去发布一个就是上线审批的卡点,当通过之后我们再去执行我们的构架,还有我们部署,然后最终我们还可能说再做的更好一些的话,就是我们去在我们的仓库里面去配置一个户口。

    就是说当我们的代码有更新的时候,自动去触发我们的构建和部署,实现一个极致的 ci/cd的流程。然后最终的话是因为我们最终要赋能业务,就是说我们做的所有东西都是要赋能业务,不能说就是说我这边做完之后,就是说做的就是很通用,但是应用性很差,这个也是不太行的。

    然后我这边推荐一本书的话,活法就是让无数人在迷茫时代找到活着的意义,然后修炼灵魂,保护美德。

    我然后这边发布一个招聘的信息,如果大家对抖音感兴趣的话,可以加到我们,包括就是说对我们刚刚讲的一些东西,有那个问题的话都是可以加到这个群里提问,后面的话我会把代码脱敏之后,然后发到我们群里,就有兴趣的话可以加到我们的群里。

    然后这次分享的话没有分享太多目前在自己的一些东西,是因为因为我们现在是在电商业务线,但是跟业务相关的一些东西不太能对外去做分享。然后这次分享的主要东西是就基于我上一家公司在做的一些东西,然后虽然最终没有做成,但是其实已经是一个可行性的方案了,最终我会把透明或者一些代码发到我们的群里。