1. 核心
核心原理利用了 nginx
的 cookie
分流,根据 cookie
值,修改 root
值进行灰度。
2. cookie 设计
一开始是将 cookie
设置放到 nginx
中,有几个问题:
- 我们的业务是多项目共用一个
nginx
配置,分流是根据整个nginx
的流程来的,单个工程没那么大流量,灰度不均。 - 灰度比例改变的时候,
nginx
的cookie
判断不够灵活,导致真实的灰度比例不符合预期。比如之前是online
,提升灰度比例的时候,要把之前online
的按比例拆分重新进行赋值。多次访问的话,其实就会重复进入判断逻辑。降低灰度比例的时候,又要把之前gray
的按比例拆分,也会重复进入判断逻辑。 - 由于第一点,流量是整个
nginx
的流程,cookie
的拆分逻辑如果很重,容易造成nginx
负载过重。
当前采用的方案是前端注入 cookie
,有以下优点:
- 修改灰度比例,无需重新发布
nginx
不会负载过高- 无需运维和后台配合
缺点如下:
因为前端修改 cookie
的时候,html
已经返回了,所以:
- 第一次没有
cookie
的用户,无法被灰度到,需要reload
。 - 灰度比例变更的时候,比如,一开始没灰度到的人,后面灰度到了,需要
reload
。
3. 细节
灰度发布分类:
- 从0到有,开启一个新的灰度发布,灰度比例大于
0
,小于100
- 修改灰度比例。微信小程序中限制了只能提高,不能降低
- 全量,即灰度
100%
- 撤销灰度,即灰度
0%
非灰度发布分类:
- 发布(全量)
- 回滚
灰度过程中有回滚怎么办?
- 不再管灰度,直接用回滚目标版本覆盖
- 如需再次灰度,再次发布即可
为什么需要统一的配置,以及如何统一?
- 发布、灰度变更(撤销灰度、灰度变全量)、回滚这几个流水线应该用统一的构建镜像流程,同时灰度配置表应该用同一份,防止被覆盖。
- 前端用来更新
cookie
的配置,也应该和灰度发布中的配置用同一份,目前是应用的已建好的七彩石配置同步 CDN 的能力。
什么时候需要产物构建?
- 只有初次发布需要产物构建
- 全量、撤销灰度、回滚等,无需产物构建,只需镜像构建
- 修改灰度比例无需任何构建
灰度配置表设计
- 七彩石配置要区分域名、工程、子工程、分支,也就是分为4层,保证每个链接的灰度互不影响。示例如下:
json
{
"pmd-mobile/match/gp-next": {
"gp": {
"develop": {
"onlineVersion": "",
"grayVersion": "b-9d893d090d49456096ebc983ac6df5c2",
"grayPercent": 59,
"cookieId": "FZyta",
"updateTime": 1740040515741
}
}
}
}
为什么需要 cookieId
,以及怎么用?
- 每个灰度项目,都有一个唯一的
cookieId
,用来在nginx
中分流,并防止项目间互相影响。生成nginx
配置的时候,会遍历七彩石配置,给每个项目生成唯一的cookieName
,然后用在每个location
块中判断。
下面的 map
逻辑不能放在 server
或 location
中,只能放到 http
中。
ts
function getGrayContentBefore() {
const list = Object.keys(globalGrayPublishConfig)
.filter((packageName) => {
const info = globalGrayPublishConfig[packageName];
return !!(info.grayVersion && info.grayPercent && info.cookieId);
});
if (!globalGrayPublishConfig || !list.length) {
return '';
}
let res = '';
list.forEach((packageName) => {
const cookieName = getCookieName(packageName);
const { cookieId } = globalGrayPublishConfig[packageName] || {};
const pageCookieName = `gray_front_end_by_tip_${cookieId}`;
res += `
map $http_cookie $${cookieName} {
default "";
"~*${pageCookieName}=gray" "gray";
"~*${pageCookieName}=online" "online";
}
`;
});
return res;
}
location
块中的判断逻辑:
js
function getGrayContent(fullSubProjectName) {
const tryFilesContent = `try_files $uri $uri/ /${fullSubProjectName}$1/index.html =404;`;
if (!globalGrayPublishConfig || !Object.keys(globalGrayPublishConfig).length) {
return tryFilesContent;
}
const list = getCurrentProjectUseGray(fullSubProjectName);
if (!list.length) {
return tryFilesContent;
}
let result = '';
list.forEach((info) => {
const { packageName } = info;
const cookieName = getCookieName(packageName);
result += `
if ( $${cookieName} = "gray") {
root /usr/share/nginx/html/gray-files;
}
`;
});
result += `
${tryFilesContent}
`;
return result;
}
渐进升级?
- 研发平台域名配置是否开启灰度发布
灰度文件目录设计
- 不应该在业务项目下建灰度子目录,而是整个镜像的灰度文件都在另一个大的目录
- 好处是,方便管理,以及防止用户误入灰度目录,比如
http://xxx/gray-files
即可直接访问灰度文件,甚至覆盖业务的页面,这是不可接受的
nginx
错误日志排查? 错误有两种:
- 第一种,无法启动
nginx
(可理解为编译时错误),会在构建镜像的时候就抛出错误,k8s 根本不会生成新的节点,也就不会影响现有的业务。可在容器管理平台中查看日志。 - 第二种,可以启动,只在单独的
location
块错误(可理解为运行时错误),可进入容器内,tail /var/log/nginx/error.log -f
查看日志。
4. 流程图
普通发布

灰度发布

灰度变全量

回滚
