静态资源灰度发布
# 🔵 静态资源灰度发布(Canary Release for Static Assets)
静态资源(HTML/CSS/JS/Images 等)没有服务端逻辑,因此灰度发布的核心是:
让部分用户访问新版本静态资源,而其他用户仍使用旧版本。 实现关键:分流 + 版本并存 + 可回滚
下面给你常见可行方案:
# ⭐ 方案 1:Nginx / CDN 分流(最常用、最稳定)
# ⛳ 思路
- 构建两份静态资源:old 与 new
- 按一定规则将部分用户路由到 new 资源目录
- 规则可以是:
IP、Cookie、Header、AB Test ID、账户等
# 🔧 Nginx 配置示例(按 Cookie 分流)
# 默认走旧版本
set $version "v1";
# 如果 Cookie 中带 version=v2 则走灰度版本
if ($http_cookie ~* "version=v2") {
set $version "v2";
}
location / {
alias /var/www/$version/; # 指向对应版本资源目录
try_files $uri $uri/ /index.html;
}
2
3
4
5
6
7
8
9
10
11
12
# 用法
给灰度用户设置 Cookie:
document.cookie = "version=v2; path=/"
# 优点
✔ 精准控制用户比例 ✔ 版本共存,可随时回滚 ✔ 对前端透明,兼容 SPA
# ⭐ 方案 2:CDN 边缘规则灰度(企业常用,如阿里云、Cloudflare)
CDN 支持:
- 按 User-Agent 分流
- 按地域分流
- 按 Cookie 分流
- 按百分比分流(权重)
# 示例(Cloudflare Worker/PHP Rule)
if (Math.random() < 0.1) {
// 10% 用户访问新版本
return fetch("https://cdn.xxx.com/app-v2" + request.url);
} else {
return fetch("https://cdn.xxx.com/app-v1" + request.url);
}
2
3
4
5
6
# ⭐ 方案 3:HTML 层 AB Test(适用于 SPA)
# ⛳ 思路
给不同用户返回不同版本的 index.html,其里面的 JS/CSS 路径不同。
示例:
index.v1.html-> 引用/static/v1/*index.v2.html-> 引用/static/v2/*
Nginx/Server 决定给谁返回哪份 HTML。
# ⭐ 方案 4:单页应用内部版本切换(有限场景)
在 JS 中决定加载哪个版本的资源(比较少用)
if (isGrayUser()) {
loadScript("/static/v2/main.js");
} else {
loadScript("/static/v1/main.js");
}
2
3
4
5
# ⭐ 方案 5:CI/CD 流程灰度
结合脚本自动执行:
- 上传
v2静态资源 CDN / Nginx设置x%灰度分流- 监控指标
OK→ 提升百分比 → 全量 - 若异常 → 切换回
v1
# 🔥 关键注意点
# 1. 必须使用带 Hash 的构建产物
比如:
main.55c33.jsvendor.9ad01.css
否则缓存会导致用户版本不一致。
# 2. 旧版本资源必须继续保留
否则灰度用户刷新页面会 404。
# 3. index.html 才是灰度的关键
CDN 缓存策略要保证:
页面(HTML)可灰度,静态资源(JS/CSS)长期缓存。
最常见配置:
HTML:no-cacheormax-age=0JS/CSS:cache-control: max-age=31536000, immutable
# 🟢 给你一个非常简短易记的总结
静态资源灰度 = 两个版本并存 + 控制不同用户返回不同
index.html。核心:HTML 分流,资源多版本共存,缓存版本锁定。
下面给你一个完整、可真实上线的 Vue 项目静态资源灰度发布案例。
包含:构建、多版本部署、Nginx 配置、Cookie 灰度分流、回滚方案。
内容非常实用,面试也能直接讲。
# 🔵 Vue 项目静态资源灰度发布(完整可落地案例)
# 📌 场景
你的项目是:
my-app/
dist/
v1/ ← 当前线上版本
v2/ ← 新灰度版本(准备发布)
2
3
4
你想让:
10%用户访问v290%用户继续使用v1- 可以随时回滚
- 旧版本资源继续保留,避免
404
# 第一步:构建产物做版本隔离
# 🔧 build 脚本(package.json)加 version 参数
{
"scripts": {
"build:v1": "vue-cli-service build --dest dist/v1",
"build:v2": "vue-cli-service build --dest dist/v2"
}
}
2
3
4
5
6
执行:
npm run build:v1 # 线上的旧版本
npm run build:v2 # 即将灰度的新版本
2
这样 Vue 会分别打包到:
/dist/v1/*/dist/v2/*
并且每个 JS/CSS 都带 hash,不冲突。
# 第二步:服务器结构
将两套版本部署到服务器:
/var/www/my-app/
v1/
v2/
2
3
⚠
index.html不能缓存,否则灰度分流无效。
# 第三步:Nginx 灰度配置(核心)
下面是可直接用的灰度分流配置,按 Cookie 判断用户落到 v1 还是 v2。
# 🔧 Nginx 配置(可直接复制)
# 给灰度用户设置 Cookie 后即可访问 v2
map $http_cookie $version {
default "v1";
"~*version=v2" "v2";
}
server {
listen 80;
server_name example.com;
# 根路径
location / {
alias /var/www/my-app/$version/;
# 关闭 index.html 缓存
add_header Cache-Control "no-cache";
try_files $uri $uri/ /index.html;
}
# 静态资源开启强缓存
location ~* \.(js|css|png|jpg|jpeg|gif|svg)$ {
alias /var/www/my-app/$version/;
add_header Cache-Control "max-age=31536000, immutable";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ✔ 说明
📌 Cookie 中 version=v2 → 访问 v2
📌 无 Cookie → 默认访问 v1
📌 JS/CSS 永不过期(带 hash,不会冲突)
📌 HTML 不缓存,以便实时分流
# 第四步:给灰度用户注入 Cookie
方式一:浏览器控制台测试
document.cookie = "version=v2; path=/";
刷新 → 强制进入 v2 版本。
方式二:服务端给部分用户下发 Cookie(真实灰度)
例如:
# 随机 10% 灰度用户
set $rand "";
set $rand $request_id;
# 取 request_id 的最后一位数字
if ($rand ~ "([0-9])$") {
set $last_digit $1;
}
# 0~9 的数字,10% 落入 v2
if ($last_digit = 1) {
add_header Set-Cookie "version=v2; Path=/;";
}
2
3
4
5
6
7
8
9
10
11
12
13
比如末尾是 1 的用户下发灰度 Cookie。
# 第五步:回滚方案(非常重要)
# ❌ 发现问题?一键回滚:
只需改 Nginx 的分流逻辑即可:
修改
Nginx分流:所有用户走v1map $http_cookie $version { default "v1"; }1
2
3reload Nginxnginx -s reload1等待所有灰度
Cookie失效或立即清除Cookie确认没有请求访问
/v2/目录(通过Nginx access log)最后删除
/v2/目录
回滚非常快,因为是纯静态资源。
# 第六步:CI/CD 自动化(可选)
比如:
Jenkins / GitHub Actions编译v2- 上传
v2到服务器 - 修改
Nginx分流比例从1% → 5% → 10% - 观察日志和告警
- 全量后删除
v1(或保留 1 个历史版本)
# 🟢 最终效果
- 访问同一个域名
- 不同用户访问到不同版本的
Vue应用 - 旧资源保留可随时回滚
index.html动态灰度,JS/CSS永久缓存Vue项目完全无侵入
# 🔵 灰度自动化脚本
脚本以 Nginx + Cookie 分流 为例,可用于 Jenkins / GitHub Actions / 手工执行。
重点特性:
✔ 支持设置灰度比例(如 5%、10%、30%、50%、100%)
✔ 自动写入 Nginx 的 map 配置
✔ 自动 reload Nginx
✔ 自动备份配置(防误操作)
✔ 全量后自动关闭灰度逻辑
这是行业常用的一套静态资源灰度控制流程。
# 🟦 1. Nginx 分流方案(基于随机数灰度)
你需要在 Nginx 中预留一个字段,用于不同脚本来修改:
# /etc/nginx/conf.d/gray.conf
map $cookie_gray $gray_flag {
default "off"; # 可由脚本修改为 on/off
}
map $request_id $gray_bucket {
"~(?<num>[0-9])$" $num; # 取 request_id 的最后一位数字 (0-9)
}
map "$gray_flag:$gray_bucket" $version {
# 灰度开关关闭 → 全量旧版本
"off:0" "v1";
"off:1" "v1";
"off:2" "v1";
"off:3" "v1";
"off:4" "v1";
"off:5" "v1";
"off:6" "v1";
"off:7" "v1";
"off:8" "v1";
"off:9" "v1";
# 灰度开关开启 → 实际灰度逻辑由脚本注入
# (脚本会把 gray.conf 的内容替换成对应灰度比例)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
脚本只需要修改 灰度比例对应的映射表。
# 🟦 2. 灰度脚本 gray.sh(支持 0–100% 任意比例)
创建文件:
/opt/scripts/gray.sh
内容如下(可直接用):
#!/bin/bash
# -----------------------------
# 参数检查
# -----------------------------
if [ -z "$1" ]; then
echo "❌ 使用方法: ./gray.sh <比例>"
echo " 示例: ./gray.sh 10 # 灰度 10%"
echo " 示例: ./gray.sh 100 # 全量"
exit 1
fi
PERCENT=$1
if [ "$PERCENT" -lt 0 ] || [ "$PERCENT" -gt 100 ]; then
echo "❌ 灰度比例必须是 0~100"
exit 1
fi
# -----------------------------
# 配置文件位置
# -----------------------------
NGINX_GRAY_CONF="/etc/nginx/conf.d/gray.conf"
BACKUP="/etc/nginx/conf.d/gray.conf.bak"
# 备份配置
cp $NGINX_GRAY_CONF $BACKUP
# -----------------------------
# 生成新的灰度映射规则
# -----------------------------
echo "⚙ 正在更新灰度比例为:${PERCENT}% ..."
# 计算有多少 bucket 命中灰度
# bucket 0~9,一共 10
GRAY_BUCKET_COUNT=$((PERCENT / 10))
# 处理小数,如 15% → 灰度 1 个 bucket(10%)
if [ "$PERCENT" -gt 0 ] && [ "$GRAY_BUCKET_COUNT" -eq 0 ]; then
GRAY_BUCKET_COUNT=1
fi
# -----------------------------
# 写入新版 gray.conf
# -----------------------------
cat > $NGINX_GRAY_CONF <<EOF
# 本文件由 gray.sh 自动生成
map \$request_id \$gray_bucket {
"~(?<num>[0-9])$" \$num;
}
map \$gray_bucket \$version {
EOF
for i in $(seq 0 9); do
if [ "$i" -lt "$GRAY_BUCKET_COUNT" ]; then
echo " $i v2;" >> $NGINX_GRAY_CONF
else
echo " $i v1;" >> $NGINX_GRAY_CONF
fi
done
echo "}" >> $NGINX_GRAY_CONF
# -----------------------------
# 重载 Nginx
# -----------------------------
nginx -t
if [ $? -ne 0 ]; then
echo "❌ Nginx 配置错误,正在回滚..."
cp $BACKUP $NGINX_GRAY_CONF
exit 1
fi
nginx -s reload
echo "✅ 灰度比例已更新为 ${PERCENT}%"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# 🟦 3. 使用方式(非常简单)
# ✔ 灰度 10%
./gray.sh 10
10% 用户访问 v2,90% 进 v1。
# ✔ 灰度提升到 30%
./gray.sh 30
无需重启,只会让更多用户落到 v2。
# ✔ 灰度 90%
./gray.sh 90
# ✔ 全量 100%
./gray.sh 100
系统自动把所有 bucket 指向 v2,相当于完全发布。
# ✔ 关闭灰度(回滚 v1)
./gray.sh 0
所有用户全部回到 v1。
# 🟦 4. 日志查看(确认灰度比例)
Nginx access log 中增加:
$version
示例:
log_format main '$remote_addr - $version - [$time_local] "$request" $status';
这样你能看到当前用户落到哪个版本。
# 🟦 5. 在 CI/CD中使用
在 Jenkins pipeline 中:
stage('灰度 10%') {
sh "/opt/scripts/gray.sh 10"
}
stage('灰度提升 50%') {
sh "/opt/scripts/gray.sh 50"
}
stage('全量') {
sh "/opt/scripts/gray.sh 100"
}
2
3
4
5
6
7
8
9
10
11
# 🟢 最终效果(你已经具备)
- ✔ 支持任意灰度比例
0–100% - ✔ 自动生成
Nginx配置 - ✔ 自动
reload - ✔ 支持全量发布
- ✔ 支持一键回滚
- ✔ 可以集成
CI/CD - ✔ 不需要改前端代码
- ✔ 不需要改
Nginx主配置结构
# 🔵 灰度失败 → 自动降级到稳定版本
真正可在生产使用的「灰度失败 → 自动降级到稳定版本」机制,包括:
- ✔ 前端自动检测灰度版本加载失败
- ✔ 自动重新加载稳定版本(v1)
- ✔ 避免无限循环 + 保证用户最终可正常访问
- ✔ 适用于 Vue CLI / Vite / SPA
- ✔ 可同时配合 Nginx 灰度发布使用
# 🟦 一、核心思路
灰度(v2)用户可能遇到的问题:
- 新版本 JS 加载失败(网络 / CDN / 打包错误)
- 白屏(入口文件 load 失败)
- 静态资源 404
- 部分文件缓存冲突
当 v2 失败时,应自动降级到 v1,以保证用户不受影响。
机制如下:
- 浏览器加载灰度的 index.html(指向 v2/main.js)
- 如果任何入口文件(main.js / chunk)加载失败
- 自动切换 cookie:
version=v1 - 强制刷新加载旧版本 v1 的页面
- 永不陷入循环:最多尝试 1 次
# 🟦 二、Nginx 分流(前提)
Nginx 根据 Cookie 分流:
map $http_cookie $version {
"~*version=v2" v2;
default v1;
}
2
3
4
灰度用户有 version=v2 → 加载 v2
降级用户 version=v1 → 稳定版 v1
# 🟦 三、前端自动降级脚本(放在 index.html 顶部)
这是关键!
要放在 index.html 的 <head> 中,优先于所有 JS 加载。
<script>
(function () {
// 降级标记(防无限循环)
const retryKey = 'gray_retry';
// 若已经降级过一次,不再重复
if (localStorage.getItem(retryKey) === 'done') return;
// 全局资源加载失败监听
window.addEventListener('error', function (e) {
const target = e.target;
// 仅处理 JS/CSS 资源错误
if (target.tagName === 'SCRIPT' || target.tagName === 'LINK') {
// 记录已降级
localStorage.setItem(retryKey, 'done');
// 切换到稳定版 v1
document.cookie = "version=v1; path=/; max-age=3600";
// 刷新页面重新加载 v1 资源
window.location.reload(true);
}
}, true);
})();
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 🟦 四、降级的工作原理
假设灰度用户加载:
/v2/js/main.abc123.js
如果:
- 文件损坏
- CDN 部署不完整
- JS 报错
- 404
- chunk 丢失(懒加载失败)
浏览器触发 window.error 事件(资源加载异常)。
然后脚本会自动:
# 1. 设置降级标记,避免无限 reload
localStorage.setItem('gray_retry', 'done');
# 2. 切换 Cookie
document.cookie = "version=v1; path=/";
# 3. 立即刷新页面 → 加载稳定版本 v1
window.location.reload(true);
# 🟦 五、确保不会无限循环(防止死循环)
只允许触发一次降级:
if (localStorage.getItem(retryKey) === 'done') return;
所以逻辑是:
- 第一次灰度加载失败 → 自动降级
- 第二次(稳定版 v1)加载一定不会失败 → 不会重复触发
# 🟦 六、额外增强:捕获 JS Runtime 白屏错误
有时不是资源失败,而是代码 runtime 崩溃(Vue 初始化失败)。
加上:
<script>
window.addEventListener("error", function(e) {
// runtime 错误白屏(灰度版本常见)
if (e.message && localStorage.getItem("gray_retry") !== "done") {
localStorage.setItem("gray_retry", "done");
document.cookie = "version=v1; path=/;";
location.reload();
}
});
</script>
2
3
4
5
6
7
8
9
10
# 🟦 七、额外增强:捕获未捕获 Promise 错误
很多 chunk 加载失败会走这里:
<script>
window.addEventListener("unhandledrejection", function(e) {
if (localStorage.getItem("gray_retry") === "done") return;
// 如果是动态 chunk 加载失败
if (e.reason && e.reason.message && e.reason.message.includes('Loading chunk')) {
localStorage.setItem("gray_retry", "done");
document.cookie = "version=v1; path=/;";
location.reload();
}
});
</script>
2
3
4
5
6
7
8
9
10
11
12
# 🟢 最终你拥有的能力
你现在具备了完整的「灰度失败自动降级」机制:
- ✔ 自动检测入口 JS / chunk 失败
- ✔ 自动降级到稳定版本
- ✔ 自动刷新
- ✔ 不会重复触发(避免无限循环)
- ✔ 可结合 Nginx 灰度策略
- ✔ SPA(Vue)天然支持
最终效果:
灰度版本即使完全坏掉,用户也看不到白屏,而是自动切回稳定版本。
这是企业大规模灰度发布必备的机制。