Consul注册地址必须与Gin服务实际暴露的IP和端口一致,注册逻辑须在gin.Run()前执行,/health路径需由Gin暴露并返回200,进程退出时须主动注销服务。

Consul注册地址必须和Gin服务监听地址一致
Consul注册时填的 Address 和 Port,不是Consul自身的地址,而是当前Gin服务实例实际暴露的 IP 和端口。如果填错(比如写成 127.0.0.1 但服务跑在 Docker 容器里),其他服务就调不通。
常见错误现象:connection refused 或服务列表里显示健康状态为 critical;查 Consul UI 的 Service Detail 会发现注册的地址根本无法被网络访问。
- 本地开发:用
localhost或本机局域网 IP(如192.168.x.x),避免全用127.0.0.1 - Docker 部署:必须填容器可被外部访问的宿主机 IP + 映射端口,不能填
0.0.0.0:8080或容器内网地址 - 云环境:优先用云平台分配的私有 IP,别用公网 IP 注册(除非明确需要跨 VPC 调用)
注册逻辑不能放在 gin.Run() 之后
Gin 启动是阻塞式调用,r.Run() 之后的代码不会执行。如果你把 Consul 注册逻辑写在它后面,服务压根就不会注册成功。
正确做法是:先初始化 Consul 客户端、构造服务注册体、调用 PUT /v1/agent/service/register,再启动 Gin 服务。
示例关键顺序:
client, _ := consulapi.NewClient(&consulapi.Config{Address: "127.0.0.1:8500"})
reg := &consulapi.AgentServiceRegistration{
ID: "user-web",
Name: "user-web",
Address: "192.168.1.100", // 实际可访问地址
Port: 8080,
Check: &consulapi.AgentServiceCheck{
HTTP: "http://192.168.1.100:8080/health",
Timeout: "5s",
Interval: "10s",
},
}
client.Agent().ServiceRegister(reg)
r.Run(":8080") // 必须放最后
健康检查路径必须由Gin自己暴露
Consul 的健康检查不是“ping 端口”,而是真实发起 HTTP 请求。如果 Gin 路由里没定义 /health(或你注册里写的那个路径),Consul 就会持续标记服务为 critical。
这个路径不需要复杂逻辑,但必须返回 200,且响应体可为空或简单 JSON:
r.GET("/health", func(c *gin.Context) {
c.Status(200)
})
- 别用重定向(302)、别返回 4xx/5xx
- 不要加鉴权中间件——Consul 检查不带 token,会被拦住
- 如果用了反向代理(如 Nginx),确保健康检查请求能透传到 Gin 实例,而不是被代理层拦截或缓存
服务注销容易被忽略,导致僵尸实例堆积
进程退出时如果不主动调用 client.Agent().ServiceDeregister("service-id"),Consul 不会自动清理。下次启动同名服务时可能因 ID 冲突失败,或旧实例长期挂着占用列表。
Go 中推荐用 os.Interrupt + os.Kill 捕获信号,在退出前 deregister:
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
<-- 启动 Gin -->
<-- 启动 goroutine 监听 quit -->
go func() {
<-quit
client.Agent().ServiceDeregister("user-web")
os.Exit(0)
}()
更稳妥的做法是配合 context.WithTimeout 控制 deregister 超时,防止退出卡死。这点在 Kubernetes Pod 优雅终止或 CI/CD 自动部署中尤为关键——没人手动去 Consul UI 点删除。


















