一、什么是服务发现

微服务的框架体系中,服务发现是不能不提的一个模块。我相信了解或者熟悉微服务的童鞋应该都知道它的重要性。这里我只是简单的提一下,毕竟这不是我们的重点。我们看下面的一幅图片:

 

图中,客户端的一个接口,需要调用服务A-N。客户端必须要知道所有服务的网络位置的,以往的做法是配置是配置文件中,或者有些配置在数据库中。这里就带出几个问题:

  • 需要配置N个服务的网络位置,加大配置的复杂性

  • 服务的网络位置变化,都需要改变每个调用者的配置

  • 集群的情况下,难以做负载(反向代理的方式除外)

总结起来一句话:服务多了,配置很麻烦,问题多多

既然有这些问题,那么服务发现就是解决这些问题的。话说,怎么解决呢?我们再看一张图

 

与之前一张不同的是,加了个服务发现模块。图比较简单,这边文字描述下。服务A-N把当前自己的网络位置注册到服务发现模块(这里注册的意思就是告诉),服务发现就以K-V的方式记录下,K一般是服务名,V就是IP:PORT。服务发现模块定时的轮询查看这些服务能不能访问的了(这就是健康检查)。客户端在调用服务A-N的时候,就跑去服务发现模块问下它们的网络位置,然后再调用它们的服务。这样的方式是不是就可以解决上面的问题了呢?客户端完全不需要记录这些服务网络位置,客户端和服务端完全解耦!

这个过程大体是这样,当然服务发现模块没这么简单。里面包含的东西还很多。这样表述只是方便理解。

图中的服务发现模块基本上就是微服务架构中服务发现的作用了。

二、consul 简介

做服务发现的框架常用的有     zookeeper     eureka     etcd     consul 这里就不比较哪个好哪个差了,需要的童鞋自己谷歌百度。 那么consul是啥?consul就是提供服务发现的工具。然后下面是简单的介绍: consul是分布式的、高可用、横向扩展的。consul提供的一些关键特性:     service discovery:consul通过DNS或者HTTP接口使服务注册和服务发现变的很容易,一些外部服务,例如saas提供的也可以一样注册。     health checking:健康检测使consul可以快速的告警在集群中的操作。和服务发现的集成,可以防止服务转发到故障的服务上面。     key/value storage:一个用来存储动态配置的系统。提供简单的HTTP接口,可以在任何地方操作。     multi-datacenter:无需复杂的配置,即可支持任意数量的区域。 我们这里会介绍服务发现,健康检查,还有一些基本KV存储。多数据中心有机会另一篇文章再说。 总结:只要知道它是解决我上一部分提出的问题就行,其它的东西慢慢理解

三、consul的几个概念

 

上图来自于consul官方文档 我们只看数据中心1,可以看出consul的集群是由N个SERVER,加上M个CLIENT组成的。而不管是SERVER还是CLIENT,都是consul的一个节点,所有的服务都可以注册到这些节点上,正是通过这些节点实现服务注册信息的共享。除了这两个,还有一些小细节,一一简单介绍。     CLIENT CLIENT表示consul的client模式,就是客户端模式。是consul节点的一种模式,这种模式下,所有注册到当前节点的服务会被转发到SERVER,本身是不持久化这些信息。     SERVER SERVER表示consul的server模式,表明这个consul是个server,这种模式下,功能和CLIENT都一样,唯一不同的是,它会把所有的信息持久化的本地,这样遇到故障,信息是可以被保留的。     SERVER-LEADER 中间那个SERVER下面有LEADER的字眼,表明这个SERVER是它们的老大,它和其它SERVER不一样的一点是,它需要负责同步注册的信息给其它的SERVER,同时也要负责各个节点的健康监测。     其它信息其它信息包括它们之间的通信方式,还有一些协议信息,算法。它们是用于保证节点之间的数据同步,实时性要求等等一系列集群问题的解决。这些有兴趣的自己看看官方文档

四、安装 Consul

  Consul 下载地址:https://www.consul.io/downloads.html,下载后解压就是一个可执行的二进制文件consul,配置好环境变量,检查 consul 是否可用:

[root@localhost ~]# consul 
Usage: consul [--version] [--help] <command> [<args>]Available commands are:
    agent          Runs a Consul agent
    catalog        Interact with the catalog
    event          Fire a new event
    exec           Executes a command on Consul nodes
    force-leave    Forces a member of the cluster to enter the "left" state
    info           Provides debugging information for operators.
    join           Tell Consul agent to join cluster
    keygen         Generates a new encryption key
    keyring        Manages gossip layer encryption keys
    kv             Interact with the key-value store
    leave          Gracefully leaves the Consul cluster and shuts down
    lock           Execute a command holding a lock
    maint          Controls node or service maintenance mode
    members        Lists the members of a Consul cluster
    monitor        Stream logs from a Consul agent
    operator       Provides cluster-level tools for Consul operators
    reload         Triggers the agent to reload configuration files
    rtt            Estimates network round trip time between nodes
    snapshot       Saves, restores and inspects snapshots of Consul server state
    validate       Validate config files/directories
    version        Prints the Consul version
    watch          Watch for changes in Consul

  如果出现上面这样代表consul是没问题的。

五、运行 Consul Agent

  Consul安装之后,代理必须运行。 代理可以在服务器或客户端模式下运行。 每个数据中心都必须至少有一台服务器,但推荐使用3台或5台服务器。 一个单一的服务器部署是非常不推荐的,因为在故障情况下数据丢失是不可避免的。   所有其他代理以客户端模式运行。 客户端是一个非常轻量级的进程,它注册服务,运行健康检查,并将查询转发给服务器。 代理程序必须在集群中的每个节点上运行。

  为了简单起见,我们现在将以开发模式启动Consul代理。 这种模式对于快速简单地启动单节点Consul环境非常有用。 它并不打算在生产中使用,因为它不会持续任何状态。

[root@localhost consul]# consul agent -dev==> Starting Consul agent...==> Consul agent running!
           Version: 'v1.0.1'
           Node ID: '590309a6-71f6-6145-fe40-d2c5e203687f'
         Node name: 'localhost.localdomain'
        Datacenter: 'dc1' (Segment: '<all>')
            Server: true (Bootstrap: false)
       Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, DNS: 8600)
      Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
           Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false==> Log data will now stream in as it occurs:

    2017/11/25 15:15:54 [DEBUG] Using random ID "590309a6-71f6-6145-fe40-d2c5e203687f" as node ID    2017/11/25 15:15:54 [INFO] raft: Initial configuration (index=1): [{Suffrage:Voter ID:590309a6-71f6-6145-fe40-d2c5e203687f Address:127.0.0.1:8300}]
    2017/11/25 15:15:54 [INFO] serf: EventMemberJoin: localhost.localdomain.dc1 127.0.0.1
    2017/11/25 15:15:54 [INFO] serf: EventMemberJoin: localhost.localdomain 127.0.0.1
    2017/11/25 15:15:54 [INFO] agent: Started DNS server 127.0.0.1:8600 (udp)
    2017/11/25 15:15:54 [INFO] raft: Node at 127.0.0.1:8300 [Follower] entering Follower state (Leader: "")
    2017/11/25 15:15:54 [INFO] consul: Adding LAN server localhost.localdomain (Addr: tcp/127.0.0.1:8300) (DC: dc1)
    2017/11/25 15:15:54 [INFO] consul: Handled member-join event for server "localhost.localdomain.dc1" in area "wan"
    2017/11/25 15:15:54 [INFO] agent: Started DNS server 127.0.0.1:8600 (tcp)
    2017/11/25 15:15:54 [INFO] agent: Started HTTP server on 127.0.0.1:8500 (tcp)
    2017/11/25 15:15:54 [INFO] agent: started state syncer    2017/11/25 15:15:54 [WARN] raft: Heartbeat timeout from "" reached, starting election    2017/11/25 15:15:54 [INFO] raft: Node at 127.0.0.1:8300 [Candidate] entering Candidate state in term 2
    2017/11/25 15:15:54 [DEBUG] raft: Votes needed: 1
    2017/11/25 15:15:54 [DEBUG] raft: Vote granted from 590309a6-71f6-6145-fe40-d2c5e203687f in term 2. Tally: 1
    2017/11/25 15:15:54 [INFO] raft: Election won. Tally: 1
    2017/11/25 15:15:54 [INFO] raft: Node at 127.0.0.1:8300 [Leader] entering Leader state    2017/11/25 15:15:54 [INFO] consul: cluster leadership acquired    2017/11/25 15:15:54 [DEBUG] consul: Skipping self join check for "localhost.localdomain" since the cluster is too small    2017/11/25 15:15:54 [INFO] consul: member 'localhost.localdomain' joined, marking health alive    2017/11/25 15:15:54 [INFO] consul: New leader elected: localhost.localdomain    2017/11/25 15:15:54 [DEBUG] Skipping remote check "serfHealth" since it is managed automatically    2017/11/25 15:15:54 [INFO] agent: Synced node info    2017/11/25 15:15:54 [DEBUG] agent: Node info in sync    2017/11/25 15:15:57 [DEBUG] Skipping remote check "serfHealth" since it is managed automatically    2017/11/25 15:15:57 [DEBUG] agent: Node info in sync    2017/11/25 15:16:54 [DEBUG] consul: Skipping self join check for "localhost.localdomain" since the cluster is too small    2017/11/25 15:17:51 [DEBUG] Skipping remote check "serfHealth" since it is managed automatically    2017/11/25 15:17:51 [DEBUG] agent: Node info in sync    2017/11/25 15:17:54 [DEBUG] manager: Rebalanced 1 servers, next active server is localhost.localdomain.dc1 (Addr: tcp/127.0.0.1:8300) (DC: dc1)
    2017/11/25 15:17:54 [DEBUG] consul: Skipping self join check for "localhost.localdomain" since the cluster is too small

  如您所见,Consul代理已经启动并输出了一些日志数据。 从日志数据中,您可以看到我们的代理正在服务器模式下运行,并声称拥有集群领导权。 此外,当地成员已被标记为该集群的健康成员。

六、集群成员

  在另一个终端运行consul members,可以看到Consul集群的成员。 应该只看到一个成员(你自己):

[root@localhost ~]# consul members
Node                   Address         Status  Type    Build  Protocol  DC   Segment
localhost.localdomain  127.0.0.1:8301  alive   server  1.0.1  2         dc1  <all>

  输出显示了我们自己的节点,它正在运行的地址,运行状况,在集群中的角色以及一些版本信息。 额外的元数据可以通过提供-detailed标志来查看。

[root@localhost ~]# consul members -detailed
Node                   Address         Status  Tags
localhost.localdomain  127.0.0.1:8301  alive   build=1.0.1:9564c29,dc=dc1,id=590309a6-71f6-6145-fe40-d2c5e203687f,port=8300,raft_vsn=3,role=consul,segment=<all>,vsn=2,vsn_max=3,vsn_min=2,wan_join_port=8302

members命令的输出基于gossip协议,并最终一致。 也就是说,在任何时候,当地代理所看到的可能与服务器上的状态不完全一致。 要获得完全一致,请使用HTTP API再将HTTP请求转发给Consul服务器:

[root@localhost ~]# curl localhost:8500/v1/catalog/nodes[
    {
        "ID": "590309a6-71f6-6145-fe40-d2c5e203687f",
        "Node": "localhost.localdomain",
        "Address": "127.0.0.1",
        "Datacenter": "dc1",
        "TaggedAddresses": {
            "lan": "127.0.0.1",
            "wan": "127.0.0.1"
        },
        "Meta": {
            "consul-network-segment": ""
        },
        "CreateIndex": 5,
        "ModifyIndex": 6
    }]

  除了HTTP API之外,还可以使用DNS接口查询节点。 请注意,必须确保将DNS查找默认情况下指向在端口8600上运行的Consul代理的DNS服务器。 DNS条目的格式(如“Armons-MacBook-Air.node.consul”)将在后面详细介绍。

[root@localhost ~]# dig @127.0.0.1 -p 8600 localhost.localdomain.node.consul
'; <<>> DiG 9.9.4-RedHat-9.9.4-51.el7 <<>> @127.0.0.1 -p 8600 localhost.localdomain.node.consul; (1 server found);; global options: +cmd;; Got answer:;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43915;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1;; WARNING: recursion requested but not available;; OPT PSEUDOSECTION:; EDNS: version: 0, flags:; udp: 4096;; QUESTION SECTION:;localhost.localdomain.node.consul. IN  A;; ANSWER SECTION:localhost.localdomain.node.consul. 0 IN A       127.0.0.1;; Query time: 0 msec;; SERVER: 127.0.0.1#8600(127.0.0.1);; WHEN: 六 11月 25 15:39:49 CST 2017;; MSG SIZE  rcvd: 78

七、停止 Agent

  可以使用Ctrl-C(中断信号)正常停止代理。 中断代理之后,您应该看到它离开集群并关闭。   通过优雅地离开,Consul通知其他集群成员该节点离开。 如果强行杀死了代理进程,则集群的其他成员将检测到该节点失败。 成员离开时,其服务和检查将从目录中删除。 当一个成员失败时,其健康被简单地标记为关键,但不会从目录中删除。 Consul将自动尝试重新连接到失败的节点,使其能够从特定的网络条件恢复,而不再联系离开的节点。   此外,如果代理正在作为服务器运行,那么优雅的离开对于避免造成影响一致协议的潜在可用性中断很重要。 有关如何安全地添加和删除服务器的详细信息,请参阅指南部分。

八、注册服务

1、服务定义

  服务可以通过提供服务定义或通过对HTTP API进行适当的调用来注册。   服务定义是注册服务最常用的方式,所以我们将在这一步中使用这种方法。 我们将建立在上一步中介绍的代理配置。   首先,为Consul配置创建一个目录。 Consul将所有配置文件加载到配置目录中,因此Unix系统上的一个通用约定是将目录命名为/etc/consul.d(.d后缀意味着“该目录包含一组配置文件”)。

[root@localhost etc]# sudo mkdir /etc/consul.d

  接下来,我们将编写一个服务定义配置文件。 假设我们有一个名为“web”的服务在端口80上运行。另外,我们给它一个标签,我们可以使用它作为查询服务的附加方式:

[root@localhost ~]# echo '{"service": {"name": "web", "tags": ["rails"], "port": 80}}' | sudo tee /etc/consul.d/web.json

  现在,重新启动代理程序,提供配置目录:

[root@localhost ~]# consul agent -dev -config-dir=/etc/consul.d==> Starting Consul agent...==> Consul agent running!
           Version: 'v1.0.1'
           Node ID: '94236f1c-2a29-85c5-b235-dd916485be5b'
         Node name: 'localhost.localdomain'
        Datacenter: 'dc1' (Segment: '<all>')
            Server: true (Bootstrap: false)
       Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, DNS: 8600)
      Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
           Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false==> Log data will now stream in as it occurs:

    2017/11/25 16:16:51 [DEBUG] Using random ID "94236f1c-2a29-85c5-b235-dd916485be5b" as node ID    2017/11/25 16:16:51 [INFO] raft: Initial configuration (index=1): [{Suffrage:Voter ID:94236f1c-2a29-85c5-b235-dd916485be5b Address:127.0.0.1:8300}]
    2017/11/25 16:16:51 [INFO] serf: EventMemberJoin: localhost.localdomain.dc1 127.0.0.1
    2017/11/25 16:16:51 [INFO] serf: EventMemberJoin: localhost.localdomain 127.0.0.1
    2017/11/25 16:16:51 [INFO] agent: Started DNS server 127.0.0.1:8600 (udp)
    2017/11/25 16:16:51 [INFO] raft: Node at 127.0.0.1:8300 [Follower] entering Follower state (Leader: "")
    2017/11/25 16:16:51 [INFO] consul: Adding LAN server localhost.localdomain (Addr: tcp/127.0.0.1:8300) (DC: dc1)
    2017/11/25 16:16:51 [INFO] consul: Handled member-join event for server "localhost.localdomain.dc1" in area "wan"
    2017/11/25 16:16:51 [INFO] agent: Started DNS server 127.0.0.1:8600 (tcp)
    2017/11/25 16:16:51 [INFO] agent: Started HTTP server on 127.0.0.1:8500 (tcp)
    2017/11/25 16:16:51 [INFO] agent: started state syncer    2017/11/25 16:16:52 [WARN] raft: Heartbeat timeout from "" reached, starting election    2017/11/25 16:16:52 [INFO] raft: Node at 127.0.0.1:8300 [Candidate] entering Candidate state in term 2
    2017/11/25 16:16:52 [DEBUG] raft: Votes needed: 1
    2017/11/25 16:16:52 [DEBUG] raft: Vote granted from 94236f1c-2a29-85c5-b235-dd916485be5b in term 2. Tally: 1
    2017/11/25 16:16:52 [INFO] raft: Election won. Tally: 1
    2017/11/25 16:16:52 [INFO] raft: Node at 127.0.0.1:8300 [Leader] entering Leader state    2017/11/25 16:16:52 [INFO] consul: cluster leadership acquired    2017/11/25 16:16:52 [DEBUG] consul: Skipping self join check for "localhost.localdomain" since the cluster is too small    2017/11/25 16:16:52 [INFO] consul: member 'localhost.localdomain' joined, marking health alive    2017/11/25 16:16:52 [INFO] consul: New leader elected: localhost.localdomain    2017/11/25 16:16:52 [DEBUG] Skipping remote check "serfHealth" since it is managed automatically    2017/11/25 16:16:52 [INFO] agent: Synced service "web"
    2017/11/25 16:16:52 [DEBUG] agent: Node info in sync    2017/11/25 16:16:52 [DEBUG] agent: Service "web" in sync    2017/11/25 16:16:52 [DEBUG] agent: Node info in sync    2017/11/25 16:16:52 [DEBUG] Skipping remote check "serfHealth" since it is managed automatically    2017/11/25 16:16:52 [DEBUG] agent: Service "web" in sync    2017/11/25 16:16:52 [DEBUG] agent: Node info in sync

  您会注意到它在输出中“同步”了Web服务。 这意味着代理程序从配置文件加载了服务定义,并已成功将其注册到服务目录中。   如果您想注册多个服务,您可以在Consul配置目录中创建多个服务定义文件。

2、查询服务

  一旦代理启动并且服务同步,我们可以使用DNS或HTTP API来查询服务。

DNS API

  我们首先使用DNS API来查询我们的服务。 对于DNS API,服务的DNS名称是NAME.service.consul。 默认情况下,所有DNS名称始终在consul命名空间中,尽管这是可配置的。 服务子域告诉Consul我们正在查询服务,NAME是服务的名称。   对于我们注册的Web服务,这些约定和设置会生成web.service.consul的完全限定的域名:

[root@localhost ~]# dig @127.0.0.1 -p 8600 web.service.consul; <<>> DiG 9.9.4-RedHat-9.9.4-51.el7 <<>> @127.0.0.1 -p 8600 web.service.consul; (1 server found);; global options: +cmd;; Got answer:;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58483;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1;; WARNING: recursion requested but not available;; OPT PSEUDOSECTION:; EDNS: version: 0, flags:; udp: 4096;; QUESTION SECTION:;web.service.consul.            IN      A;; ANSWER SECTION:web.service.consul.     0       IN      A       127.0.0.1;; Query time: 0 msec;; SERVER: 127.0.0.1#8600(127.0.0.1);; WHEN: 六 11月 25 16:22:29 CST 2017;; MSG SIZE  rcvd: 63

  正如你所看到的,一个A记录返回了服务可用的节点的IP地址。 A记录只能保存IP地址。   您也可以使用DNS API来检索整个地址/端口对作为SRV记录:

[root@localhost ~]# dig @127.0.0.1 -p 8600 web.service.consul SRV; <<>> DiG 9.9.4-RedHat-9.9.4-51.el7 <<>> @127.0.0.1 -p 8600 web.service.consul SRV; (1 server found);; global options: +cmd;; Got answer:;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 65288;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 3;; WARNING: recursion requested but not available;; OPT PSEUDOSECTION:; EDNS: version: 0, flags:; udp: 4096;; QUESTION SECTION:;web.service.consul. IN SRV;; ANSWER SECTION:web.service.consul. 0 IN SRV 1 1 80 localhost.localdomain.node.dc1.consul.;; ADDITIONAL SECTION:localhost.localdomain.node.dc1.consul. 0 IN A 127.0.0.1localhost.localdomain.node.dc1.consul. 0 IN TXT "consul-network-segment=";; Query time: 0 msec;; SERVER: 127.0.0.1#8600(127.0.0.1);; WHEN: 六 11月 25 16:25:21 CST 2017;; MSG SIZE rcvd: 156

  SRV记录表示Web服务正在端口80上运行,并且存在于节点localhost.localdomain.node.dc1.consul.上。DNS使用该记录的A记录返回附加部分。   最后,我们也可以使用DNS API来按标签过滤服务。 基于标记的服务查询的格式是TAG.NAME.service.consul。 在下面的例子中,我们向Consul询问所有带有“rails”标签的web服务。 自从我们使用该标签注册我们的服务后,我们得到了成功的回应:

[root@localhost ~]# dig @127.0.0.1 -p 8600 rails.web.service.consul; <<>> DiG 9.9.4-RedHat-9.9.4-51.el7 <<>> @127.0.0.1 -p 8600 rails.web.service.consul; (1 server found);; global options: +cmd;; Got answer:;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41016;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1;; WARNING: recursion requested but not available;; OPT PSEUDOSECTION:; EDNS: version: 0, flags:; udp: 4096;; QUESTION SECTION:;rails.web.service.consul. IN A;; ANSWER SECTION:rails.web.service.consul. 0 IN A 127.0.0.1;; Query time: 0 msec;; SERVER: 127.0.0.1#8600(127.0.0.1);; WHEN: 六 11月 25 16:29:10 CST 2017;; MSG SIZE rcvd: 69

HTTP API

  除了DNS API之外,HTTP API还可以用来查询服务:

[root@localhost ~]# curl http://localhost:8500/v1/catalog/service/web[
    {
        "ID": "94236f1c-2a29-85c5-b235-dd916485be5b",
        "Node": "localhost.localdomain",
        "Address": "127.0.0.1",
        "Datacenter": "dc1",
        "TaggedAddresses": {
            "lan": "127.0.0.1",
            "wan": "127.0.0.1"
        },
        "NodeMeta": {
            "consul-network-segment": ""
        },
        "ServiceID": "web",
        "ServiceName": "web",
        "ServiceTags": [
            "rails"
        ],
        "ServiceAddress": "",
        "ServicePort": 80,
        "ServiceEnableTagOverride": false,
        "CreateIndex": 6,
        "ModifyIndex": 6
    }]

  目录API提供了托管给定服务的所有节点。 正如我们稍后将看到的健康检查一样,您通常只需要查询检查通过的健康实例。 这是DNS正在做的事情。 这是一个查询只查找健康的实例:

[root@localhost ~]# curl 'http://localhost:8500/v1/health/service/web?passing'[
    {
        "Node": {
            "ID": "94236f1c-2a29-85c5-b235-dd916485be5b",
            "Node": "localhost.localdomain",
            "Address": "127.0.0.1",
            "Datacenter": "dc1",
            "TaggedAddresses": {
                "lan": "127.0.0.1",
                "wan": "127.0.0.1"
            },
            "Meta": {
                "consul-network-segment": ""
            },
            "CreateIndex": 5,
            "ModifyIndex": 6
        },
        "Service": {
            "ID": "web",
            "Service": "web",
            "Tags": [
                "rails"
            ],
            "Address": "",
            "Port": 80,
            "EnableTagOverride": false,
            "CreateIndex": 6,
            "ModifyIndex": 6
        },
        "Checks": [
            {
                "Node": "localhost.localdomain",
                "CheckID": "serfHealth",
                "Name": "Serf Health Status",
                "Status": "passing",
                "Notes": "",
                "Output": "Agent alive and reachable",
                "ServiceID": "",
                "ServiceName": "",
                "ServiceTags": [],
                "Definition": {},
                "CreateIndex": 5,
                "ModifyIndex": 5
            }
        ]
    }]

3、更新服务

  服务定义可以通过更改配置文件并向代理发送SIGHUP来更新。 这使您可以更新服务,而不会出现任何停机或无法提供服务查询的情况。   或者,可以使用HTTP API动态地添加,删除和修改服务。

九、Consul 集群

  我们已经开始了我们的第一个代理,并注册和查询该代理的服务。 这显示了使用Consul是多么的容易,但并没有表明如何将其扩展到可扩展的生产级服务发现基础设施。 在这一步中,我们将创建我们的第一个真正的集群与多个成员。   当一个Consul代理启动时,它不知道任何其他节点:它是一个孤立的集群。 要了解其他集群成员,代理必须加入现有集群。 要加入现有的集群,只需要知道一个现有的成员。 代理加入后,会与该成员通讯,并迅速发现集群中的其他成员。 Consul代理可以加入任何其他代理,而不仅仅是服务器模式下的代理。

1、启动代理

  在我们之前的例子中,我们使用了-dev标志来快速设置一个开发服务器。 但是,这不足以在集群环境中使用。 我们将从这里省略-dev标志,而是指定我们的集群标志。   集群中的每个节点都必须具有唯一的名称。 默认情况下,Consul使用机器的主机名,但我们将使用-node命令行选项手动覆盖它。   我们还将指定一个-bind:这是Consul侦听的地址,它必须可以被集群中的所有其他节点访问。 虽然绑定地址不是绝对必要的,但最好提供一个。 Consul将默认尝试侦听系统上的所有IPv4接口,但如果找到多个私有IP,将无法启动错误。 由于生产服务器通常具有多个接口,因此指定一个绑定地址可确保您永远不会将Consul绑定到错误的接口。   第一个节点将作为我们在这个集群中唯一的服务器,我们用-server来指明这一点。 -bootstrap-expect选项向Consul服务器提示我们期望加入的其他服务器节点的数量。 此选项的用途是延迟复制日志的引导,直到预期数量的服务器成功加入。   我们已经将-enable_script_checks选项设置为true,以启用可以执行外部脚本的运行状况检查。 这将在后面的例子中使用。 对于生产用途,您希望将ACL配置为与此配合使用,以控制注册任意脚本的能力。   最后,我们添加-config-dir选项,标记可以找到服务和检查定义的位置。   总而言之,这些设置产生一个这样的consul代理命令:

[root@localhost ~]# consul agent -server -bootstrap-expect=1 -data-dir=/tmp/consul -node=agent-one -bind=192.168.100.101 -enable-script-checks=true -config-dir=/etc/consul.d

  现在,在另一个终端中,我们将连接到第二个节点。   这次,我们将-bind设置为第二个节点的IP,并将-node设置为agent-two。 由于这个节点不会是Consul服务器,所以我们不提供-server。   总而言之,这些设置产生一个这样的consul代理命令:

[root@localhost ~]# consul agent -data-dir=/tmp/consul -node=agent-two -bind=192.168.100.102 -enable-script-checks=true -config-dir=/etc/consul.d

  此时,您有两个Consul代理正在运行:一个服务器和一个客户端。 两个Consul代理人对彼此还是一无所知,都是他们自己的单节点集群的一部分。 您可以通过对每个代理运行consul members来验证这一点,并注意到每个代理只能看到一个成员。

2、加入集群

  现在,我们将通过在新终端中运行以下命令来告诉第一个代理加入第二个代理:

[root@localhost ~]# consul join 192.168.100.101Successfully joined cluster by contacting 1 nodes.

  如果在虚拟机里面运行上面的命令提示下面的失败的话,在每台虚拟机上执行下这个命令:

[root@localhost ~]# consul join 192.168.100.101Error joining address '192.168.100.101': Unexpected response code: 500 (1 error(s) occurred:* Failed to join 192.168.100.101: dial tcp 192.168.100.101:8301: getsockopt: no route to host)Failed to join any nodes.[root@localhost ~]# sudo iptables -F

  您应该在每个代理日志中看到一些日志输出。 如果仔细阅读,您会看到他们收到了加入信息。 如果你对每个代理执行consul members,你会看到两个代理人现在彼此了解:

[root@localhost ~]# consul members
Node       Address               Status  Type    Build  Protocol  DC   Segment
agent-one  192.168.100.101:8301  alive   server  1.0.1  2         dc1  <all>agent-two  192.168.100.102:8301  alive   client  1.0.1  2         dc1  <default>

记住:要加入集群,Consul代理只需要了解一个现有的成员。 加入集群后,代理人互相传播完整的会员信息。

3、在启动时自动加入集群

  理想情况下,每当新节点出现在您的数据中心时,它就会自动加入Consul集群,无需人工干预。 Consul通过启用AWS,Google Cloud或Azure中的实例的自动发现功能,使用给定的标签 key/value来促进自动加入。 要使用集成,请将retry_join_ec2retry_join_gceretry_join_azure嵌套对象添加到您的Consul配置文件。 这将允许新的节点加入集群,而不需要任何硬编码的配置。 或者,您可以在启动时使用-join选项或start_join设置以及其他已知Consul代理的硬编码地址加入集群。

4、查询节点

  就像查询服务一样,Consul也有查询节点的API。 您可以通过DNS或HTTP API执行此操作。   对于DNS API,名称的结构是NAME.node.consul或NAME.node.DATACENTER.consul。 如果数据中心被省略,Consul将仅搜索本地数据中心。   例如,从“agent-one”中,我们可以查询节点“agent-two”的地址:

[root@localhost etc]# dig @127.0.0.1 -p 8600 agent-two.node.consul; <<>> DiG 9.9.4-RedHat-9.9.4-51.el7 <<>> @127.0.0.1 -p 8600 agent-two.node.consul; (1 server found);; global options: +cmd;; Got answer:;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57127;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1;; WARNING: recursion requested but not available;; OPT PSEUDOSECTION:; EDNS: version: 0, flags:; udp: 4096;; QUESTION SECTION:;agent-two.node.consul. IN A;; ANSWER SECTION:agent-two.node.consul. 0 IN A 192.168.100.102;; Query time: 0 msec;; SERVER: 127.0.0.1#8600(127.0.0.1);; WHEN: 六 11月 25 17:31:29 CST 2017;; MSG SIZE rcvd: 66

  除了服务之外,查找节点的能力对于系统管理任务来说是非常有用的。 例如,知道要通过SSH连接的节点的地址与将节点作为Consul集群的一部分并查询它一样简单。

5、离开集群

  要离开集群,可以正常退出代理(使用Ctrl-C)或强制终止其中一个代理。 优雅地离开允许节点转换到离开状态; 否则,其他节点将检测到它失败。

十、健康检查

  现在我们已经看到了运行Consul,添加节点和服务以及查询这些节点和服务的简单性。 在本节中,我们将介绍为节点和服务添加健康检查。 健康检查是服务发现的关键组件,可以防止使用不健康的服务。   此步骤建立在之前创建的Consul集群上。 此时,您应该运行一个双节点集群。

1、检查定义

  与服务类似,可以通过提供检查定义或通过对HTTP API进行适当的调用来注册检查。   我们将使用检查定义方法,因为就像服务一样,定义是设置检查最常用的方法。   在第二个节点的Consul配置目录中创建两个定义文件:

[root@localhost ~]# echo '{"check": {"name": "ping", "script": "ping -c1 google.com >/dev/null", "interval": "30s"}}' >/etc/consul.d/ping.json[root@localhost ~]# echo '{"service": {"name": "web", "tags": ["rails"], "port": 80, "check": {"script": "curl localhost >/dev/null 2>&1", "interval": "10s"}}}' >/etc/consul.d/web.json

  第一个定义添加了一个名为“ping”的主机级别的检查。 此检查运行间隔30秒,调用ping -c1 google.com。 在基于脚本的运行状况检查上,检查以与启动Consul进程相同的用户身份运行。 如果该命令以非零退出码退出,则该节点将被标记为不健康。 这是任何基于脚本的健康检查的约定。   第二个命令修改名为web的服务,添加一个检查,每隔10秒通过curl发送一个请求,以验证Web服务器是否可访问。 与主机级运行状况检查一样,如果脚本以非零退出代码退出,服务将被标记为不健康。   现在,重新启动第二个代理,用consul reload加载它,或者发送一个SIGHUP信号。 您应该看到以下日志行:

[root@localhost ~]# consul reload
Configuration reload triggered2017/11/26 10:47:41 [INFO] Reloading configuration...
    2017/11/26 10:47:41 [WARN] agent: check "service:web" has the 'script' field, which has been deprecated and replaced with the 'args' field. See https://www.consul.io/docs/agent/checks.html    2017/11/26 10:47:41 [WARN] agent: check "service:web" has the 'script' field, which has been deprecated and replaced with the 'args' field. See https://www.consul.io/docs/agent/checks.html    2017/11/26 10:47:41 [WARN] agent: check "ping" has the 'script' field, which has been deprecated and replaced with the 'args' field. See https://www.consul.io/docs/agent/checks.html    2017/11/26 10:47:41 [WARN] agent: check "ping" has the 'script' field, which has been deprecated and replaced with the 'args' field. See https://www.consul.io/docs/agent/checks.html    2017/11/26 10:47:41 [INFO] agent: Synced service "web"
    2017/11/26 10:47:41 [INFO] agent: Synced check "ping"
    2017/11/26 10:47:47 [WARN] agent: Check "service:web" is now critical    2017/11/26 10:47:57 [WARN] agent: Check "service:web" is now critical    2017/11/26 10:48:04 [WARN] agent: Check "ping" is now warning    2017/11/26 10:48:04 [INFO] agent: Synced check "ping"
    2017/11/26 10:48:07 [WARN] agent: Check "service:web" is now critical    2017/11/26 10:48:17 [WARN] agent: Check "service:web" is now critical    2017/11/26 10:48:27 [WARN] agent: Check "service:web" is now critical

  前几行表示代理已经同步了新的定义。 最后一行表明我们为Web服务添加的检查是至关重要的。 这是因为我们实际上没有运行Web服务器,所以curl测试失败了!

2、检查健康状态

  现在我们已经添加了一些简单的检查,我们可以使用HTTP API来检查它们。 首先,我们可以使用这个命令查找任何失败的检查(注意,这可以在任一节点上运行):

[root@localhost etc]# curl http://localhost:8500/v1/health/state/critical[{"Node":"agent-two","CheckID":"service:web","Name":"Service 'web' check","Status":"critical","Notes":"","Output":"","ServiceID":"web","ServiceName":"web","ServiceTags":["rails"],"Definition":{},"CreateIndex":230,"ModifyIndex":262}]

  我们可以看到,只有一个检查,我们的Web服务检查,在危险(critical)状态。   另外,我们可以尝试使用DNS查询Web服务。 由于服务不健康,Consul不会返回任何结果:

[root@localhost ~]# dig @127.0.0.1 -p 8600 web.service.consul; <<>> DiG 9.9.4-RedHat-9.9.4-51.el7 <<>> @127.0.0.1 -p 8600 web.service.consul; (1 server found);; global options: +cmd;; Got answer:;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 38998;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1;; WARNING: recursion requested but not available;; OPT PSEUDOSECTION:; EDNS: version: 0, flags:; udp: 4096;; QUESTION SECTION:;web.service.consul.            IN      A;; AUTHORITY SECTION:consul.                 0       IN      SOA     ns.consul. hostmaster.consul. 1511666146 3600 600 86400 0;; Query time: 1 msec;; SERVER: 127.0.0.1#8600(127.0.0.1);; WHEN: 日 11月 26 11:15:46 CST 2017;; MSG SIZE  rcvd: 97

十一、KV Data

  Consul除了提供服务发现和综合健康检查之外,还提供一个易于使用的KV存储。 这可以用来保存动态配置,协助服务协调,建立leader选举,并使开发人员可以考虑构建的任何东西。

1、简单使用

  为了演示有多简单,我们将操作K / V存储中的几个键。 有两种方式与Consul KV存储进行交互:通过HTTP API和Consul KV CLI。 以下示例显示使用Consul KV CLI,因为它是最容易入门的。 对于更高级的集成,您可能需要使用Consul KV HTTP API。   首先让我们探索KV存储。 我们可以向Consul询问名为redis / config / minconns的路径上的key的值:

[root@localhost ~]# consul kv get redis/config/minconns
Error! No key exists at: redis/config/minconns

  正如你所看到的,我们没有得到任何结果,这是合理的,因为KV存储没有数据。 接下来,我们可以insert或put 值到 KV 存储中。

[root@localhost ~]# consul kv put redis/config/minconns 1Success! Data written to: redis/config/minconns[root@localhost ~]# consul kv put redis/config/maxconns 25Success! Data written to: redis/config/maxconns[root@localhost ~]# consul kv put -flags=42 redis/config/users/admin abcd1234
Success! Data written to: redis/config/users/admin

  现在我们在存储中有key,我们可以查询单个 key 的 value:

[root@localhost ~]# consul kv get redis/config/minconns1

  Consul保留有关使用-detailed标志检索的字段的其他元数据:

[root@localhost ~]# consul kv get -detailed redis/config/minconns
CreateIndex      517Flags            0Key              redis/config/minconns
LockIndex        0ModifyIndex      517Session          -Value            1

  对于“redis / config / users / admin”这个键,我们设置了一个标志值42。所有的键都支持设置一个64位的整数标志值。 这不是Consul在内部使用的,但客户可以使用它为任何KV添加有意义的元数据。   可以使用递归选项列出存储的所有 key和 value。 结果将以字典顺序返回:

[root@localhost ~]# consul kv get -recurse
redis/config/maxconns:25redis/config/minconns:1redis/config/users/admin:abcd1234

  要从Consul KV中删除一个 key,发出“删除” 命令:

[root@localhost ~]# consul kv delete redis/config/minconns
Success! Deleted key: redis/config/minconns

  也可以使用递归选项删除整个前缀:

[root@localhost ~]# consul kv delete -recurse redis
Success! Deleted keys with prefix: redis

  要更新现有key的值,请在相同路径上put一个值:

[root@localhost ~]# consul kv put foo bar
Success! Data written to: foo[root@localhost ~]# consul kv get foo
bar[root@localhost ~]# consul kv put foo zip
Success! Data written to: foo[root@localhost ~]# consul kv get foo
zip

  Consul可以使用Check-And-Set操作提供原子键更新。 要执行CAS操作,请指定-cas选项:

[root@localhost ~]# consul kv get -detailed foo
CreateIndex      710Flags            0Key              foo
LockIndex        0ModifyIndex      716Session          -Value            bar[root@localhost ~]# consul kv put -cas -modify-index=716 foo bar
Success! Data written to: foo[root@localhost ~]# consul kv put -cas -modify-index=716 foo bar
Error! Did not write to foo: CAS failed

  在这种情况下,第一个CAS更新成功,因为索引是716。第二个操作失败,因为索引不再是716。

十二、Consul Web UI

  Consul支持web ui界面。UI可用于查看所有服务和节点,查看所有运行状况检查及其当前状态,以及读取和设置键/值数据。 用户界面自动支持多数据中心。   要设置自带的UI,请使用-ui参数启动Consul代理:

consul agent -ui

  UI可以在与HTTP API相同的端口上的/ui路径中使用。 默认情况下,这是http://localhost:8500/ui。   您可以在这里查看Consul Web UI的现场演示。

十三、Docker下安装consul

1、拉取镜像

 docker search consul

2、启动consul

  • 启动节点1(server模式) docker run -d -e 'CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}' --name=node1 consul agent -server -bind=172.17.0.2  -bootstrap-expect=3 -node=node1  -node:节点的名称 -bind:绑定的一个地址,用于节点之间通信的地址,可以是内外网,必须是可以访问到的地址 -server:这个就是表示这个节点是个SERVER -bootstrap-expect:这个就是表示期望提供的SERVER节点数目,数目一达到,它就会被激活,然后就是LEADER了

  • 启动节点2-3(server模式) docker run -d -e 'CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}' --name=node2 consul agent -server -bind=172.17.0.3  -join=172.17.0.2 -node-id=$(uuidgen | awk '{print tolower($0)}')  -node=node2  docker run -d -e 'CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}' --name=node3 consul agent -server -bind=172.17.0.4  -join=172.17.0.2 -node-id=$(uuidgen | awk '{print tolower($0)}')  -node=node3 -client=172.17.0.4  -join:这个表示启动的时候,要加入到哪个集群内,这里就是说要加入到节点1的集群 -node-id:这个貌似版本8才加入的,这里用这个来指定唯一的节点ID,可以查看这个issue -client:这个表示注册或者查询等一系列客户端对它操作的IP,如果不指定这个IP,默认是127.0.0.1。

  • 启动节点4(client模式) docker run -d -e 'CONSUL_LOCAL_CONFIG={"leave_on_terminate": true}' --name=node4 consul agent -bind=172.17.0.5 -retry-join=172.17.0.2 -node-id=$(uuidgen | awk '{print tolower($0)}')  -node=node4  除了没有-server,其它都是一样的,没有这个就说明这个节点是CLIENT

  • 查看下集群的状态 docker exec -t node1 consul members

 

4个节点都列出来了。Status表示它们的状态,都是alive。Type表示它们的类型,三个SERVER一个CLIENT,和我们之前启动的一样。DC表示数据中心,都是dc1。

  • 节点异常consul的处理

    日志打印,心跳检查node1的ip超时,接着开始选举。node2被选举为新的leader。我们查看下现在的leader: curl http://172.17.0.4:8500/v1/status/leader 返回的内容:   "172.17.0.3:8300"  172.17.0.3 就是 node2节点的IP

    • LEADER 挂了 leader挂了,consul会重新选取出新的leader,只要超过一半的SERVER还活着,集群是可以正常工作的。node1是leader,所以把这个容器停了。 docker stop node1 看看其他节点的日志(node2):

3、注册个服务

使用HTTP API 注册个服务,使用[接口API](https://www.consul.io/api/agent/service.html API)调用

调用 http://consul:8500/v1/agent/service/register PUT 注册一个服务。request body:

{
  "ID": "userServiceId", 
  "Name": "userService", 
  "Tags": [              
    "primary",
    "v1"
  ],
  "Address": "127.0.0.1",
  "Port": 8000,          
  "EnableTagOverride": false,
  "Check": {             
    "DeregisterCriticalServiceAfter": "90m",
    "HTTP": "http://www.baidu.com", 
    "Interval": "10s"   
  }}

使用curl调用

curl http://172.17.0.4:8500/v1/agent/service/register -X PUT -i -H "Content-Type:application/json" -d '{
  "ID": "userServiceId",  
  "Name": "userService",
  "Tags": [
    "primary",
    "v1"
  ],
  "Address": "127.0.0.1",
  "Port": 8000,
  "EnableTagOverride": false,
  "Check": {
    "DeregisterCriticalServiceAfter": "90m",
    "HTTP": "http://www.baidu.com",
    "Interval": "10s"
  }}'

4、发现个服务

刚刚注册了名为userService的服务,我们现在发现(查询)下这个服务

curl http://172.17.0.4:8500/v1/catalog/service/userService

返回的响应:

[
    {
        "Address": "172.17.0.4",
        "CreateIndex": 880,
        "ID": "e6e9a8cb-c47e-4be9-b13e-a24a1582e825",
        "ModifyIndex": 880,
        "Node": "node3",
        "NodeMeta": {},
        "ServiceAddress": "127.0.0.1",
        "ServiceEnableTagOverride": false,
        "ServiceID": "userServiceId",
        "ServiceName": "userService",
        "ServicePort": 8000,
        "ServiceTags": [
            "primary",
            "v1"
        ],
        "TaggedAddresses": {
            "lan": "172.17.0.4",
            "wan": "172.17.0.4"
        }
    }]

内容有了吧,这个就是我们刚刚注册的服务的信息,就可以获取到

服务的名称是“userService” 服务地址是“127.0.0.1” 服务的端口是“8000”

5、存储个K/V

设置一个值到user/config/connections 内容为5

docker exec -t node1 consul kv put user/config/connections 5

6、获取特定的值

docker exec -t node1 consul kv get -detailed user/config/connections

值的内容为5,还有key等相关的值。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐