本文中,我们将介绍如何使用consul
进行服务发现。
首先,我们先运行启动一个consul服务:
consul agent -server -bootstrap-expect=1 -data-dir=/tmp/consul -node=consulServer -ui -config-dir=./consul.d/ -client 0.0.0.0
启动consul后,就需要进行服务注册了。
服务注册
consul的服务注册可用通过一个json
配置文件进行配置。我们只需要指定存放的目录,consul启动之后,会根据指定的目录找到json
的配置信息。
这里有官网给出的配置示例:consul服务发现的json示例
在开头,我们指定了-config-dir=./consul.d/
作为服务注册的目录,我们需要创建并进入该目录下。
mkdir consul.d && cd consul.d
参考官网示例,本文给出一个例子:
{
"service": {
"id": "redis",
"address": "", // 默认是本机IP地址
"name": "redis", // 服务名称
"tags": ["primary"], // 标签
"port": 8000 // 端口
}
}
注意,配置好文件后,需要重新启动consul
,或者重新加载:
root@cchui-virtual-machine:~# consul reload
Configuration reload triggered
之后,进入UI
界面后
到这里,就注册好了一个新的服务。
此外,参考官网我们也可以使用API
的方式查看注册的服务信息:
curl localhost:8500/v1/catalog/service/服务名称
----------------------------------------------------------------------------------------
root@cchui-virtual-machine:~# curl localhost:8500/v1/catalog/service/redis
[{"ID":"043eb35e-0a1d-d8fe-b040-4c13dfba2b51","Node":"consulServer","Address":"127.0.0.1","Datacenter":"dc1","TaggedAddresses":{"lan":"127.0.0.1","lan_ipv4":"127.0.0.1","wan":"127.0.0.1","wan_ipv4":"127.0.0.1"},"NodeMeta":{"consul-network-segment":""},"ServiceKind":"","ServiceID":"redis","ServiceName":"redis","ServiceTags":["primary"],"ServiceAddress":"","ServiceWeights":{"Passing":1,"Warning":1},"ServiceMeta":{},"ServicePort":8000,"ServiceEnableTagOverride":false,"ServiceProxy":{"MeshGateway":{},"Expose":{}},"ServiceConnect":{},"CreateIndex":128,"ModifyIndex":128}]
健康检查
健康检查是检查服务是否存活,具体可用参看官网:Consul的健康检查–Check Definitions
consul给出了script
、HTTP
、 TCP
、gRPC
和Docker
等的健康检查定义示例。
需要在配置信息中添加checks
关键字,进行配置,如:
示例:
{
"service": {
"name": "web",
"tags": ["rails"],
"port": 80,
"checks": [{
"id": "api", // 健康检查项的id,唯一
"name": "HTTP API on port 5000", // 检查项的名字
"http": "http://localhost:5000/health", // 定期访问的Url,通过这个url请求结果确定服务是否正常
"tls_skip_verify": false, // 关闭tls验证
"method": "POST", // 设置http请求方式,默认是GET
"header": { // 可以自定义请求头,可以不配置
"Content-Type": ["application/json"]
},
"interval": "10s", // 定期检查的时间间隔,这里是10秒
"timeout": "1s" // 请求超时时间,1秒
}]
}
}
可以看到,服务是不健康的,因为还没有启动这个服务:
接下来,我们简单开启一个web服务:
package main
import (
"log"
"net/http"
)
func main() {
http.HandleFunc("/health", func(writer http.ResponseWriter, request *http.Request) {
if _, err := writer.Write([]byte("hello world")); err != nil {
log.Panicf("/health have err : %s", err)
}
})
if err := http.ListenAndServe("127.0.0.1:5000", nil); err != nil {
log.Panicf("web err %s", err)
}
}
之后,再检查,就发现就可以了:
编程实践
这里,我们给出示例,看一个简单的示例:和gRPC
结合,实现一个服务注册、发现的过程。
实现逻辑
之前,我们写gRPC
,是服务端和客户端直接相连的,客户端直接把gRPC
服务端的IP
地址直接写死,进行调用。
现在,我们需要解耦,客户端通过与consul连接,发现gRPC
的服务。
如下图所示:
服务端
步骤:
- 初始化consul配置
- 创建consul对象
- 告诉consul,注册服务的信息
- 注册服务到consul
- 编写gRPC的代码
服务端代码:
func main() {
// 初始化consul配置
consulConfig := api.DefaultConfig()
// consul的IP地址
consulConfig.Address = "192.168.144.128:8500"
// 创建consul对象
consulServer, err := api.NewClient(consulConfig)
if err != nil {
log.Printf("api.NewClient error : %s", err)
}
// 告诉consul,注册服务的信息
registrationInfo := api.AgentServiceRegistration{
ID: "gRPC.id",
Name: "my gRPC server",
Address: "192.168.1.40", // 本机地址
Port: 50051,
Check: &api.AgentServiceCheck{
CheckID: "consul gRPC id",
TCP: "192.168.1.40:50051", // gRPC服务的调用地址
Interval: "2s",
Timeout: "5s",
},
}
// 注册服务到consul
if err := consulServer.Agent().ServiceRegister(®istrationInfo); err != nil {
log.Printf("consul register error : %s", err)
}
......
// 初始化一个gRPC的结构体对象
s := grpc.NewServer(grpc.Creds(creds), grpc.UnaryInterceptor(myLogFilter))
......
}
查看UI界面:
发现已经注册上了服务。
客户端
步骤:
- 初始化consul配置
- 创建consul对象
- 通过consul,进行服务发现
- 获得一个IP地址,这里可用使用负载均衡
- 编写gRPC的代码
客户端代码:
package main
......
var (
address = "localhost:50051"
defaultName = "world"
)
func main() {
// 初始化consul配置
consulConfig := api.DefaultConfig()
consulConfig.Address = "192.168.144.128:8500"
// 创建consul对象
consulClient, err := api.NewClient(consulConfig)
if err != nil {
log.Printf("api.NewClient error : %s", err)
}
// 服务发现
servers, _, err := consulClient.Health().Service("my gRPC server", "", true, nil)
if err != nil {
log.Printf("get Health server fail : %s", err)
}
// 拼接IP地址
target := servers[0].Service.Address + ":" + strconv.Itoa(servers[0].Service.Port)
address = target
......
// 连接 gRPC 服务器
conn, err := grpc.Dial(address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&auth))
......
}
说明:
在上面的代码中,我们使用到了这个函数:
// 从consul服务上获取健康的服务
func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {}
service : 服务名称,即注册服务时的服务名
tag : 标签
passingOnly : 是否通过健康检查
q :查询参数,通常传nil
[]*ServiceEntry :存储服务的切片,因为注册的服务可能是一个集群(服务相同,IP不同,可用负载均衡选择一个)
*QueryMeta :额为查询返回的值
之后,
启动,连接成功。
总结
本文,我们先说明了consul如何进行服务注册,之后,讲述了如何进行健康检查。最后,使用一个简单的示例,将gRPC
和consul
进行了结合,实现了服务发现和健康检查的示例。