背景
公司最开始的前端项目(指的是csr客户端渲染项目)都是部署在ECS机器上,如下图所示
后面开始做微服务架构,为了方便统一管理与部署,前端项目就改成了容器化来进行部署,如下图所示
Pod内会起一个nginx服务,然后通过nginx来返回index.html(注意静态资源走的是CDN),如下所示
server {
listen 8000;
root /app/dist;
index index.html index.htm index.shtml;
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods GET,OPTIONS;
add_header Cache-Control "no-store";
expires -1;
try_files $uri /index.html;
}
}
经过几年的运行,及在私有化过程中碰到的一些问题,公司基于成本与现有的问题决定前端项目去容器化部署,于是调研了去容器化的方案,主要有两个思路
- 思路1: 恢复到旧的ECS机器部署
- 思路2: 使用公有云基于OSS提供的静态网站部署
经过初步评估之后,排除了ECS机器部署这种方式,尝试使用oss部署静态网站的方式,看是否满足我们功能的同时又可以节省成本所以,下面的内容都是针对使用oss部署静态网站的验证
方案
首先看下各云对部署静态网站的支持情况
- 阿里云 OSS ✅ 如何使用React及静态网站托管功能部署单页应用_对象存储-阿里云帮助中心
- 腾讯云 OSS ✅ 对象存储 使用 COS 静态网站功能搭建前端单页应用-最佳实践-文档中心-腾讯云
- 华为云 OSS ✅ 配置静态网站托管_对象存储服务 OBS_控制台指南_静态网站托管_华为云
- minio ❌ (本身不支持,需要增加代理层)
可以看到除了minio这种自建的存储方案,其它公有云都已经支持静态网站的部署
根据现有的情况做了两种方案
方案1
所有的应用都放到一个Bucket下,应用之间使用应用名作为隔离
- 优点:
- 所有的子应用最好放到同一个Bucket下面,方便查看与管理;
- 只要配置一个域名
- 缺点:
- 单纯通过oss提供的静态域名可能满足不了需求
多个应用放同一个Bucket,映射关系如下所示
方案2
方案2: 一个应用放到一个Bucket,应用之间使用Bucket隔离
- 优点:
- 应用之间隔离性更好,影响范围更小
- 可以针对各个应用单独设置Bucket配置
- 缺点
- 会创建多个Bucket
- 多个域名(可以通过反向代理解决,一个域名都不用设置)
一个项目一个Bucket,映射关系如下所示
接下来我们验证上面的方案是否可行
验证
在验证之前,我们需要一点前置知识
什么是单页应用
单页应用(英语:single-page application,缩写SPA)是一种网络应用程序或网站的模型,它通过动态重写当前页面来与用户交互,而非传统的从服务器重新加载整个新页面。这种方法避免了页面之间切换打断用户体验,使应用程序更像一个桌面应用程序。
简单来说就是url发生变化,页面根据url变化来渲染,且url变化的过程不会与后端服务进行交互,导致浏览器刷新。所以单页应用中前端路由有两种方式,hash模式与history模式,如果是hash模式,那么切换路由的时候,变化的是hash值而不是pathname,反之history模式下切换路由时变化的就是pathname;那么history模式下就会面临一个问题,刷新页面的时候怎么保证页面能够正常渲染,解决方案就是刷新页面的时候,确保服务端返回的也是index.html,然后由前端根据pathname渲染对应的页面,以nginx为例,保证刷新的时候返回的是index.html
server {
listen 8000;
root /app/dist;
index index.html index.htm index.shtml;
location / {
# 保证前端页面刷新的时候,返回的是index.html页面
try_files $uri /index.html;
}
}
另外有些场景,同一个域名访问多个隔离的应用,会用pathname来做隔离,这时候就需要在使用react-router or vue-router的时候配置basename,确保路由能够正确匹配上
publicPath是什么
publicPath是webpack中决定页面静态资源加载路径的配置,如果设置不正确会导致资源404,具体的配置如下所示
module.exports = {
//...
output: {
publicPath: 'https://cdn.example.com/assets/',
},
};
常用的配置有
- 设置为cdn地址,这样保证页面中加载的js、css、image都是从cdn上获取
- 设置为/,这样一般是独立域名部署,且未使用cdn的场景
- 设置为/ydxs-cms/, 这样一般是多个项目部署在一个域名下面,且未使用cdn的场景
- 设置为./,这样一般都是在本地开发中使用
了解了👆的前置知识时候,我们继续往👇看 这里先以华为云的OBS(对应阿里云的OSS)来进行验证 如果一点都没有了解过同学,可以先看下官方文档配置静态网站托管
在验证之前,我们需要先确定验证点,不论是单项目放一个Bucket还是多个项目放一个Bucket,前端项目(csr客户端渲染项目)功能要想正常,需要确保
- 前端路由能够正常匹配渲染
- 刷新页面的时候要返回的是正确的html,保证能够正常展示页面,而不是出现404获取找不到页面
- 图片、字体等静态、动态加载的静态资源能够正常加载
- html无缓存、js、css、图片等静态资源要有缓存
单项目
创建一个index.html,代码如下所示
<body>
<div>oss目录下的首页 v2</div>
<script src="./app.js"></script>
</body>
将index.html手动上传到Bucket根目录,然后通过自定义域名进行访问,访问结果如下所示
注意这里一定要使用自定义的域名,不然会以附件的形式下载,因为这些公有云都有限制
然后我们使用简单的react h5 项目进行验证 使用相对路径即publicPath设置为’./’,结果会出现css中的图片找不到
换成绝对路径即publicPath设置为’/’ css中的图片可以正常访问
切换前端路由并刷新,返回的是404页面,没有返回正确的html
将错误404页面修改成index.html
页面是能够正常显示,但是状态码是403
到这里我们可以知道,OBS Bucket作为单个react项目渲染是没什么问题的,唯一有点问题的就是403这个状态码了(没找到解决方法)
多项目
同一个Bucket放置多个项目,然后通过目录(项目名)区分不同的子应用,先创建一个qmyx-cms目录,然后在目录内上传一个index.html,index.html内容如下所示
<body>
<div>oss/qmyx-cms目录下的index.html</div>
</body>
然后点击切换路由能够正常展示,但是刷新之后页面就不行了,返回的html内容是Bucket根目录下的index.html,而不是qmyx-cms/index.html目录下的index.html
找了一圈没有找打可以根据路径配置404错误页面的配置,然后试了下配置中的重定向规则
重定向规则是会进行302跳转的,所以整个过程url会发生变化,如下图所示,所以解决不了我们的问题
一个OBS Bucket放置多个react项目,直接通过自定义域名的方式来访问OBS多项目是满足不了这种多项目场景的,原因是无法根据path指定index.html
那么从反向代理的角度来看,是不是可以满足同一个Bucket多个项目 在Bucket上创建两个项目qmyx-cms与ydxs-cms,两个项目放入上面的react h5项目,如下所示
先用配置的自定义域名进行访问看下,是否能够正常渲染 直接访问,会发现不行,原因是publicPath设置的值为/,这时候在根目录下肯定是没有对应的静态资源的,所以需要将publicPath设置为publicPath: ‘/qmyx-cms’,
静态资源可以了,但是显示的是404,所以需要修改react-route的basename=’qmyx-cms’,但问题是我们不需要自定义域名直接访问,而是通过反向代理的域名来进行访问,所以不用管
同理,我们将另外一个ydxs-cms的项目也上传上去
项目在Bucket上传好了之后,我们现在通过反向代理的方式来进行访问两个项目 在本地添加nginx配置,如下图所示
server {
listen 8890;
server_name ydxs-react.com;
location / {
proxy_pass http://test-web.xxx.cn/ydxs-cms/;
}
}
上面的错误,是proxy_pass的路径没写对导致,写对了就不会有上面的错误了
最终写出来的nginx配置如下所示
server {
listen 8891;
server_name ydxs-react.com;
location /ydxs-cms {
proxy_pass http://test-web.xxx.cn;
index index.html index.htm;
}
location / {
try_files $uri @redirect;
}
# 需要将ydxs-cms的路由全部要返回index.html,不然会出现403错误无法正常展示页面,原因是找不到对应的资源时,会返回根目录下配置的error.html
location @redirect {
rewrite ^ /ydxs-cms/index.html break;
proxy_pass http://test-web.xxx.cn;
}
}
然后在访问项目,项目已经可以正常渲染了
但是出现了一个问题,图片无法正常加载,原因是因为路径不对,如下所示
修改一下publicPath,将值由/ydxs-cms改为/ydxs-cms/,这样就可以保证动态加载的文件路径与静态加载的图片资源路径都是对的
最终图片都可以正常加载
到这里,我们已经可以确认,通过反向代理的方式是可以满足一个OBS下放置多个项目的场景的,因为
- 前端路由能够正常渲染
- 刷新页面,路由能够正常渲染
- 图片、字体等静态字体静态or动态的方式加载正常
在继续验证html、js、css等静态资源的缓存策略
可以看到通过OBS上返回的静态资源,都返回了etag与last-Modified属性,所以对于html、js、css等静态资源不用担心缓存问题(注意这里是抛开cdn场景来说的)
也就说,每次发版,html的etag都会发生变化,最终会走协商缓存,展示最新的html内容,所以不会存在迭代发布之后,内容没更新的情况
当然如果OBS自己返回的header不保险,也可以在反向代理的nginx那里配置cache-control: no-store,如下所示
server {
listen 8891;
server_name ydxs-react.com;
location @redirect {
rewrite ^ /ydxs-cms/index.html break;
+ add_header Cache-Control "no-store";
proxy_pass http://test-web.xxx.cn;
}
}
到这里,我们可以得出结论,在华为云下面同一个Bucket下面放多个项目,通过反向代理的方式,是可以保证前端项目正常加载与渲染的
在回过头验证一点,就是通过反向代理OBS提供的默认静态网站域名,而不是自定义静态网站域名行不行,这样就可以少配置一个域名,修改nginx配置如下所示
server {
listen 8893;
server_name ydxs-react.com;
location /ydxs-cms {
- proxy_pass http://test-web.xxx.cn;
+ proxy_pass https://xxxx.obs.cn-south-1.myhuaweicloud.com;
}
location / {
try_files $uri @redirect;
}
location @redirect {
rewrite ^ /ydxs-cms/index.html break;
add_header Cache-Control "no-store";
- proxy_pass http://test-web.xxx.cn;
+ proxy_pass https://xxxx.obs.cn-south-1.myhuaweicloud.com;
}
}
直接访问会发现文件是以附件的形式下载,正常现象,因为文档里面就是这么描述的
然后与自定义域名的方式对比,发现多了一个header头Content-Disposition:attachment
我们看下这个Content-Disposition:attachment起的作用到底是啥 更多内容可以查看mdn文档Content-Disposition,所以我们知道可以去掉这个header头,走默认的内容形式即可 我们在反向代理的时候把这个头干掉来试一下,通过proxy_hide_header去掉Content-Disposition字段
server {
listen 8893;
server_name ydxs-react.com;
location /ydxs-cms {
proxy_pass https://xxxx.obs.cn-south-1.myhuaweicloud.com;
}
location / {
try_files $uri @redirect;
}
location @redirect {
rewrite ^ /ydxs-cms/index.html break;
add_header Cache-Control "no-store";
+ proxy_hide_header Content-Disposition;
proxy_pass https://xxxx.obs.cn-south-1.myhuaweicloud.com;
}
}
去掉之后就可以正常渲染了
当然返回的js、css、图片等静态资源还有这个header字段,我们可以通通去掉
再次刷新缓存访问,就会发现js等静态资源的Content-Disposition:attachment也去掉了
当然上面只是简单粗暴的将Content-Disposition去掉了,后续还要验证真正下载的接口是否能够正常下载;
结论
通过上面的验证,可以得出如下结论(华为云下)
- OBS Bucket作为单个react项目渲染是没什么问题的,唯一有点问题的就是403这个状态码了(没找到解决方法,在阿里云那边到是看到可以修改状态码的方式)
- 一个OBS Bucket放置多个react项目,直接通过自定义域名的方式来访问OBS多项目是满足不了这种多项目场景的,原因是无法根据path指定index.html
- 面同一个Bucket下面放多个项目,通过反向代理的方式,是可以保证各个前端项目正常加载与渲染
- 通过反向代理的方式,可以不用给Bucket配置自定义的域名,但是需要验证正常的下载功能是否OK
- 页面无法正常渲染或者资源无法正常获取,排查publicPath、basename这些控制产物url及控制前端路由的参数
另外还需要确认Bucket的稳定性,因为当Bucket不能访问时会导致应用访问不了,所以要有对应的降级方案;其次还需要确认Bucket的QPS怎么样,能不能承受项目的流量;最后还需要去确认Bucket的安全性有没有问题,当确认完成之后,才会进行真实项目迁移
从华为云的验证结果可以推测阿里云与腾讯云的应该都是可以的,我们可以根据自己公司的实际情况从
- ECS机器部署
- 容器化部署
- OSS部署
选择适合前端csr项目的部署方式。