consul之前一直被当成一个服务发现、分布式KV服务、服务健康检查服务等,但此前发布的1.2版本,宣称其实现了Service Mesh方案。而今年被称为Service Mesh的关键之年,就在这里跟大家分享一下自己的研究结果。
macOS下升级consul很简单,简单用brew命令就好
brew update consul
为了方便后面修改consul的配置文件,添加一个 -config-dir
参数
/usr/local/opt/consul/homebrew.mxcl.consul.plist
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"> <dict> <key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <false/> </dict> <key>Label</key> <string>homebrew.mxcl.consul</string> <key>ProgramArguments</key> <array> <string>/usr/local/opt/consul/bin/consul</string> <string>agent</string> <string>-dev</string> <string>-advertise</string> <string>127.0.0.1</string> <string>-config-dir</string> <string>/usr/local/etc/consul.d</string> </array> <key>RunAtLoad</key> <true/> <key>WorkingDirectory</key> <string>/usr/local/var</string> <key>StandardErrorPath</key> <string>/usr/local/var/log/consul.log</string> <key>StandardOutPath</key> <string>/usr/local/var/log/consul.log</string> </dict></plist>
这个配置文件中,我添加了以下两行:
<string>-config-dir</string><string>/usr/local/etc/consul.d</string>
用golang写两个小程序,用以模拟两个微服务。
service1.go
package mainimport ( "net/http" "log" "io")func TestServer(w http.ResponseWriter, req *http.Request) { resp, err := http.Get("http://127.0.0.1:8082/test2") if resp != nil && resp.Body != nil { defer resp.Body.Close() } if err != nil { w.Write([]byte("make request failed\n")) return } io.Copy(w, resp.Body)}func main() { http.HandleFunc("/test1", TestServer) err := http.ListenAndServe(":8081", nil) if err != nil { log.Fatal("ListenAndServe: ", err) }}
service2.go
package mainimport ( "io" "net/http" "log")func TestServer(w http.ResponseWriter, req *http.Request) { io.WriteString(w, "hello, world!\n")}func main() { http.HandleFunc("/test2", TestServer) err := http.ListenAndServe(":8082", nil) if err != nil { log.Fatal("ListenAndServe: ", err) }}
这里模拟微服务 service1
调用 service2
。
在consul的配置文件目录下新建两个json文件,用来配置上述两个服务。
/usr/local/etc/consul.d/01_service1.json
:
{ "service": { "name": "service1", "port": 8081, "connect": { "proxy": { "config": { "upstreams": [{ "destination_name": "service2", "local_bind_port": 38082 }] } } } } }
/usr/local/etc/consul.d/01_service2.json
:
{ "service": { "name": "service2", "port": 8082, "connect": { "proxy": { } } } }
然后执行命令重新加载consul的配置
consul reload
修改service1中引用service2的代码:
......func TestServer(w http.ResponseWriter, req *http.Request) { //resp, err := http.Get("http://127.0.0.1:8082/test2") resp, err := http.Get("http://127.0.0.1:38082/test2") if resp != nil && resp.Body != nil { defer resp.Body.Close() } if err != nil { w.Write([]byte("make request failed\n")) return } io.Copy(w, resp.Body)}......
将service1、service2跑起来,然后用curl命令访问service1
> go run service1.go &> /dev/null> go run service2.go &> /dev/null> curl http://127.0.0.1:8081/test1# 如果出现以下输出,则说明一切正常,Bingo!hello, world!
除了Service Mesh的玩法,consul 1.2还提供了SDK的用法。简单来说就是Go语言开发的微服务按照它的规范修改服务提供方、服务消费方的代码,服务间的调用将会自动使用底层的connect隧道。这个使用方法不太符合service mesh的初衷,做过微服务框架sdk的我不是太喜欢,这里就不详细讲了,可以参考官方给出的文档。
Connect Native原理
Connect Native Go语言项目改造指引
其实consul的文档本身说的比较明白,这里结合consul-ui及代码大概分析一下。
当给consul的服务配置里添加了 "connect":{"proxy":{}}
后,consul将会为每个服务实例创建一个专门的隧道代理,如下图所示:
隧道代理的作用是当以connect模式连入时,会自动建立一条到原服务实例的tcp隧道,后面tcp层以上的应用协议数据流将在这条tcp隧道上传输,具体代码在 https://github.com/hashicorp/consul/blob/master/connect/proxy/listener.go#NewPublicListener
而涉及服务间调用时,在consul服务配置里添加服务 UpstreamListener
声明,服务消费方访问服务时需使用 UpstreamListener
的地址。 UpstreamListener
实际上是一个反向代理,当访问它时,它会以connect模式连接对应的服务实例 ConnectProxy
,具体代码在 https://github.com/hashicorp/consul/blob/master/connect/proxy/listener.go#NewUpstreamListener
结合上述两条规则,整个数据链路就通了。
这里有一个问题,为啥一定要connect模式的隧道代理呢?反向代理服务不能直接连接原来的目标服务地址吗?
看了https://github.com/hashicorp/consul/blob/master/connect/service.go#Dial
大概知道原因了。因为connect模式的隧道代理是使用TLS加密的,这样物理服务器节点之间的网络流量就走TLS安全连接了,再加上intentions机制,服务间的调用安全性上有了很大保障。还有一个原因,如果采用Connect-Native的方式集成consul的service mesh功能,底层连接是TLS,上层就可以很方便地走HTTP/2.0协议了。
优点:
1. 直接使用tcp隧道,因此直接支持各类基于tcp的协议代理,如HTTP/1.1、HTTP/2.0、GRPC。
2. 实现原理简单, https://github.com/hashicorp/consul/blob/master/connect/
https://github.com/hashicorp/consul/tree/master/api/connect*.gohttps://github.com/hashicorp/consul/tree/master/agent/connect/
下的关键文件不超过20个,逻辑很容易就看清了。
3. 直接结合consul做服务注册与服务发现,集成度高。
缺点:
1. 目前的负载均衡算法还很简单,就是随机,见下面:
2. 一些微服务框架的基本功能还不具备,如超时、重试、熔断、流量分配等,可以从 https://github.com/hashicorp/consul/blob/master/connect/proxy/listener.go#handleConn
这里开始扩展。
3. 需要手动修改consul的服务配置;服务消费方要根据consul里的服务配置,修改调用其它服务的地址(这里跟service mesh的初衷有些不符)。
目前来看consul的service mesh方案还比较简单,功能很基本,但具备进一步扩展的空间,可以好好研究学习它的代码。