Kratos本身不处理灰度路由,需依赖服务发现层打标(如version/canary)、Selector插件按metadata过滤、以及请求头/gRPC元数据逐跳透传,三者缺一不可。

Kratos 本身不处理灰度路由,它把这件事交给了服务发现层 + 网关层。你不能在 Kratos 的 http.Handler 或 grpc.Server 里硬写“如果 header 是 x-canary 就调 v2”,那会污染业务、无法动态更新、也违背了微服务治理分层原则。
registry 实例打标是灰度路由的起点
Kratos 的服务注册默认只带基础信息(地址、端口、健康状态),灰度能力依赖你主动往 registry.Service.Metadata 里塞标签:
- 常见标签:
"version": "v1.2"、"canary": "true"、"weight": "20" - 启动时注册必须显式传入:
registry.Register(®istry.Service{... Metadata: map[string]string{"version": "v1.2", "env": "staging"}}) - 错误做法:只靠 Pod 标签或 Deployment 名称隐式推断版本——Kratos 客户端和服务端都不读这些
如果你用的是 Nacos、Consul 或自建 etcd 注册中心,确保它们支持 metadata 存储和查询;否则 selector 过滤会失效。
Selector 插件必须启用并配置过滤逻辑
Kratos 的负载均衡由 Selector 控制,默认是随机或轮询,不感知 metadata。要实现灰度,得自己写 filter 并注入:
- 在客户端初始化时指定 selector:
client := http.NewClient(client.WithSelector(selector)) -
selector需实现Select方法,从registry.Service列表中按 metadata 筛选:- 比如上游请求带
x-canary: true,就只返回Metadata["canary"] == "true"的实例 - 若没匹配到,应 fallback 到全量列表,避免请求失败
- 比如上游请求带
- 不要用全局静态 selector —— 不同接口可能需要不同灰度策略(比如 /api/user 走 version 标签,/api/order 走 env 标签)
注意:kratos/pkg/rpc/transport/http 和 grpc transport 使用的是同一套 selector,但 gRPC client 初始化时需显式传入,HTTP client 默认不加载,容易漏配。
请求头和 gRPC 元数据必须手动透传
Kratos 的中间件默认不转发自定义 header,gRPC 的 metadata 也不自动跨跳传递:
- HTTP 场景下,上游加了
req.Header.Set("x-canary", "true"),下游收不到,因为http.RoundTripper不透传非标准头 - 正确做法是在调用前手动拷贝:
for k, vs := range upstreamReq.Header { if strings.HasPrefix(strings.ToLower(k), "x-") { for _, v := range vs { req.Header.Add(k, v) } } } - gRPC 场景下,server 收到
metadata.FromIncomingContext(ctx)后,若要继续往下传(A→B→C),必须在 B 的 handler 中重新构造 outgoing metadata 并附加到新 ctx:md, _ := metadata.FromIncomingContext(ctx) newCtx := metadata.NewOutgoingContext(context.Background(), md)
漏掉这一步,灰度链路会在第二跳就断裂——这是线上排查最常卡住的点。
Kratos 的灰度不是开箱即用的功能,它的设计哲学是「把决策权交给基础设施」。真正关键的不是框架写了多少代码,而是你是否在注册中心打了正确的标、selector 是否按需过滤、header/metadata 是否每一跳都稳稳接住。这三个环节只要断一环,灰度就变成玄学。



















