官网介绍地址 视频介绍的挺不错的,不过是英文的
Consul is a service mesh solution providing a full featured control plane with service discovery, configuration, and segmentation functionality. Each of these features can be used individually as needed, or they can be used together to build a full service mesh. Consul requires a data plane and supports both a proxy and native integration model. Consul ships with a simple built-in proxy so that everything works out of the box, but also supports 3rd party proxy integrations such as Envoy.
大致的意思是,Consul提供服务网格(可自行搜索一下含义)解决方案,功能齐全,支持控制平面,服务发现、配置和分段功能。这些功能中的每个特性都可以根据需要单独使用,也可以一起使用来构建一个完整的服务网格。Consul需要一个数据平面,并支持代理和原生集成模型。Consul提供了一个简单的内置代理,因此一切都可以开箱即用,但也支持第三方代理集成,如Envoy。
学习参考的资料
官网
中文文档
前面介绍了consul的功能很强大,提供了服务网格的整体解决方案。本文主要使用 consul来演示其中服务发现这个单独的特性。本案例采用的是consul单机进行测试。启动服务consul服务端,需要占用6个端口,因此只部署了单实例,内存占用 17m, 占用端口8300,8301,8302,8500,8502,8600
本文的实操环境是基于虚拟机完成,虚拟机通过静态ip与本机相连,虚拟机局域网ip地址固定为 10.248.174.155。consul的版本为1.11.1
# 虚拟机操作系统
> uname -a
> Linux n248-174-155 4.14.81.bm.15-amd64 #1 SMP Debian 4.14.81.bm.15 Sun Sep 8 05:02:31 UTC 2019 x86_64 GNU/Linux
# 下载
wget https://releases.hashicorp.com/consul/1.11.1/consul_1.11.1_linux_amd64.zip
# 解压
unzip consul_1.11.1_linux_amd64.zip
解压后只有一个可执行文件,将这个可执行文件加入到环境变量PATH中即可,记得使用source 命令让PATH的变量重新加载
# consul version 查看版本号
root@n248-174-155:~/file$ consul version
Consul v1.11.1
Revision 2c56447e
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)
另外由于启动时,指定了参数开始 web-ui 所以可以在web端可视化的展示服务的状态,将ip替换成你的ip即可
http://10.248.174.155:8500/
一般来说,官方提供的各种语言相关的sdk都原生命令有对应的联系,consul是用go语言编写的,原生api对go的支持非常好。
由于已经将consul 加入到PATH变量中了,所以接下来的操作都和在哪个目录完全没有关系了。
# 后台启动方式,指定了日志文件路径/home/root/consul/consul.log
nohup consul agent -dev -client 0.0.0.0 -ui > /home/root/consul/consul.log 2>&1 &
查看进程
root@n248-174-155:~/file$ netstat -ntpl|grep consul
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 127.0.0.1:8300 0.0.0.0:* LISTEN 793138/consul
tcp 0 0 127.0.0.1:8301 0.0.0.0:* LISTEN 793138/consul
tcp 0 0 127.0.0.1:8302 0.0.0.0:* LISTEN 793138/consul
tcp6 0 0 :::8500 :::* LISTEN 793138/consul
tcp6 0 0 :::8502 :::* LISTEN 793138/consul
tcp6 0 0 :::8600 :::* LISTEN 793138/consul
consul kv put services/svr1/001 '{"Addr":"192.168.4.5","Port":3358}'
consul kv put services/svr1/002 '{"Addr":"192.164.45.22","Port":3458}'
consul kv put services/svr2/001 '{"Addr":"192.138.44.55","Port":3456}'
consul kv put -flags=25 service/svr1/003 '{"Addr":"192.174.35.67","Port":8890}'
consul kv get service/svr1/001
consul kv get -detailed service/svr1/001
consul kv get -recurse
consul kv get -recurse service/svr1/
consul kv get -keys service/svr1/
consul kv delete service/svr1/001
consul kv delete -recurse service/svr1/
consul watch -type=keyprefix -prefix=/services/svr1/001
# 指定服务ip地址、端口号、服务唯一id、服务名、服务的多个tag
consul services register -address=124.3.2.4 -port=4458 -id=1234001 -name=mysvr-rpc -tag=v.1.1 -tag=user
heath.json 文件内容
{
"ID": "service:1234001",
"Name": "自定义结点健康检查",
"Notes": "回调http接口查看",
"DeregisterCriticalServiceAfter": "30s",
"HTTP": "http://ip:port/consul/health/?id=555",
"Method": "GET",
"Interval": "10s",
"Timeout": "5s",
"ServiceID":"1234001"
}
# heath.json 定义的是只针对某个服务结点的健康检查,直接从文件中读取具体的数据
curl --request PUT --data @heath.json http://127.0.0.1:8500/v1/agent/check/register
# 根据服务的唯一id卸载服务
consul services deregister -id=1234001
curl http://127.0.0.1:8500/v1/agent/checks
# 只返回健康没问题的
curl http://127.0.0.1:8500/v1/health/service/mysvr-rpc?passing=1
consul自带服务发现与注册功能,下面将做一个服务注册与发现的例子,仅供参考。
注意如果你指定了http的健康检查方式,一定要在对应的地址启动http服务,否则健康检查会失败,直接摘除服务。或者可以使用grpc的方式进行代替。
package consul
import (
"context"
"fmt"
"google.golang.org/grpc/health/grpc_health_v1"
"log"
"strings"
"github.com/hashicorp/consul/api"
uuid "github.com/satori/go.uuid"
)
const (
consulIp = "10.248.174.155"
consulPort = "8500"
)
func RandomStr(len int) string {
nUid := uuid.NewV4().String()
str := strings.Replace(nUid, "-", "", -1)
if len < 0 || len >= 32 {
return str
}
return str[:len]
}
// HttpReg 服务注册时候提供一个健康检查地址,支持http,grpc
func HttpReg(svrName string, ipAddr string, port int) string {
// 创建Consul客户端连接
client, err := api.NewClient(&api.Config{
Address: fmt.Sprintf("http://%v:%v", consulIp, consulPort),
})
if err != nil {
log.Fatalf("client 创建失败,退出:%v\n", err)
}
svrID := RandomStr(32)
// 设置Consul对服务健康检查的参数
check := api.AgentServiceCheck{
HTTP: fmt.Sprintf("http://%v:%v/consul/health/?id=%v", "127.0.0.1", 80, svrID), // 健康检查地址,自定义ip和端口
Interval: "3s", // 健康检查频率
Timeout: "2s", // 健康检查超时
Notes: "Consul 代码健康检查",
DeregisterCriticalServiceAfter: "5s", // 健康检查失败30s后 consul自动将注册服务删除
Name: "代码自定义检查svr1",
//GRPC: fmt.Sprintf("%v:%v/%v", svcHost, svcPort, "svrName"),
}
//设置微服务向Consul注册信息
reg := &api.AgentServiceRegistration{
ID: svrID, // 服务节点的ID
Name: svrName, // 服务名称
Address: ipAddr, // 服务IP
Port: port, // 服务端口
Tags: []string{"v1.1", "backup"}, // 标签,可在服务发现时筛选服务,类似v1.1
Check: &check, // 健康检查
}
// 执行注册
if err := client.Agent().ServiceRegister(reg); err != nil {
log.Fatalln(err)
}
return svrID
}
//HttpUnReg 服务卸载
func HttpUnReg(svrID string) {
// 创建Consul客户端连接
client, err := api.NewClient(&api.Config{
Address: fmt.Sprintf("http://%v:%v", consulIp, consulPort),
})
if err != nil {
log.Fatalf("client 创建失败,退出:%v\n", err)
}
// 执行服务卸载
if err := client.Agent().ServiceDeregister(svrID); err != nil {
log.Fatalln(err)
}
}
//========如果使用grpc接口实现健康检查,则需要实现HealthServer 接口,服务启动时候注册这个pb==========
// HealthImpl 健康检查实现
type HealthImpl struct{}
// Check 实现健康检查接口,这里直接返回健康状态
func (h *HealthImpl) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) {
return &grpc_health_v1.HealthCheckResponse{
Status: grpc_health_v1.HealthCheckResponse_SERVING,
}, nil
}
// Watch 让HealthImpl实现RegisterHealthServer内部的interface接口
func (h *HealthImpl) Watch(req *grpc_health_v1.HealthCheckRequest, w grpc_health_v1.Health_WatchServer) error {
return nil
}
package consul
import (
"fmt"
"github.com/hashicorp/consul/api"
"log"
"testing"
"time"
)
func TestClient(t *testing.T) {
// Get a new client
client, err := api.NewClient(&api.Config{
Address: "http://10.248.174.155:8500",
})
if err != nil {
log.Fatalln(err)
}
// Get a handle to the KV API
kv := client.KV()
// PUT a new KV pair
p := &api.KVPair{Key: "my-svr", Value: []byte("1000"), Flags: 32}
_, err = kv.Put(p, nil)
if err != nil {
log.Fatalln(err)
}
// Lookup the pair
pair, _, err := kv.Get("my-svr", nil)
if err != nil {
log.Fatalln(err)
}
fmt.Printf("KV: %v %s\n", pair.Key, pair.Value)
}
func TestHttpReg(t *testing.T) {
svrID := HttpReg("mysvr-rpc", "127.0.0.1", 3456)
log.Printf("服务注册成功:%v\n", svrID)
for true {
// 制定一个定时器,模拟20s后摘除服务
t := time.NewTicker(20 * time.Second)
select {
case tt := <-t.C:
HttpUnReg(svrID)
log.Printf("服务卸载,tt:%v\n", tt)
return
}
}
}
package consul
import (
"fmt"
"github.com/hashicorp/consul/api"
"log"
)
// GetNode 没有watch的api 无法感知结点的变更,如果做本地缓存则需要启动协程去定时轮询,全量更新到本地缓存
func GetNode(svrName string) {
// 创建Consul客户端连接
client, err := api.NewClient(&api.Config{
Address: fmt.Sprintf("http://%v:%v", consulIp, consulPort),
})
if err != nil {
log.Fatalf("client 创建失败,退出:%v\n", err)
}
// 根据服务名和tag 是否健康检查通过 以及其它option 筛选service实例
service, meta, serr := client.Health().Service(svrName, "", true, nil)
log.Printf("结束svr%+v,meta:%+v, err:%v\n", service, meta, serr)
for _, s := range service {
fmt.Printf("服务:%+v\n", s.Service)
}
}
package consul
import "testing"
func TestGetNode(t *testing.T) {
GetNode("mysvr-rpc")
}
本文没有深入介绍consul的一些底层原理性东西,主要介绍了consul的安装及功能的使用,介绍了一些常用的api命令,最后用go语言简单实现了一下服务注册与发现的过程。