这里主要记录一下,我在我司搭建和推广前端CICD时,基于公司现有场景和业务,以及相关实践所做的一些思考和总结。
原始的发布:手动发版
一开始,公司的发布是由开发手动进行发布的:
- 如果是jsp项目(那时候还有这个),那么前端开发需要将页面构建出来,然后按照一定的规则转换为jsp文件,之后,将jsp文件提交到后台的项目仓库中,随着后台一起发布。
- 如果是发布纯粹的静态资源,那么我们在构建出静态资源之后,需要将其丢到服务器的指定目录中
- 如果需要发布cdn,那么我们还需要将构建出来的静态资源,放到cdn相关的git仓库中,然后让后台进行cdn的同步。
这个阶段的发布,基本上都是靠人工,从打包构建,到资源上传发布,都是由开发手动完成的,这个方式自然有很多问题了:
- 发布步骤繁琐,耗时耗力
- 手动发布容易出错,且无可避免
- 发布流程不易于交接和维护,且多个项目则更加难以管控和维护了
- 发布依赖某部分个人,无法对发布进行把控,有安全隐患
虽然后面我们使用了一些工具库,比如gh-pages来简化将资源发布到服务器或者git仓库的工作,然而,这无法解决根本问题。手动发版有那么多问题,而且随着项目的变多,发版变成一个非常麻烦且容易出错的事情。所以,我们考虑自动化我们的发布流程。
基于业务和公司目前的情况进行讨调研
我们的需求
- 我们需要自动化构建部署流程,测试、验收环境通过git触发或者手动触发来执行构建任务
- 需要能支持多种项目的不同构建、部署方法(jsp、cdn、客户端、monorepo、node等)
- 能够简单的为新老项目创建CICD任务,且易于维护。(如果为项目配置CICD很麻烦,则难以让别人用起来)
考虑点
- CI/CD工具:我们因为各方面的原因,并未选择自建git仓库(比如gitlab),目前所使用的git托管为腾讯工蜂,然而,它并不具备devops平台
- 需要支持的项目部署方式的多样性:cdn,nginx静态资源,node服务,electron客户端,monorepo等等
- 支持不同环境部署的构建配置差异
- docker的构建和部署。
- 一个项目,部署多个地方?比如报告系统,多个医院,多个部署,简单的自动化构建无法满足。可能要配合提交关键字去处理了(这种需要单独做特殊做处理)
- 项目配置CICD需要较为简单且可维护
- 构建部署优化
- 代码回滚功能
- 不同项目可能依赖不同的构建环境
- 手动触发进行部署的方案和纯粹自动部署方案(通过git钩子)应该要同时存在。
常见自动部署构建流程
通常来说,常见的构建部署,无非就是拉取需要构建的源代码、安装依赖、打包构建、部署这几个部分。
- 拉取源代码可以通过jenkins之类的进行触发
- 安装依赖、打包构建通常是使用shell执行构建命令,构建命令根据项目的不同而不同
- 部署则分为合并仓库、部署资源到服务器、docker部署等等
CICD平台搭建:jenkins+自研构建工具
由于那时,后台会用shell为项目编写发布脚本,然后发布时直接运行对应的脚本即可。因此那时我们前端考虑的也是编写自己的发布脚本,将之前需要手动构建、上传服务器等操作通过脚本的形式自动化。同时那时候研究了一些CI/CD的相关资料和书籍,找到的内容大多是讲解CI/CD的理论,而相关的实践也大多数是jenkins+脚本的形式,那时候的jenkins也是功能最完善且免费的CI/CD工具,而且他的一些功能能也能够大致满足我们的需求,自然就相中了他,于是我们决定基于jenkins+构建脚本的形式开实现我们自己的CI/CD流程。
使用jenkins
我们使用jenkins,除了他是功能丰富、免费之外,其相关的文档资料也是很多的,而我们使用jenkins的主要利用的是他的以下几个功能:
- 能非常灵活的为项目创建适合项目的构建部署任务:自定义脚本、组合构建不同的job
- 图形化界面
- 可以以多种方式触发任务:基于参数的手动构建、web钩子等等,可以适配不同的产品
- 基于用户角色的权限控制
- 提供构建环境:我们编写符合我们自己需求的docker服务器镜像作为我们的构建环境。
我们有了jenkins,那么其实基于jenkins的pipeline,我们可以从基础的:拉取代码 -> 安装依赖构建打包 -> 部署这个流程来为项目编写构建部署脚本,然后再基于git的hooks钩子,在push或者merge时触发jenkins的构建任务,以此来实现我们的CICD。而且,你还可以在整个构建过程中将lint、测试工作加入到你的CICD的构建流中,甚至可以基于jenkins的插件,来做到构建的通知的等等。如果项目少、构建简单,那自然直接这样干没有啥问题,但是,我们目前所面临的问题可不止是这么简单。
构建工具
我们梳理了下公司目前还在维护的项目,同时,也整理出了我们从项目打包构建到发布所需要做的一些事情,结果发现,我们目前涉及的项目类型是多样的,jsp、普通web项目、ndoe服务、electron客户端项目等等,并且需要针对monorepo、multirepo等不同的git代码存储风格进行优化,最后,我们还需要处理不同项目、不同场景的不同发布方式。
而且,如果每个项目都需要开发自己去在jenkins去配置以及编写复杂的构建部署脚本,这显然会阻碍我们的自动化构建的推进。
所以,我们考虑编写符合公司项目部署的构建工具,通过配置的形式来简化我们配置构建任务时的工作。此时,我们在jenkins中不再编写大量的shell脚本去完成我们的构建,而是可以通过调用统一的构建工具命令,然后由构建工具去读取项目中的构建配置去完成我们的构建部署。此时jenkins中的pipeline甚至可以抽离为一个模板了(项目可以提供jenkinsfile)。
构建工具大致实现功能(仅作为参考)
根据上面所述,我们的构建工具需要承担的任务比较多,其实,它最好的形式自然是向github、gitlab这种成熟的devops看齐,模仿他们的构建配置去实现我们的构建工具,不过需要根据我们的实际需求精简一些。
- 构建工具作为cli命令工具的形式去提供
- 读取项目中的特定的配置文件,来执行整个构建部署任务
- 提供大致的构建部署阶段:依赖安装、构建阶段、部署阶段等
- 在配置文件中为各自阶段自定义构建任务(其本质就是定义构建的流程)
- 提供常见的任务支持:比如将资源通过ssh发送到某个服务器中,合并资源到git仓库等等(这是减少配置复杂度的关键)
- 提供环境变量支持
注:为了后续的代码回滚功能,我们在构建阶段中,需要将构建产物依照版本号信息进行持久化存储(比如存放到本地磁盘、对象存储等等地方都可以)且需要将构建和部署他们直接隔离开,让他们没有直接联系,以便在回滚状态时可以直接进入到部署阶段。
最终的CICI流程
有了jenkins和构建工具,那么我们可以大致了解我们的CICD的整个构建流程是怎样的了:
- 有一个新的普通项目被创建,他需要配置CICD任务。通常我们会创建dev、acceptance(等同于release)和master分支,作为我们的测试、验收和生产分支。
- 首先,在jenkins中创建一个任务,依照jenkins的pipeline模板去创建(可通过配置参数进行手动触发)
- 通常我们需要为测试、验收和生产分支建立3个不同的jenkins任务
- 这个jenkins中的pipeline中的内容很简单,在使用我们自己构建的docker服务器镜像环境中,拉取git仓库的指定分支代码,然后运行构建工具CLI命令,传递必要的参数
- 在建立了jenkins任务之后,我们需要在项目添加构建配置文件,并为不同的环境设定不同的构建配置。
- 最后,我们需要在我们git系统中,为这个git仓库开启push,merge,pull request等钩子,并在这些钩子触发时,去运行jenkins的任务。
有了上面的一些准备工作,对于开发而言,其整个工作流可能就是:
- 从dev或者master中拉取一个新分支进行任务开发
- 开发完毕后,将其合并到dev来发布到测试环境,此时,在分支合并时,触发git钩子,继而,触发了相应的jenkins任务。
- 该项目的jenkins被触发执行,根据相关的信息,拉取代码、进行构建、然后部署到测试环境。
- 部署到验收、生产环境同理(生产环境应该是走pull request)
而如果需要代码回滚,则可以直接在jenkins中手动触发该项目的任务,唯一不同的就是传递参数以标识其为代码回滚而不是重头构建,且需要提供回滚的版本号。
后续的演进
我们上面的版本,大致实现了我们的需求以及满足了我们的大部分考量因素。然而,其肯定是存在不足的,只不过,这是综合我们公司实际需求、针对我们公司目前所面临的痛点以及公司现有资源的情况下,目前可实现的方案了。不过,我们仍然对后续的前端CICD演进进行了探讨。
使用成熟的devops平台
手动实现构建工具固然有意思,也足够灵活,但是其维护性、功能的完善程度和使用的心智负担等等也都是需要考虑的,如果能够使用现有的成熟平台,何乐而不为呢?像github、gitlab等都有较为成熟的devops平台,都能满足我们绝大部分需求。
将测试和lint添加到构建流程中
我们目前的CICD仅仅只是完成项目的打包发布就ok的了,目前,都只是使用CICD思想完成了项目的自动化构建部署而已,当然,也因为目前所面临的痛点就是项目发布问题,像测试、代码检查、通知等部分,我们仍然未涉及。然而,这在后续的发展过程,这些也肯定是需要考量进来的。
如何实现你的回滚?
一个方法则是你的构建,可以指定某次git提交,这样就可以重新以这次提交进行整个CICD流程,以将其恢复到指定的项目版本。但这样,意味着回滚时间较长,且你的构建需要支持针对某次提交进行构建,可能会比较复杂。
另一个方法是,在CICD过程中,构建和部署是分开的,且他们之间可以独立。什么意思呢,也就是说,构建后的产物,以版本号或者加时间,将其持久化存储起来,而部署,而是部署构建出来的某个版本的构建产物。部署不依赖于构建流程,这样可以实现回滚(跳过构建步骤)。
支持docker容器化构建部署?
在我们的node服务端项目中,容器化自然是很吸引人的,而针对容器化项目的构建部署,也是需要考虑的点。
复杂项目的构建
有一些项目的构建可能因为其业务的特殊性,无法按照我们所定义的标准CICD流程去走,比如说:一个项目,需要为某个客户打包部署某一个版本(这在toBe产品中会存在),那么这对你的构建工具来说需要考量的因素就更加复杂了。而这种,我们更倾向于特殊处理,为其单独编写构建的pipeline,并为其设置特定的jenkins构建方式。