Kratos本身不处理灰度路由,需注册中心打标(如Nacos中metadata={"version":"v2","env":"canary"})并由网关或客户端Selector按metadata过滤实现;硬编码version判断会破坏可维护性。

Kratos 本身不处理灰度路由逻辑,接口版本灰度必须由注册中心打标 + 网关或客户端 Selector 按 metadata 过滤实现,硬编码 version 判断会破坏可维护性。
Kratos服务注册时必须带 version 标签
灰度的前提是下游能识别“这是 v2 实例”。Kratos 的 ServiceInstance 支持 Metadata 字段,启动时需显式注入版本信息:
- 使用
Nacos或Consul注册时,在Metadata中写入{"version": "v2", "env": "canary"} - 若用
etcd,需确保自定义的 registry 插件支持序列化Metadata(contrib/registry/etcd 默认不透传 map,需自行扩展) - 避免只靠
Name区分版本(如user-service-v2),这会导致服务发现层无法做统一标签路由
客户端调用必须用 Selector 过滤 version 标签
Kratos 的 transport 层(gRPC/HTTP)默认走全量实例列表,要实现灰度需替换默认 Selector:
- 在初始化 client 时传入自定义
transport.WithSelector,例如:micro.Selector(Selector.WithFilter(func(s *registry.Service) bool { return s.Metadata["version"] == "v2" })) - 若使用 Kratos v2 的
client.NewClient,需配合client.WithMiddleware注入 context-aware 的 selector,从ctx.Value("x-version")动态提取目标 version - 注意:gRPC 的
balancer不感知 metadata,所以不能只依赖内置 round_robin;必须在Pick阶段手动过滤registry.Service列表
HTTP 请求头透传 x-version 必须显式实现
上游加了 req.Header.Set("x-version", "v2"),下游收不到——因为 Kratos 的 HTTP transport 不自动转发非标准 header:
- 在网关或入口服务中,用中间件提取
x-version并存入ctx:ctx = context.WithValue(ctx, "x-version", c.GetHeader("x-version")) - 下游调用前,手动将该值附加到新请求 header:
req.Header.Set("x-version", ctx.Value("x-version").(string)) - 不要依赖
http.Transport的ProxyConnectHeader或Clone,它不会处理自定义 header 透传 - gRPC 场景下同理:用
metadata.Pairs("x-version", "v2")构造OutgoingContext,server 端再用metadata.FromIncomingContext提取
VirtualService + DestinationRule 是更稳的替代方案
如果你已接入 Istio,直接放弃在 Kratos 层做 version 分流,改用服务网格能力:
- 在
DestinationRule中定义subsets,按 Pod labelversion: v2分组 - 在
VirtualService中写规则:headers: { "x-version": { exact: "v2" } }或按权重分流 - 这样 Kratos 代码完全无感,所有灰度逻辑收敛在 YAML 和集群配置中,运维可随时调整、回滚
- 缺点是要求所有服务部署在 K8s 且 sidecar 注入正常;若用裸机或 VM,则必须回到注册中心 + Selector 方案
真正容易被忽略的是:灰度标识一旦进入调用链,就必须逐跳透传,而 Kratos 的 transport.Client 默认不携带上层 ctx.Value。每个 HTTP/gRPC 调用点都要显式读取、构造、附加,漏掉任意一环,灰度就断在那一跳。


















