研究consul的service mesh功能

作者:Jeremy的点滴技术

来源:https://jeremy-xu.oschina.io/2018/07/%E7%A0%94%E7%A9%B6consul%E7%9A%84service-mesh%E5%8A%9F%E8%83%BD/


之前一直是将consul当成一个服务发现、分布式KV服务、服务健康检查服务等,不过前几天consul发布了1.2版本,宣称其实现了Service Mesh方案,最近在做Service Mesh相关的工作,正好有一点时间,就花时间研究一下。

试用consul的service mesh

升级consul至1.2版本

macOS下升级consul很简单,简单用brew命令就好

 
  
  1. brew update consul


为了方便后面修改consul的配置文件,添加一个 -config-dir参数

/usr/local/opt/consul/homebrew.mxcl.consul.plist

 
  
  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">

  3. <plist version="1.0">

  4.  <dict>

  5.    <key>KeepAlive</key>

  6.    <dict>

  7.      <key>SuccessfulExit</key>

  8.      <false/>

  9.    </dict>

  10.    <key>Label</key>

  11.    <string>homebrew.mxcl.consul</string>

  12.    <key>ProgramArguments</key>

  13.    <array>

  14.      <string>/usr/local/opt/consul/bin/consul</string>

  15.      <string>agent</string>

  16.      <string>-dev</string>

  17.      <string>-advertise</string>

  18.      <string>127.0.0.1</string>

  19.      <string>-config-dir</string>

  20.      <string>/usr/local/etc/consul.d</string>

  21.    </array>

  22.    <key>RunAtLoad</key>

  23.    <true/>

  24.    <key>WorkingDirectory</key>

  25.    <string>/usr/local/var</string>

  26.    <key>StandardErrorPath</key>

  27.    <string>/usr/local/var/log/consul.log</string>

  28.    <key>StandardOutPath</key>

  29.    <string>/usr/local/var/log/consul.log</string>

  30.  </dict>

  31. </plist>


这个配置文件中,我添加了以下两行:

 
  
  1. <string>-config-dir</string>

  2. <string>/usr/local/etc/consul.d</string>


写两个模拟的微服务

用golang写两个小程序,用以模拟两个微服务。

service1.go

 
  
  1. package main

  2. import (

  3.    "net/http"

  4.    "log"

  5.    "io"

  6. )

  7. func TestServer(w http.ResponseWriter, req *http.Request) {

  8.    resp, err := http.Get("http://127.0.0.1:8082/test2")

  9.    if resp != nil && resp.Body != nil {

  10.        defer resp.Body.Close()

  11.    }

  12.    if err != nil {

  13.        w.Write([]byte("make request failed\n"))

  14.        return

  15.    }

  16.    io.Copy(w, resp.Body)

  17. }

  18. func main() {

  19.    http.HandleFunc("/test1", TestServer)

  20.    err := http.ListenAndServe(":8081", nil)

  21.    if err != nil {

  22.        log.Fatal("ListenAndServe: ", err)

  23.    }

  24. }


service2.go

 
  
  1. package main

  2. import (

  3.    "io"

  4.    "net/http"

  5.    "log"

  6. )

  7. func TestServer(w http.ResponseWriter, req *http.Request) {

  8.    io.WriteString(w, "hello, world!\n")

  9. }

  10. func main() {

  11.    http.HandleFunc("/test2", TestServer)

  12.    err := http.ListenAndServe(":8082", nil)

  13.    if err != nil {

  14.        log.Fatal("ListenAndServe: ", err)

  15.    }

  16. }


这里模拟微服务 service1调用 service2


在consul里配置两个服务

在consul的配置文件目录下新建两个json文件,用来配置上述两个服务。

/usr/local/etc/consul.d/01_service1.json:

 
  
  1. {

  2.    "service": {

  3.      "name": "service1",

  4.      "port": 8081,

  5.      "connect": {

  6.        "proxy": {

  7.          "config": {

  8.            "upstreams": [{

  9.               "destination_name": "service2",

  10.               "local_bind_port": 38082

  11.            }]

  12.          }

  13.        }

  14.      }

  15.    }

  16.  }


/usr/local/etc/consul.d/01_service2.json:

 
  
  1. {

  2.    "service": {

  3.      "name": "service2",

  4.      "port": 8082,

  5.      "connect": {

  6.        "proxy": {

  7.        }

  8.      }

  9.    }

  10.  }


然后执行命令重新加载consul的配置

 
  
  1. consul reload


修改service1中引用service2的代码:

 
  
  1. ......

  2. func TestServer(w http.ResponseWriter, req *http.Request) {

  3.    //resp, err := http.Get("http://127.0.0.1:8082/test2")

  4.    resp, err := http.Get("http://127.0.0.1:38082/test2")

  5.    if resp != nil && resp.Body != nil {

  6.        defer resp.Body.Close()

  7.    }

  8.    if err != nil {

  9.        w.Write([]byte("make request failed\n"))

  10.        return

  11.    }

  12.    io.Copy(w, resp.Body)

  13. }

  14. ......


跑起来

将service1、service2跑起来,然后用curl命令访问service1

 
  
  1. > go run service1.go &> /dev/null

  2. > go run service2.go &> /dev/null

  3. > curl http://127.0.0.1:8081/test1

  4. # 如果出现以下输出,则说明一切正常,Bingo!

  5. hello, world!


其它使用方法

除了Service Mesh的玩法,consul 1.2还提供了SDK的用法。简单来说就是Go语言开发的微服务按照它的规范修改服务提供方、服务消费方的代码,服务间的调用将会自动使用底层的connect隧道。这个使用方法不太符合service mesh的初衷,做过微服务框架sdk的我不是太喜欢,这里就不详细讲了,可以参考官方给出的文档。

  • Connect Native原理:https://www.consul.io/docs/connect/native.html

  • Connect Native Go语言项目改造指引:https://www.consul.io/docs/connect/native/go.html


consul的service mesh原理分析


其实consul的文档本身说的比较明白,这里结合consul-ui及代码大概分析一下。


当给consul的服务配置里添加了 "connect":{"proxy":{}}后,consul将会为每个服务实例创建一个专门的隧道代理,如下图所示:

640?wx_fmt=png


隧道代理的作用是当以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协议了。


consul的service mesh优缺点分析


优点:

  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*.go、 https://github.com/hashicorp/consul/tree/master/agent/connect/下的关键文件不超过20个,逻辑很容易就看清了。

  3. 直接结合consul做服务注册与服务发现,集成度高。


缺点:

  1. 目前的负载均衡算法还很简单,就是随机,见下面:

    640?wx_fmt=png

  2. 一些微服务框架的基本功能还不具备,如超时、重试、熔断、流量分配等,可以从 https://github.com/hashicorp/consul/blob/master/connect/proxy/listener.go#handleConn这里开始扩展。

  3. 需要手动修改consul的服务配置;服务消费方要根据consul里的服务配置,修改调用其它服务的地址(这里跟service mesh的初衷有些不符)。


总结


目前来看consul的service mesh方案还比较简单,功能很基本,但具备进一步扩展的空间,可以好好研究学习它的代码。


参考

  1. https://www.hashicorp.com/blog/consul-1-2-service-mesh

  2. https://www.consul.io/intro/getting-started/connect.html

  3. https://www.consul.io/docs/agent/options.html

  4. https://www.consul.io/docs/connect/intentions.html

  5. https://www.consul.io/docs/connect/native.html

  6. https://www.consul.io/docs/connect/native/go.html

  7. https://www.consul.io/docs/connect/configuration.html

  8. https://www.consul.io/docs/connect/proxies.html

  9. https://www.consul.io/docs/connect/dev.html

  10. https://www.consul.io/docs/connect/ca/consul.html


- END -


 往期推荐:

  • 死磕Java系列:

  1. 深入分析ThreadLocal

  2. 深入分析synchronized的实现原理

  3. 深入分析volatile的实现原理

  4. Java内存模型之happens-before

  5. Java内存模型之重排序

  6. Java内存模型之分析volatile

  7. Java内存模型之总结

  8. J.U.C之AQS简介

  9. J.U.C之AQS:CLH同步队列

  10. J.U.C之AQS同步状态的获取与释放

  11. J.U.C之AQS阻塞和唤醒线程

  12. J.U.C之重入锁:ReentrantLock

  13. J.U.C之读写锁:ReentrantReadWriteLock

  14. J.U.C之Condition

  15. J.U.C之并发工具类:CyclicBarrier

  16. J.U.C之并发工具类:Semaphore

  17. J.U.C之并发工具类:CountDownLatch

……

  • Spring系列:

  1. Spring Cloud Zuul中使用Swagger汇总API接口文档

  2. Spring Cloud Config Server迁移节点或容器化带来的问题

  3. Spring Cloud Config对特殊字符加密的处理

  4. Spring Boot使用@Async实现异步调用:使用Future以及定义超时

  5. Spring Cloud构建微服务架构:分布式配置中心(加密解密)

  6. Spring Boot快速开发利器:Spring Boot CLI

……


可关注我的公众号

640?wx_fmt=jpeg

深入交流、更多福利

扫码加入我的知识星球

640?wx_fmt=png

点击“阅读原文”,看本号其他精彩内容

猜你喜欢

转载自blog.csdn.net/j3t9z7h/article/details/81009457
今日推荐