Skip to content

1. 核心

核心原理利用了 nginxcookie 分流,根据 cookie 值,修改 root 值进行灰度。

一开始是将 cookie 设置放到 nginx 中,有几个问题:

  1. 我们的业务是多项目共用一个 nginx 配置,分流是根据整个 nginx 的流程来的,单个工程没那么大流量,灰度不均。
  2. 灰度比例改变的时候,nginxcookie 判断不够灵活,导致真实的灰度比例不符合预期。比如之前是 online,提升灰度比例的时候,要把之前 online 的按比例拆分重新进行赋值。多次访问的话,其实就会重复进入判断逻辑。降低灰度比例的时候,又要把之前 gray 的按比例拆分,也会重复进入判断逻辑。
  3. 由于第一点,流量是整个 nginx 的流程,cookie 的拆分逻辑如果很重,容易造成 nginx 负载过重。

当前采用的方案是前端注入 cookie,有以下优点:

  1. 修改灰度比例,无需重新发布
  2. nginx 不会负载过高
  3. 无需运维和后台配合

缺点如下:

因为前端修改 cookie 的时候,html 已经返回了,所以:

  1. 第一次没有 cookie 的用户,无法被灰度到,需要 reload
  2. 灰度比例变更的时候,比如,一开始没灰度到的人,后面灰度到了,需要 reload

3. 细节

灰度发布分类:

  1. 从0到有,开启一个新的灰度发布,灰度比例大于 0,小于 100
  2. 修改灰度比例。微信小程序中限制了只能提高,不能降低
  3. 全量,即灰度 100%
  4. 撤销灰度,即灰度 0%

非灰度发布分类:

  1. 发布(全量)
  2. 回滚

灰度过程中有回滚怎么办?

  1. 不再管灰度,直接用回滚目标版本覆盖
  2. 如需再次灰度,再次发布即可

为什么需要统一的配置,以及如何统一?

  • 发布、灰度变更(撤销灰度、灰度变全量)、回滚这几个流水线应该用统一的构建镜像流程,同时灰度配置表应该用同一份,防止被覆盖。
  • 前端用来更新 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 逻辑不能放在 serverlocation 中,只能放到 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 错误日志排查? 错误有两种:

  1. 第一种,无法启动 nginx(可理解为编译时错误),会在构建镜像的时候就抛出错误,k8s 根本不会生成新的节点,也就不会影响现有的业务。可在容器管理平台中查看日志。
  2. 第二种,可以启动,只在单独的 location 块错误(可理解为运行时错误),可进入容器内,tail /var/log/nginx/error.log -f 查看日志。

4. 流程图

普通发布

灰度发布

灰度变全量

回滚

5. 参考

  1. https://blog.csdn.net/qq_44472722/article/details/126960858
  2. https://blog.csdn.net/Matthewmq/article/details/141865830
  3. https://www.cnblogs.com/hahaha111122222/p/13328778.html