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和网关的灰度规则全关了。记住:灰度路由只做一层,不要多层叠加。

好了,这一章的内容就到这里。下一章我会讲灰度发布的监控与可观测性——毕竟灰度流量放出去了,你得知道它跑得怎么样,对吧?