如何从 0 到 1 搭建全链路构建系统
正文如下
1.分享话题
本次分享话题主要是站在中小型团队的视角上,偏系统和技术性地分享政采云前端团队是如何从零到一搭建前端工程构建发布系统——云长系统。
2.分享大纲
通过本次的早早聊大会分享,你将从实现背景、系统基座、系统设计实现、未来规划 4 个方面了解到政采云前端团队 ZooTeam 在构建部署方面所做的探索。
3.背景
前端构建部署的发展已经历经多年,从早期前后端应用不分离,进行整体式部署,发展为前后端应用分离并通过打包工具产出静态产物,通过 CDN、静态资源服务器等托管最终产物,进而演进到通过 CI/CD 以及 Docker 等容器化环境进行持续集成以及独立应用级部署。
构建部署的方式日新月异,但在 19 年政采云的构建系统还是通过比较原始的方式进行着,前端的所有应用都接入了 Jenkins,通过预先在 Jenkins 系统中预设的流水线命令行脚本,针对不同的应用运行不同的脚本,有通过 Webpack CLI 打包的,也有 Gulp 打包的,还有自己编写的脚本打包的,可谓百花齐放。
但与此同时,也产生了很多问题。第一,使用体验不友好,全体应用都混合展现在一起,有不同业务线不同职能团队和不同类型的应用,查找十分耗时且各发布环境区分不明显易误操作,界面设计也比较原始,作为前端工程师,不能忍。第二,脚手架烟囱林立,需要维护多套脚手架以及不同的打包方式,难维护,兼容性差。第三,权限控制方式较为粗犷,只能按人员维度进行控制,无其他维度如应用、部门、时间等,发布权限通常集中在 2-3 个人身上,所有发布任务均通过这 2-3 人执行,一是加重相关同学负担,二是不灵活,夜间发布时也需要联系相关同学。第四,无法满足数据、安全、连通场景诉求,如进行工程化、研发方面数据统计,或者上线前执行代码检测,甚至与其他工程化工具打通如 GUI 客户端,IDE 等都显露出一定短板。
所以,我们决定搭建一套应用构建系统来解决以上痛点。这套系统需要满足一定要求:易用性较好;支持统一化构建方式,以及符合工程规范的应用直接接入;严格的权限控制,有一定的授权机制,通过审批机制临时下放某次发布的权限;最后支持数据统计,与多系统的对接集成。
4.系统基座
构建部署系统的基座其实就是工程、编码、工具的标准化规范化,有了统一的规范,才能够减少开发以及认知上的差异,系统也可以按照特定的流水线去执行应用构建的过程,而且遵循规范的应用可以快速接入。
工程标准化
在工程标准化方面,我们进行了项目结构的整理,针对以前多套项目、组件脚手架进行了统一整理,将目录结构、命名规范、工程依赖、生成入口等合而为一。其次针对不同框架类型,参考了业界的标准进行了统一编码规范制定,产出了相同的 ESLint 约束集。再次,抽离了之前配置在项目中的 Webpack Babel 等与构建相关的公共配置文件,由统一的 CLI 工具进行维护。最后,针对不同类型的项目,我们做差异化配置的支持,使不同项目运行与配置对开发者透明,同时也开放了自定义配置的入口,保留一定灵活性。
命令行工具
有了统一的标准,那么还需要一个工具来将标准化的项目给 'Run' 起来,这些主要交由 CLI 工具来负责。CLI 工具主要基于 Webpack Babel Node.js 编写,主要完成项目的本地启动、构建、发布、创建、升级等操作。上述核心操作的上层则是不同插件的加载过程,例如,针对 React 项目加载配套的 React-CLI-Service 插件,注入对应的操作命令,开发同学即可进行项目相关操作。以此来适配不同类型项目,达到统一开发入口的目的。
基础底座
除了上述部分,我们还产出了统一的 ESLint 规则集,在本地配合 husky
等开源工具在提交时做代码检查,同时也提供给服务端在应用上线前做代码规范扫描。至此有了标准工程、较为统一的编码规范、配套工具,整个构建部署系统的基座已经形成。
5.系统设计
设计目标
设计系统之前我们也有想过很多方案,有通过 Node 做一个应用管理系统后面构建发布还是走 Jenkins 的,也有通过 Node 完成构建部分,最终产物通过 CDN 和 nginx 静态资源托管的。但是计划没有变化快,唯一不变是变化,由于架构升级的关系,平台在推行整体应用部署的容器化,趁着这趟东风,我们也决定通过 Node.js + Docker 的能力进行前端应用的构建部署。
系统分层
有了系统大致技术层面的定性后,接下来就要考虑功能层面的设计。首先针对系统做了架构分层,包含核心层、服务层、展示层。最底部核心层提供数据以及工具,包含 CLI Service,Lint 规则集,核心的构建流程 Build-Core,以及应用、人员、工程等元数据。服务层主要的功能包含应用管理、构建部署服务、发布、权限、日志等基础服务,展示层主要对应运行在浏览器上页面功能,包括数据总览、应用管理、发布流程、权限申请审批这四大块。
展示层部分
展示层部分功能详细可见下图,其中核心功能为应用管理与发布流程。从功能上讲,应用管理主要包含了应用移交、分支管理、成员管理、变更记录等。成员管理为开发、测试等人员信息管理,以方便在发布流程中定位到对应人员;应用移交主要为应用管理员权限的转让;分支管理对应 git 分支的变更;变更记录为变更应用信息的操作日志记录。发布流程模块主要是从开发、测试、预发到生产环境的链路式构建发布,以及权限申请。
服务端部分
服务端整体功能亦参考下图,其中构建部署与发布流程管理为主要功能模块。构建部署主要包含分支代码拉取更新、NPM 依赖安装,代码规范检测、Webpack 打包构建,构建镜像的推送以及镜像滚动更新这几个过程。发布流程管理则主要包含发布流程的申请、流程节点签批、流转和完结通知、临时发布授权以及最终上线后进行代码的归档。
整体框图
结合上述梳理的系统功能模块,我们大致设计出最终的系统框图,展示层主要是基于 Vue + Vuetify 实现,而服务层是基于 Node.js + Koa2 + MySQL 来实现,下面我们主要看下服务层的具体流程。
API 网关
API 网关是服务端的最外层,主要负责对连接到服务的请求做一些认证与处理。请求连接时针对访问来源,通过 CORS 和域名白名单来提供基本的跨域访问支持,然后通过存储在系统中的公钥来解析请求头中的 Token,获取对应用户身份做基本的接口访问鉴权,同理,通过 WebSocket 的连接也需要做鉴权。认证完成后,系统最后决定是否提供服务或者建立会话,最终返回结果,但是考虑到存在服务异常的情况,网关的外层做了兜底,确保返回正确的状态码与可读性较好的提示。
构建队列
进行构建服务之前,考虑到服务器资源有限,且可能会存在多个应用同时构建的情况,需要一个队列来存储待运行的构建任务,排队执行减少资源开销。由于服务是多个 docker 实例部署运行,所以需要在多服务之间共享构建队列,这里连接单独部署的 Redis
实例,通过 list 结构来存储任务数据,通过 rpush 和 lpop
命令来实现入队出队操作,同时将正在运行的构建任务通过 HashMap 在 Redis 中存储。
工作进程
有了构建队列,系统可以在队列中取出任务依次执行,但是由于构建任务是 CPU-Sensitive
的应用场景,如果直接在主进程中执行任务会长时间阻塞而无法处理其他请求,所以要借助 Child_Process
来作为构任务运行的宿主。主进程通过消息将构建任务信息发送给子进程,子进程执行构建任务,将构建日志以及产物写入对应存储媒介,同时通过 IPC 信道将构建任务运行中产生的日志信回传给主进程,由主进程通过 WebSocket 推送给浏览器。
任务创建
新建一个构建任务时,通常携带着构建信息,包含应用 ID、Git 分支、CommitId、构建任务 ID 以及环境信息等。当这些信息被提交到服务端后,会根据应用 ID 与 CommitId 检查数据库中是否已经存在构建好的镜像,如果有就直接使用已存在的镜像进行部署。反之,则通过 Redis 判断任务是否已在构建或正在队列中,防止任务重复入队执行。
构建部署
当任务被成功创建后,接下来就是重头戏构建部署流程,主要包含四大块:代码检查、在线构建、产物存储以及部署分发。
代码检查代码检查主要涵盖 ESLint 编码规范检查、NPM 依赖检查、性能检查、安全检查。编码规范检查主要是依托上面提到的项目维度的编码规范规则集以及 ESLint 提供的 Node API 来进行。NPM 包检查主要内容为分析是否直接引用了在黑名单中的依赖包,性能检查则是使用内部的基于 Lighthouse 实现的页面性能评分系统,杜绝性能较差的页面发布。最后安全检查,则是针对代码中出现的不安全的引用如 HTTP 链接,特定域名,通过在 React / Vue AST 树解析过程中的检查特定代码片段,来定位到存在安全风险的内容。
应用打包通过了代码检查,则进入到应用打包的过程,本质上也就是 Webpack Build 的过程。构建过程中会传入应用的上下文内容,加载特定的 Webpack 插件,最终生成一份 Webpack 配置文件,通过运行 Zoo CLI 提供的 Node API 生成初步的构建产物。
依赖注入拿到初步生成的构建产物,其实也就是 HTML / JS / CSS 等静态资源文件,我们需要针对页面注入一些附加的资源,如添加埋点、监控等 SDK 和全局变量脚本,抑或是业务资源如全站吊顶等。通过 html parser 解析页面 HTML 结构,在 head body 等位置插入对应脚本片段,然后回写到对应文件中,至此便生成了最终的静态资源产物。
镜像生成由于前端应用托管在 docker 容器平台,所以还需要生成最终的镜像 image 供部署使用。这一步比较简单,通过将宿主机的 docker.sock 套接字文件链接到容器内部,便可以通过 docker client 在容器内部的构建服务中直接调用 docker 命令。然后获取应用需要的 Dockerfile,在产物目录下运行 docker build
即可生成应用镜像。
镜像部署生成出最终镜像之后通过 docker auth 验证,执行 docker push
命令推送镜像到私有仓库存储。部署时调用 Kubernetes
的 API Server 触发对应应用 Pod 内部镜像更新,由于镜像滚动更新是异步过程,所以需等待一段时间方可生效。至此,应用的整体构建部署流程基本上已经讲完了。
6.流程闭环
构建部署只是研发链路上的一个小环节,站在研发的链路上,我们寻求的是更多规范化可提效的系统建设,以及上下游环节的串联,以达到从需求设计到发布上线,再通过数据分析完成产品优化的流程闭环。
7.更多
未来,我们会继续打磨系统,在规范化的前提下通过自动化部署、移动端功能的补齐、以及与工程化 GUI 客户端 和 IDE 的集成来丰富整个构建系统的能力,让开发变得更加“傻瓜化”,解放出更多的人力做更有价值的事。
8.欢迎加入 ZooTeam
如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变既定的节奏,将会是“5 年工作时间 3 年工作经验”;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com
。
加微信 codingdreamer ,备注进群,加入大会专属内推群,及讲师团队钉钉群
扫码关注公众号,订阅更多精彩内容。