4. 灰度流量路由技术:Nginx灰度分流、网关层灰度、服务网格灰度
灰度发布的核心,说白了就是「怎么把流量切过去」。
你想想看,新版本上线,总不能一把梭全切过去吧?万一炸了怎么办。所以我们需要一套流量路由机制,让一部分用户先走新版本,另一部分继续走老版本。我这些年做过的项目里,流量路由方案基本分三层:入口层(Nginx)、网关层(Spring Cloud Gateway / Kong)、以及更精细的服务网格层(Istio)。
每一层都有它的适用场景,也有它的坑。今天我就把这三种方案掰开揉碎了讲给你听。
4.1 Nginx灰度分流:最朴素的入口层方案
Nginx做灰度,我个人觉得是最容易上手的方式。它不需要改代码,不需要引入新组件,只要改改配置就行。
我在项目中遇到过一个小团队,他们连网关都没有,就靠Nginx扛着所有流量。灰度发布怎么办?就用Nginx的split_clients模块。
核心原理:根据请求特征(IP、Cookie、Header)计算哈希值,按比例分流。
举个例子,我想让10%的流量走新版本,90%走老版本:
http {
split_clients "${remote_addr}${http_user_agent}" $variant {
10% new_backend;
* old_backend;
}
upstream new_backend {
server 10.0.1.10:8080;
}
upstream old_backend {
server 10.0.1.20:8080;
}
server {
listen 80;
location / {
proxy_pass http://$variant;
}
}
}
这里有个细节要注意:split_clients用的是MurmurHash,同一个客户端IP会始终落在同一个分组里。这就保证了用户的一致性体验——不会这次请求走新版本,下次又切回老版本。
我的建议:如果只是做简单的AB测试或金丝雀发布,Nginx方案完全够用。但如果你需要更复杂的路由规则(比如按用户ID、按地域),那Nginx的配置就会变得很臃肿。
我曾经踩过一个坑:用Nginx做灰度时,忘了考虑CDN缓存。结果CDN把旧版本的页面缓存了,用户怎么刷新都看不到新功能。嗯,这里要注意——灰度流量一定要绕过CDN,或者用Cookie做标识。
4.2 网关层灰度:Spring Cloud Gateway / Kong
当你的微服务多了,Nginx那套就有点不够用了。这时候网关层灰度就派上用场了。
网关层的好处是:它能感知到请求的上下文,比如JWT里的用户信息、Header里的版本号。你可以基于这些信息做更精细的路由。
4.2.1 Spring Cloud Gateway方案
Spring Cloud Gateway支持自定义路由谓词(Predicate)和过滤器(Filter)。我习惯的做法是:在请求Header里加一个X-Version字段,网关根据这个字段决定路由到哪个服务版本。
spring:
cloud:
gateway:
routes:
- id: user-service-gray
uri: lb://user-service-gray
predicates:
- Header=X-Version, gray
filters:
- StripPrefix=1
- id: user-service-stable
uri: lb://user-service-stable
predicates:
- Path=/api/user/**
filters:
- StripPrefix=1
你看,这个配置的意思是:如果请求Header里有X-Version: gray,就走灰度版本;否则走稳定版本。
关键点:灰度版本的谓词要写在前面,因为Spring Cloud Gateway是按顺序匹配的。一旦匹配到灰度路由,就不会再走稳定版本了。
我记得有一次,同事把灰度路由写在了后面,结果灰度流量全跑到了稳定版本上。排查了半天才发现是顺序问题。所以,路由顺序一定要小心。
4.2.2 Kong方案
Kong作为API网关,它的灰度能力更强一些。它支持插件机制,可以用canary插件实现按比例分流。
# 通过Kong Admin API配置灰度
curl -X POST http://localhost:8001/services/user-service/routes \
--data "name=user-route" \
--data "paths[]=/api/user"
# 启用canary插件
curl -X POST http://localhost:8001/services/user-service/plugins \
--data "name=canary" \
--data "config.percentage=10" \
--data "config.upstream_host=user-service-gray"
Kong的canary插件支持按百分比、按Header、按Consumer等多种方式。我个人比较喜欢按Header的方式,因为可以在测试环境里手动指定Header来验证灰度效果。
注意:Kong的canary插件是商业版功能,开源版没有。如果你用的是开源Kong,可以考虑用Lua脚本自己实现一个简单的灰度路由。
4.3 服务网格灰度:Istio
说到最精细的灰度路由,那必须是Istio。它工作在Sidecar层面,对应用完全透明。你不需要改一行代码,就能实现按版本、按权重、按Header的路由。
我刚开始接触Istio时,觉得这东西太复杂了。但用顺手之后,真香。
4.3.1 基于权重的灰度
假设我有一个user-service,v1版本是稳定的,v2版本是灰度。我想让10%的流量走v2:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service
spec:
hosts:
- user-service
http:
- route:
- destination:
host: user-service
subset: v2
weight: 10
- destination:
host: user-service
subset: v1
weight: 90
这个配置一应用,流量就自动按比例分配了。而且Istio用的是一致性哈希,同一个用户会始终落在同一个版本上。
4.3.2 基于Header的灰度
有时候我们想针对特定用户做灰度,比如只让内部测试人员走新版本。这时候可以用Header匹配:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service
spec:
hosts:
- user-service
http:
- match:
- headers:
x-user-type:
exact: internal
route:
- destination:
host: user-service
subset: v2
- route:
- destination:
host: user-service
subset: v1
你看,这个配置的意思是:如果Header里x-user-type的值是internal,就走v2版本;否则走v1版本。
我的经验:Istio的灰度路由配合Kiali可视化工具,效果特别好。你能在界面上实时看到流量是怎么分配的,哪个版本有多少请求,延迟怎么样。排查问题的时候,这个能力太重要了。
4.4 三种方案的对比与选择
说了这么多,到底该选哪个?我整理了一个表格,方便你对比:
| 维度 | Nginx | Spring Cloud Gateway / Kong | Istio |
|---|---|---|---|
| 侵入性 | 低(改配置即可) | 中(需改网关配置) | 极低(对应用透明) |
| 路由粒度 | 粗(IP/Cookie/Header) | 中(Header/Path/参数) | 细(Header/权重/来源/方法) |
| 动态调整 | 需reload | 支持动态刷新 | 实时生效 |
| 学习成本 | 低 | 中 | 高 |
| 适用场景 | 小型项目、入口层灰度 | 微服务架构、API网关层 | 云原生、大规模微服务 |
我个人建议:
- 如果你团队小、服务少,用Nginx就够了。别为了灰度引入一堆复杂组件。
- 如果你已经在用Spring Cloud Gateway或Kong,那就用网关层的灰度方案。别重复造轮子。
- 如果你上了Kubernetes,而且服务数量超过20个,那强烈建议上Istio。它的灰度能力是其他方案比不了的。
避坑指南:我曾经在一个项目里,同时用了Nginx灰度 + 网关灰度 + Istio灰度。结果三层灰度规则叠加,流量乱得一塌糊涂。后来我统一用Istio做灰度,把Nginx和网关的灰度规则全关了。记住:灰度路由只做一层,不要多层叠加。
好了,这一章的内容就到这里。下一章我会讲灰度发布的监控与可观测性——毕竟灰度流量放出去了,你得知道它跑得怎么样,对吧?