想要毫秒级切换网站版本吗?想要快速实现灰度吗?快来看最新的源代码把。 项目地址:GITHUB
精益求精
灵魂提问:前端的发版应该式怎么样的?
这里简单说一下我们之前的发版流程。首先,前端的源代码式放在gitlab上的。这样开始发版的时候,只需要Jenkins配置好,就能一键启动。
具体流程:
- Jenkins从gitlab上拉代码到本地。
- 执行运维内嵌的脚本(通常使用pipeline),启动项目的依赖安装。
- 执行运维内嵌的脚本,启动项目的编译,生成静态文件。
- 启动docker build,基座使用nginx。将相关资源打包成镜像。
- 推送到k8s系统中,执行相关pod的启动和销毁。
上面这个就是简单的Jenkins流程,换成其他的打包系统,整体流程是不变的。那么问题来了,这里的设计已经很先进了,还有什么要改进的?
我的问题
工作中确实可以简单的实现前端发布,甚至是推给后端,让他们做一个简易版。一个ftp就可以搞定。但是这些是完全不够的。做技术还是要有那么一些追求的。比如,怎么样做的更牛比,怎么样设计的更高效。
经过我的思考,问题如下:
- 依赖安装过于占用时间,需要优化。
- 编译之后的静态资源要放到CDN上。(这一步很简单,大部分人都可以轻松办到)
- 基于nginx的前端存在缓存问题,每次发版都有可能出现用户资源未更新的情况。假如你说增加no-cache参数,那么问题就变成了用户每次都要拉资源,太慢了,不合理。
- 如果项目要随时上线,那么切换测试环境和生产环境有重新编译的可能(大部分网站在业务增加之后都有测试环境和生产环境2套配置)。QA对这个流程不放心,而实际上也确实可能存在换环境就出问题的可能。
- 如果项目要进行小范围测试(或者是线上验证)。那么只能发布到线上再进行验证。如果有流量进来,又恰好代码出了问题,那么就会造成实际的线上bug。
- 最后就是通知了。编译或者上线要有通知,这样才能让相关人员看到进度。
这些问题都不算严重问题,是可以随时克服的(QA和运维克服)。但是本着极客精神,我们要探索所有的可能性。
方案确定
编译前优化
编译前的很多动作是可选的,这里列举一些措施,后面的具体实现中就不做详细说明了。
- 打包工具的优化。常用的webpack已经过时了,单线程低效率的打包已经不能满足日益复杂的项目。需要替换成多线程高效率的打包工具。比如rspack、esbuild、swc等等。
- 代码检查的优化。可以在某个节点增加代码检查的流程。比如在提交前检查代码格式(很多现成的,比较好集成)。或者是合并到主分支前的代码扫描,gitlab增加hooks或者流水线,合并前执行sonar的代码扫描。还可以增加一个通知,把扫描结果发到群里。
- 增加私有库。私有库可以放一些公司内部的库,同时也能一定程度提高依赖安装速度。(私有库的网络设计不好,会导致公有库的包更新不及时甚至更新失败)
编译中的优化
这个阶段已经把代码拉到了编译服务器的磁盘中,我们要优化的是编译的时长。根据不同的阶段,可以简单分成需要依赖的编译流程和不需要依赖的编译流程。
需要安装依赖
这个阶段大致分成两种可能,可以单独去处理。
- 新项目,本地没有任何历史记录。不需要考虑任何优化,所有事情都要做一遍。
- 老项目,package.json有版本更新。这里需要注意的是有的人可能会把一个依赖复制两次,需要兼容这种情况。
优化手段比较简单,主要是检查到上面两种情况之一就执行npm install
来安装依赖。也可以执行npm ci
来减少信息输出,可以加快一点安装速度。同时,还要考虑,当本地缓存有问题的时候,要删除缓存再执行命令。
不需要安装依赖
正常项目其实只是业务逻辑的更新,了不起增加几个新页面。在这种情况中,我们是不需要安装依赖的,在编译流程中完成可以去掉这个流程。可以极大的减少发布时间。在发布脚本中需要判断当前项目是哪种情况,从而动态执行依赖安装流程。
其他优化
由于案例项目是使用Jenkins来发布的,所以还需要增加Jenkins的优化。在发布选项中增加额外的参数来。
- 增加选项,控制是否清空本地缓存。
- 增加选项,是否手动执行安装依赖。
编译后的优化
这个案例中编译后会执行镜像打包的流程。有些公司使用的是虚拟机或者云服务器,我们从简单的开始讲。
使用云服务器
这个模式下,基本就是搭建一个nginx服务,将文件推到ngxin的目录中。好处是简单实用,坏处是不够友好。
- 解决静态资源更新不及时问题。静态资源直接同步(比如ftp或者sync之类的方式)到nginx引用的目录,如果遇到用户正在下载,就会出现部分静态资源404的清空。解决办法:
- 静态资源先同步到一个固定文件夹,再完成之后一次性复制到nginx下。适用于用户较少访问的情况。
- 实用CDN。先把静态资源同步到CDN服务,再确定同步成功之后,直接同步到云服务器即可。
- 解决同步失败和回滚问题。由于网络或者其他情况,会导致同步文件失败。有时候也会遇到要回滚的情况。
- 可以按照固定格式创建同步文件夹,静态资源先同步到这里,然后再复制到nginx下。
- 或者也可以在同步完成之后,直接修改nginx配置,重启nginx就行。
使用容器方式
打包成镜像再推送到服务器可以简化流程,减少可能出现的问题。但是镜像有它自己的需要优化的点。
- 优化镜像大小。可以尽量挑小基座来打包。比如:nginx-alpine,只有17MB。或者是alpine-slim,只有5MB。这些小镜象缺少一些常用的库,如果是对这个有依赖,可以尝试手动安装,或者是更大一些的基座。还有就是优化dockerfile中的流程,减少layout,尝试使用缓存。
- 优化镜像大小2。这里的大小指的是镜像中静态资源的大小。使用CDN之后很多js、css文件已经不再需要打包到镜像中了。这里可以将这些内容去除,留下必要的内容。
- 优化启动顺序。pod启动是相当快的,但是假如你的pod中有一些后启动或者慢启动的项目,就要考虑太快启动造成的问题。这里可以在k8s中增加探活的机制。只有新的pod可用之后再切换新老pod的使用。其次就是旧的pod必须要等流量完全没有之后再销毁。
上线后的优化--重构
其实我们现有的项目已经很不错了。可以做到很便捷的上下线,同时也尽量减少了人工出错的可能。但是这些还不够,还要增加上面提到的几个优化。
新的优化不再依赖nginx的静态资源分发,需要我们自己处理:
- 编译之后的静态资源存CDN(降级处理可以存本地,作为一个冗余备份)。
- 入口文件
index.html
存数据库,可以设定测试换自动生效,生产环境自动进入灰度。 - 增加校验,防止本地开发发布到测试环境或者生产环境。
- 增加域名管理,根据不同的域名处理不同的项目。多项目,每个项目使用不同的域名情况下,需要隔离开每个域名的绑定内容。
- 增加配置管理,每个环境有自己独立的配置,可以增加基础配置,用来检验不同环境的配置是否必填等。
- 增加灰度能力。看情况开发,比如线上试用,走百分比灰度。线上验收,走体验版本或者白名单。小范围内部验证,走ip定向灰度。
- 快速切换能力。上线和回滚只需要点一个按钮就行。
- 缓存。不同的版本切换要清空缓存,同一个版本多次访问,要利用缓存。
以上内容已经足够独立成一个项目了。在一个正经的项目中,我们还要增加额外的支持能力:
- 用户管理、权限管理。不同的用户要区分开,不能每个人都去上线发布。
- 快捷登录。每个公司都会有自己的用户体系,可以集成这些鉴权。比如ladp或者钉钉等。我们接入的是钉钉,可以将页面嵌入在钉钉的应用栏中,快捷登录使用。
- 记录日志。每一个操作都应该留痕,要清晰的知道是谁操作了,同时记录操作改了哪些内容。
未来规划
我们在项目中已经稳定运行了一年多的时间。经过了多次发版的验证,可以很明确的感受到发版的效率在提高。不管是白天还是晚上,可以随时发版。在不影响线上的情况下可以做到线上回归验证。前端再也不用等待发版了,我们可以一边刷手机,一边等其他人操作。要是遇到问题,我们也能急速回滚,从开始回滚到旧版本生效,甚至不会超过1秒。
当然,我们也准备了接下来的规划。将现有的能力拓展的更加厉害。
- 更好的配置管理。配置已经可以做到注入页面使用了,但是还缺乏校验、已经语法提示。接下来在不增加操作难度的情况下,增加配置内容的校验,已经在开发中动态注入字段和提示。
- 自由创建测试环境。现有的环境还是固定的几个(其实已经可以自由切换了),但是我们要在现有的基础上增加无限多的测试环境。谁都可以给自己创建一套环境,联调和测试再也不怕环境不够用了。
- 更多的快捷登录和权限。现有的权限比较简单,控制的还不够精细,我们计划在未来增加更精细的权限控制。
- 手动修改HTML内容。在实践中,其实还有一个小小的需求,就是可能需要手动改一些html的内容,未来也将增加这部分的规划。
项目现在已经开源,愿意动手的已经可以下载下来试用了。如果还不太了解,也可以在demo中研究一下各种功能。目前的demo还不支持自由创建域名,只有一个域名也可以简单看看使用效果。
项目地址:GITHUB
DEMO地址DEMO
测试的域名:测试域名
更多代码和流程的解析请看后续文章。