Consul客户端初始化时Address必须显式指定,否则默认127.0.0.1:8500会导致连接拒绝;服务ID需唯一(如拼接主机名+PID),Name固定用于发现;健康检查路径须为Gin已注册且返回200的外网可达地址;注销必须在捕获信号后立即执行,不可defer。

Consul 客户端初始化时 Address 必须显式指定
默认 api.DefaultConfig() 用的是 "127.0.0.1:8500",但实际部署中 Consul 很可能不在本地——比如 Docker 容器里、远程 VM 或 Kubernetes 集群。不改这个地址,client.Agent().ServiceRegister() 会直接失败,错误类似 "Get \"http://127.0.0.1:8500/v1/agent/service/register\": dial tcp 127.0.0.1:8500: connect: connection refused"。
正确做法是读取环境变量或配置文件后拼接:
-
config.Address = os.Getenv("CONSUL_ADDR")(推荐,便于容器化部署) - 或硬编码为
"192.168.1.31:8500"(仅限开发调试) - 注意:端口必须是 HTTP API 端口(默认 8500),不是 DNS 端口(8600)
服务注册的 ID 和 Name 不能重复,否则新实例覆盖旧实例
Consul 用 ID 唯一标识一个服务实例。如果多个进程启动时都用相同 ID(比如写死 "user-srv"),后启动的会把前一个踢下线,导致服务“闪断”。
常见安全写法:
-
ID拼入主机名 + 进程 PID:fmt.Sprintf("user-srv-%s-%d", hostname, os.Getpid()) -
Name保持固定(如"user-srv"),用于服务发现时的逻辑名 -
Tags可加环境标识:[]string{"env=prod", "version=v1.2.0"},方便过滤
健康检查路径必须是 Gin 路由且返回 200,否则 Consul 标记为不健康
Consul 的 HTTP 健康检查会 GET 你填的 HTTP 字段路径,比如 "http://192.168.1.31:8080/health"。如果 Gin 没配这个路由,或返回非 200(比如 404、500、重定向),Consul 就会把服务状态标为 critical 并从服务列表剔除。
确保:
- Gin 中已定义该路由:
r.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"status": "ok"}) }) - 路径中的 IP 和端口必须是服务对外可访问的真实地址(不是
localhost) - 不要用
127.0.0.1,Consul agent 在另一台机器上时根本连不通
注销服务时 deregister 必须在信号捕获后立即执行,不能 defer
很多人写 defer deregisterFromConsul(),但 defer 是函数返回时才执行,而 os.Signal 接收后主 goroutine 已退出,defer 根本没机会运行。
正确顺序:
- 用
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)监听 阻塞等待信号- 信号到达后,**立刻调用**
deregisterFromConsul(),再log.Println("exited") - 别依赖任何异步逻辑——注销失败,服务就一直挂在 Consul 里
最易被忽略的是健康检查路径的可达性:它必须是 Consul agent 所在机器能直接 curl 通的地址,而不是开发机 localhost。跨网络部署时,这个细节几乎每次都会卡住第一次上线。











