kratos.New() panic 的根本原因是传入 nil registry,consul.New() 失败时返回 nil 和 error,必须同时检查二者,不可忽略 reg;推荐在 Wire provider 中完成构造与校验。

consul.New() 返回 nil 却没检查,直接 panic
Kratos 启动时 kratos.New() 报 invalid memory address,大概率是传了 nil 的 registry.Registry。Consul 客户端初始化失败(比如地址不通、ACL 拒绝、TLS 配置错)时,consul.New() 会返回 nil, error,但很多人只判 error != nil 就跳过,没意识到 reg 已是 nil。
- 必须显式检查两个返回值:
reg, err := consul.New(...); if err != nil { log.Fatal(err) },不能用_忽略 - 推荐在 Wire provider 中完成构造和校验,避免逻辑泄漏到
main() - Consul 地址写
"127.0.0.1:8500"时,确认本地 Consul 确实监听该地址+端口(netstat -tuln | grep 8500),Docker 环境注意 host.docker.internal 或网络模式
resolver.Register() 被覆盖导致服务发现静默失效
resolver.Register() 是全局单例操作,重复调用同 scheme(如 "discovery")会静默覆盖前一个注册器。常见于多个包的 init() 函数里各自注册,或单元测试中反复 new + register。
- 只应在项目启动入口(如
cmd/xxx/main.go)调用一次resolver.Register(dis),dis是从consul.New()得到的registry.Discovery实例 - 检查是否误在
api/或data/包的init()里调用了它——这类代码会被自动执行,且无提示 - 运行时可加日志验证:
resolver.Get("discovery") != nil,若为 nil 说明没注册或被覆盖
gRPC Dial endpoint 格式错误,跳过服务发现
用 grpc.DialContext 连接其他服务时,如果 endpoint 不是 discovery:///service-name 格式,gRPC 会直连该字符串解析出的地址,完全绕过服务发现逻辑。
- 必须写成
"discovery:///user-service":协议头固定discovery://,三斜杠///,后面紧跟注册时ServiceInstance.Name的值(严格区分大小写) - 不能写
"consul://..."、"127.0.0.1:9000"、"localhost:9000",这些都会 bypass resolver - 确认注册端用的 name 和发现端写的 name 完全一致,例如注册时是
name: "user-service",就不能在 dial 时写"UserService"或"userservice" - 调用
grpc.DialContext时必须传grpc.WithDiscovery(r),其中r是同一个registry.Discovery实例
健康检查未就绪,注册中心标记为 critical
Consul 页面看到服务状态是 critical,通常不是注册失败,而是健康检查探活失败。Kratos 默认不自动暴露 /health,需手动配置。
- 实现
health.Checker接口,把 DB、Redis、下游 gRPC 客户端等 readiness 状态聚合进来 - 将该 checker 注册到 HTTP server 的
/health路由,并确保 K8s liveness probe 或 Istio health check 指向该路径 - Consul 客户端配置里要显式传入
Check字段,例如&consul.Config{Address: "...", Check: &consul.Check{HTTP: "http://localhost:8000/health", Interval: "5s"}} - 检查
/health接口返回码是否为 200,内容是否含{"status":"UP"}类结构;返回非 200 或超时都会触发critical
实际排查时,先看日志里有没有 consul: connect to ... failed 或 resolver: scheme discovery not registered 这类明确报错;没有的话,再逐层验证注册、resolver、dial、health 四个环节的字符串和实例一致性——最常出问题的是大小写、斜杠数量、init() 重复注册这三处。











