前端架构之前端部署流程设计

前端部署流程需要做些什么,应该达到什么样的效果

今天有幸读到字节架构前端的一篇文章:2021 年当我们聊前端部署时,我们在聊什么

文章中基本列举了在设计前端部署流程中所有需要注意的点以及最佳的时间方案,看完之后收益很大。

值得一提的是,我之前并没有度过类似的文章,也没有参与过架构的培训。然而在日常的开发过程中,出于对原有流程的个人觉得不太好用的地方也尝试进行了改变。后台也出现了一些对于 CDN 历史资源以及 abtest 的要求,更加驱使我去优化前端的部署流程。

当时优化的思路其实是从业务需求和个人体验和理解出发,指定了一系列的部署程,当今天看到这篇文章时,居然发现很多文章中的关键思想和我当初的想法如出一辙。果然所有的技术进步都是由实际业务推动的,另外在业务一线实践过才能真正体会到架构的价值所在。

回顾一下我自己的优化方案

1、docker 镜像部署

原本的纯前端项目是由 docker 进行部署的,每个项目中都需要一个小型的“静态资源服务器”实现输出静态资源。流程大概如下:

这么做有几个问题:

  1. 每个项目都得起一个静态资源服务,浪费资源,也不好统一管理。
  2. 每次发布服务都会将上个版本的资源“覆盖”,无法保留之前版本的文件。
  3. 无法使用公共的静态资源

2、静态资源服务器

使用一台单独的服务器用于存储静态资源,并作为 CDN 的回源服务器。

该方案依赖 webpack 每个版本输出的文件名是不同的,以解决文件覆盖的问题,这里推荐使用 contenthash 模式(和 chunkhash 区别在于 css 的抽离不会影响 hash)

这样一来就解决了新老版本静态资源共存的问题,直接解决的业务问题比如 SEO 收录页面的快照样式问题。同时也方便快速回滚。

虽然资源的问题可以通过 hash 解决,但 html 是不走强缓存的,文件名也是不携带 hash 的,所以当 html 同步的那一刻起相当于线上发布成功。

3、文件存储服务器

我们希望纯前端的项目尽可能纯粹,不希望每次都通过发布服务上线,于是废弃了 docker 部署的方案,采用直接同步文件的形式进行部署。

那么问题来了,由于 runner 上构建的资源是临时目录,随时都有可能被清理,如何才能够保留上一个版本构建的内容呢?

于是参考了 docker 镜像仓库的设计模式,设计了一台专门的文件存储服务器专门用来存储各个版本的构建成果。

3、docker 部署优化

对于后端依旧使用 docker 部署的服务,可以对依赖安装这一步进行优化,可参考之前的文章:使用依赖镜像优化 docker 构建流程

回答一下部分上面文章中提到的问题吧

  1. 前端代码从 tsx/jsx 到部署上线被用户访问,中间大致会经历哪些过程?
    1. 简单的来说就是构建,同步文件,重启服务。里面细节挺多的。
  2. 上述过程中分别都有哪些考虑、指标和优化点,以满足复杂的业务需求?
    1. 考虑发布时间,权限控制,配置怎么隔离,如何灰度和快速回滚以及怎么做到自动化的代码检测
  3. 可能大部分同学都知道强缓存/协商缓存,那前端各种产物(HTML、JS、CSS、IMAGES 等)应该用什么缓存策略?以及为什么?
    1. 若使用协商缓存,但静态资源却不频繁更新,如何避免协商过程的请求浪费?
    2. 若使用强缓存,那静态资源如何更新?
  4. 配套的,前端静态资源应该如何组织?
  5. 配套的,自动化构建 & 部署过程如何与 CDN 结合?
  6. 如何避免前端上线,影响未刷新页面的用户?
    1. 保留旧版本的静态资源
  7. 刚上线的版本发现有阻塞性 bug,如何做到秒级回滚,而非再次部署等 20 分钟甚至更久?
    1. 上文的方法就可以
  8. 如何实现一个预发环境,除了前端资源外都是线上环境,将变量控制前端环境内?
  9. 部署环节如何方便配套做 AB 测试等?
  10. 如何实现一套前端代码,发布成多套环境产物?
    1. 动态配置下发,环境隔离,这个之前的文章有讲过
  11. 如何实现按 feature 发布产物供用户使用,并逐步扩大 feature 灰度,将影响减到最小(即线上同时存在多 feature 产物)?
  12. CDN 域名突然挂了,如何实现秒级 CDN 降级修补而非再次全部业务重新部署一次?
    1. webpack 动态 publicPath

文章中一些值得关注的点

关于静态资源、缓存和 CDN

总体内容和我们的架构类似,就是最大程度地利用缓存,html 使用协商缓存,所有 js,css 和图片等静态资源上 cdn 并使用强缓存,同时保证文件能够更新需要在文件名上添加 contenthash。

文章中关于如何将静态资源部署到 CDN 的内容:

图中的流程与本人对 CDN 的理解有所不同,CDN 本来就是利用路由帮用户选择最近的内容节点缓存,按理是不需要 nginx 进行反向代理。按照图上的访问路径的话,用户的请求都已经能到 nginx 服务器了,那直接把文件放在服务器中岂不是能达到更好的效果。

不过文中也提到了是为了避免 nginx 存储过大才上传的 CDN。我觉得这里应该是作者引用的名称有误,应该是指传到了 oss 之类的文件存储的服务中去,再将这个服务作为回源地址。CDN 本身是不作为源文件的存储用途的。

关于自动化构建

文章中介绍的整体流程与我们基本保持一致,我们使用的是 gitlab-ci + docker(ecs) 完成了服务的自动化构建和部署。但为了保证权限以及流程上的一些要求,部分步骤依旧是需要人工干预,主要有一下两个步骤:

  1. 上线代码合并,需要通过提交 MR 合并到主干,同时进行 code review
  2. 发版,需要 owner 手动创建 tag
  3. 最后发布上线需要手动确认

关于预发,灰度,ABtest

预发

首先是预发环境,前面有个问题也有提到如何做到“除了前端资源外都是线上环境,将变量控制前端环境内”。这里我们的做法是将即将上线的前端服务先发布到一台专门的机器上(如果是纯前端那就相当于只有 html,如果是有后端的 ssr 服务则按照正常服务部署,静态资源由于存在 hash 且不会覆盖可以正常先上 cdn),然后单独申请一个域名指向这台机器。

这样部署后访问这个预发的域名则能保证所有的前端代码是即将上线的代码,而后端的服务依旧是线上的服务。

关于灰度,ABtest

我们使用的简单做法与上面做预发环境的方式类似,也就是单独申请一个灰度域名用来做 ABtest。文章中提到一种使用 BFF 直出 HTML 的方式可以实现更细致的灰度控制。

使用 BFF 直出 HTML 也是我们去年主要推动的一点技术架构改动,在前后端完全分离之后,html 将和其他静态资源一起被放置在一台 nginx 服务器上由 nginx 输出,但 nginx 的控制权在运维手里,无法做到更深入的定制,所以由业务部门单独开发了一套 BFF 服务专门用来输出各个域名的 html 文件。

图中红框部分目前暂未完全实现,配置中心这一部分的功能依赖于 redis 配置,虽然简单但也能够满足需求。

值得高兴的是,原本这一套方案由于缺乏理论和实践支持,在实施时也是抱着尝试的态度进行,但实践下来的效果还不错,也坚定我继续完善的计划,现在看到字节的架构师也有同样的设计更坚定了自己的想法。

但是关于 BFF 加工 HTML 也有需要注意的地方,目前尝试过的方式有使用 cheerio 注入自定义 html 的方式,然而当 html 体量比较大的时候,例如同构 ssr 输出的 html 中携带了大量的 json 数据,导致解析时间过长使得 html 返回的时间很长,最终导致白屏时间变长的问题。另外也可以使用一些模板语法进行构建,但可能也会有类似的问题。

关于配置隔离和动态 publicPath

和文章构建时注入不同,这里讲的是运行时注入。

后端配置隔离相对比较好做,只要在服务启动时从配置中心拉取配置即可。而前端的配置如果需要在构建后获取的话,只能通过 BFF 修改 HTML 注入全局变量实现。

如果要做到一套 HTML 多环境部署的话,CDN 域名的切换是关键。HTML 中的静态资源可以通过字符串替换,模板渲染等方式修改,而 js 中引用的异步 chunk 只能采用动态 publicPath 修改 webpack 默认的配置来实现。关键是 css 和 css 图片的依赖无法动态修改,所以在构建时就需要根据 css 的特性将 publicPath 设置为相对路径。

相关资料

在线地址:https://www.processon.com/view/link/62061828f346fb3a0a331851

密码:suzhihao


前端架构之前端部署流程设计
https://www.wobushi.top/2022/前端架构之前端部署流程设计/
作者
Pride Su
发布于
2022年2月11日
许可协议