基于 Nginx 实现一个灰度上线系统 灰度系统可以将流量分成多份一部分走新版本代码一部分走老版本代码从而降低线上问题的风险。正文从这开始软件开发一般不会上来就是最终版本而是会一个版本一个版本的迭代。新版本上线前都会经过测试但就算这样也不能保证上线了不出问题。所以在公司里上线新版本代码一般都是通过灰度系统。灰度系统可以把流量划分成多份一份走新版本代码一份走老版本代码。而且灰度系统支持设置流量的比例比如可以把走新版本代码的流量设置为 5%没啥问题再放到 10%50%最后放到 100% 全量。这样可以把出现问题的影响降到最低。不然一上来就全量万一出了线上问题那就是大事故。而且灰度系统不止这一个用途比如产品不确定某些改动是不是有效的就要做 AB 实验也就是要把流量分成两份一份走 A 版本代码一份走 B 版本代码。那这样的灰度系统是怎么实现的呢其实很多都是用 nginx 实现的。nginx 是一个反向代理的服务用户请求发给它由它转发给具体的应用服务器。这一层也叫做网关层。由它负责转发请求给应用服务器那自然就可以在这里控制流量的分配哪些流量走版本 A哪些流量走版本 B。下面我们实现一下首先我们准备两个版本的代码。这里创建个 nest 项目# 全局安装 npm i -g nestjs/cli # 安装成功后直接使用 nest 命令创建项目 nest new gray_test -p npm把 nest 服务跑起来npm run start浏览器访问下看到 hello world 代表 nest 服务跑起来了。然后改下 AppService修改下端口然后再npm run start浏览器访问下现在我们就有了两个版本的 nest 代码。接下来的问题是如何用 nginx 实现灰度让一部分请求走一个版本的代码一部分请求走另一个版本呢我们先跑一个 nginx 服务。docker desktop 搜索 nginx 镜像这步需要科学上网点击 run设置容器名为 gray1端口映射宿主机的 82 到容器内的 80现在访问 http://localhost:82 就可以看到 nginx 页面了我们要修改下配置文件把它复制出来docker cp gray1:/etc/nginx/conf.d /Users/a0000/Desktop/learn然后编辑下这个 default.conf添加这么一行配置location ^~ /api { rewrite ^/api/(.*)$ /$1 break; proxy_pass http://192.168.1.6:3001; }这行就是加了一个路由把/api/开头的请求转发给http://宿主机IP:3001这个服务。用 rewrite 把 url 重写了比如/api/xxx变成了/xxx。然后我们重新跑个 nginx 容器容器名为 gray2端口映射 83 到容器内的 80。指定数据卷挂载本地的/Users/a0000/Desktop/learn/conf.d目录到容器内的/etc/nginx/conf.d目录。点击 run。然后看下 files 部分可以看到容器内的/etc/nginx/conf.d目录标识为了 mounted。点开看看这就是本地的那个文件。我们在本地改一下试试容器内也同样修改了。在容器内修改这个文件本地同样也会修改。也就是说挂载数据卷之后容器内的这个目录就是本地目录是同一份。然后我们访问下http://localhost:83/api/看看nest 服务访问成功了。现在我们不是直接访问 nest 服务了而是经历了一层 nginx 反向代理或者说网关层。自然我们可以在这一层实现流量控制的功能。前面我们讲负载均衡的时候是这么配的默认会轮询把请求发给 upstream 下的 server。现在需要有多组 upstreamupstream version1.0_server { server 192.168.1.6:3000; } upstream version2.0_server { server 192.168.1.6:3001; } upstream default { server 192.168.1.6:3000; }有版本 1.0 的、版本 2.0 的默认的 server 列表。然后需要根据某个条件来区分转发给哪个服务。我们这里根据 cookie 来区分set $group default; if ($http_cookie ~* version1.0){ set $group version1.0_server; } if ($http_cookie ~* version2.0){ set $group version2.0_server; } location ^~ /api { rewrite ^/api/(.*)$ /$1 break; proxy_pass http://$group; }如果包含 version1.0 的 cookie那就走 version1.0_server 的服务有 version2.0 的 cookie 就走 version2.0_server 的服务否则走默认的。这样就实现了流量的划分也就是灰度的功能。然后我们重新跑下容器这时候你访问http://localhost:83/api/走到的就是默认的版本。然后带上 version2.0 的 cookie走到的就是另一个版本的代码这样我们就实现了灰度的功能。但现在还有一个问题什么时候设置的这个 cookie 呢比如我想实现 80% 的流量走版本 1.020% 的流量走版本 2.0其实公司内部一般都有灰度配置系统可以配置不同的版本的比例然后流量经过这个系统之后就会返回 Set-Cookie 的 header里面按照比例来分别设置不同的 cookie。比如随机数载 0 到 0.2 之间就设置 version2.0 的 cookie否则设置 version1.0 的 cookie。这也叫做流量染色。完整的灰度流程是这样的第一次请求的时候会按照设定的比例随机对流量染色也就是设置不同 cookie。再次访问的时候会根据 cookie 来走到不同版本的代码。这就实现了灰度功能可以用来做 5% 10% 50% 100% 这样逐步上线的灰度上线机制。也可以用来做产品的 AB 实验。公司里都会用这样的灰度系统。总结新版本代码的上线基本都会用灰度系统可以逐步放量的方式来保证上线过程不会出大问题也可以用来做产品 AB 实验。我们可以用 nginx 实现这样的功能。nginx 有反向代理的功能可以转发请求到应用服务器也叫做网关层。我们可以在这一层根据 cookie 里的 version 字段来决定转发请求到哪个服务。在这之前还需要按照比例来给流量染色也就是返回不同的 cookie。不管灰度系统做的有多复杂底层也就是流量染色、根据标记转发流量这两部分我们完全可以自己实现一个。