1. 介绍

1.1 什么是 consul

Consul 是一个支持多数据中心、分布式、高可用的服务发现和配置系统,由 HashiCorp 公司用 Go 语言开发,
Consul 支持健康检查,并允许 HTTP 和 DNS 协议调用 API 存储键值对

1.2 consul 能解决什么问题

  • 服务发现:consul 客户端能提供服务发现功能,使用 DNS 或者 http 请求
  • 健康检查:consul 可以提供健康检查,可以检查 web 服务是否返回200,可以监控集群的健康情况
  • kv 存储:提供 kv 存储,可以保存动态配置、分布式选举等。提供方便的 http api 来访问 kv 数据
  • 多数据中心:支持多个数据中心,便于系统的跨中心扩展

1.3 基本架构

consul 是一个分布式、高可用系统。它通过在每个服务节点(node)上运行 consul agent 进行服务的健康检查。agent 与一个或多个 consul server 交换信息。这些 consul server 保存数据和副本,server 之间自主选举出一个 leader。推荐用 3 到 5 个 server 构成集群,避免数据丢失。建议每个数据中心使用一个 consul 集群

1.4 与现有系统的比较(zookeeper,etcd)

  1. Zookeeper 与 etcd 在功能和架构上是类似的,他们通过 nodes 间的投票选举主服务器,它们采用强一致算法,应用程序需要使用客户端库来构建复杂的分布式系统
  2. consul 在同一个数据中心内也使用 server nodes 投票方式提供强一致。它提供多数据中心的支持,多个数据中心的 server nodes 和 client 之间可以相互连接
  3. Zookeeper 没有直接提供服务发现功能,需要应用程序使用 kv 存储机制来构建服务发现功能,在 consul 中consul agent 客户端只需要简单的注册服务,就可以通过 DNS 查询 或者 HTTP 接口使用服务发现功能
  4. consul agent client 安装在服务节点上,可以完成丰富的检查功能,例如:服务状态 200 ok、内存、磁盘是否使用超量,并且 client 提供了简单的 HTTP 接口访问服务

2. 安装部署

2.1 安装 consul

consul 使用 golang 开发,编译后只有一个可执行文件,把这个可执行文件安装到系统内 PATH环境变量指向的路径下即可

  1. linux 下安装到 /usr/bin
  2. Mac 下面通过 brew install consul 安装

终端下确认安装成功

$ 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

2.2 Consul 运行模式

安装 consul 后,可以运行 consul agent 在 服务器(server)或者 客户端(client)模式。每个数据中心至少有一台 server 模式运行的 consul,推荐是 3 或者 5 台。当出现故障时,单一 server 运行有丢失数据的风险

其他 agent 运行在客户端模式,这是一个轻量级的进程,处理服务注册、健康检查、向 consul server 转发查询的请求。Agent 必须运行在集群的每一个节点上

2.3 运行集群

通过创建同一个 lo 设备的别名,演示 consul 集群的使用

1. linux 网卡别名创建,创建三个别名

$ sudo ifconfig lo:2 127.0.0.2 up
$ sudo ifconfig lo:3 127.0.0.3 up
$ sudo ifconfig lo:4 127.0.0.4 up

$ ifconfig -a

lo:2      Link encap:Local Loopback
          inet addr:127.0.0.2  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1

lo:3      Link encap:Local Loopback
          inet addr:127.0.0.3  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1

lo:4      Link encap:Local Loopback
          inet addr:127.0.0.4  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1


2. Mac 网卡别名创建

$ sudo ifconfig lo0 inet 127.0.0.2 alias
$ sudo ifconfig lo0 inet 127.0.0.3 alias
$ sudo ifconfig lo0 inet 127.0.0.4 alias

$ ifconfig -a

lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
    options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
    inet 127.0.0.1 netmask 0xff000000
    inet6 ::1 prefixlen 128
    inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
    inet 127.0.0.2 netmask 0xff000000
    inet 127.0.0.3 netmask 0xff000000
    inet 127.0.0.4 netmask 0xff000000
    nd6 options=201<PERFORMNUD,DAD>


3. 创建集群服务配置文件目录

# 创建 cluster 需要的配置目录,每个 consul server 对应一个 consul-0, consul-0.d 目录
$ mkdir -pv /data/tmp/{consul-0,consul-0.d,consul-1,consul-1.d,consul-2,consul-2.d}


4. 启动三个 consul agent server

# 参数说明
# server:服务器模式运行
# dns-port:dns 查询端口号
# http-port:http 服务端口
# ui:开启 web 管理页面
# bootstrap-expect:启动集群需要最小的服务器数
# data-dir:服务数据存储的目录
# node:agent 名称
# bind:绑定服务的 ip 地址
# config-dir:配置目录

$ /usr/local/bin/consul agent -server -dns-port=4001 -http-port=4101 -ui -bootstrap-expect=2 -data-dir=/data/tmp/consul-0 -node=agent-one -bind=127.0.0.2 -config-dir=/data/tmp/consul-0.d

$ /usr/local/bin/consul agent -server -dns-port=4002 -http-port=4102 -bootstrap-expect=2 -ui -data-dir=/data/tmp/consul-1 -node=agent-two -bind=127.0.0.3 -config-dir=/data/tmp/consul-1.d

$ /usr/local/bin/consul agent -server -dns-port=4003 -http-port=4103 -ui -bootstrap-expect=2 -data-dir=/data/tmp/consul-2 -node=agent-three -bind=127.0.0.4 -config-dir=/data/tmp/consul-2.d


5. 查看运行状态

$ consul members -http-addr=127.0.0.1:4103
Node         Address         Status  Type    Build  Protocol  DC   Segment
agent-one    127.0.0.2:8301  alive   server  1.0.0  2         dc1  <all>
agent-three  127.0.0.4:8301  alive   server  1.0.0  2         dc1  <all>
agent-two    127.0.0.3:8301  alive   server  1.0.0  2         dc1  <all>


6. 通过 dns 查询服务信息

$ dig @127.0.0.1 -p 4001 agent-one.node.consul

;; QUESTION SECTION:
;agent-one.node.consul.        IN    A

;; ANSWER SECTION:
agent-one.node.consul.    0    IN    A    127.0.0.2



2.4 服务注册

服务注册功能,可以把服务注册到 consul 系统,方法有两种

  1. 通过 HTTP API 的方式
  2. 通过定义配置的方式,最常用的方式

1. 创建 consul agent client 配置目录

# 创建 consul client 配置目录
$ mkdir -pv /data/tmp/{consul-cli-0,consul-cli-0.d}


2. 创建一个服务配置文件

服务名叫 web,运行在 80 端口,定义了一个 tags 方便服务查询

$ echo '{"service": {"name": "web", "tags": ["rails"], "port": 8888}}' | sudo tee /data/tmp/consul-cli-0.d/web.json


3. 启动客户端代理

# bind:绑定本地的 ip
# join:加入的服务器集群 ip 地址,默认端口 8301
# config-dir:服务配置文件的目录

$ consul agent -node=cli-0 -join 127.0.0.2 -join 127.0.0.3 -join 127.0.0.4 -bind=127.0.0.1 -data-dir=/data/tmp/consul-cli-0 -config-dir=/data/tmp/consul-cli-0.d


4. 查看运行状态

$ consul members -http-addr=127.0.0.1:4103
Node         Address         Status  Type    Build  Protocol  DC   Segment
agent-one    127.0.0.2:8301  alive   server  1.0.0  2         dc1  <all>
agent-three  127.0.0.4:8301  alive   server  1.0.0  2         dc1  <all>
agent-two    127.0.0.3:8301  alive   server  1.0.0  2         dc1  <all>
cli-0        127.0.0.1:8301  alive   client  1.0.0  2         dc1  <default>


5. 通过 DNS 查询服务

$ dig @127.0.0.1 -p 8600 web.service.consul

;; QUESTION SECTION:
;web.service.consul.        IN    A

;; ANSWER SECTION:
web.service.consul.    0    IN    A    127.0.0.1


6. 通过 DNS 查询服务地址端口

$ dig @127.0.0.1 -p 8600 web.service.consul SRV

;; QUESTION SECTION:
;web.service.consul.        IN    SRV

;; ANSWER SECTION:
web.service.consul.    0    IN    SRV    1 1 8888 cli-0.node.dc1.consul.

;; ADDITIONAL SECTION:
cli-0.node.dc1.consul.    0    IN    A    127.0.0.1
cli-0.node.dc1.consul.    0    IN    TXT    "consul-network-segment="


7. 通过 HTTP API 查询服务

$ curl http://localhost:8500/v1/catalog/service/web 2>/dev/null | python -m json.tool
[
    {
        "ID": "810d812f-f2d4-7963-33ee-2ae1491ca59e",
        "Node": "cli-0",
        "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": 8888,
        "ServiceEnableTagOverride": false,
        "CreateIndex": 17195,
        "ModifyIndex": 17195
    }
]


8. 更新服务信息

更新服务配置文件后,通过发送 SIGHUP 信号给 agent 在线更新服务信息




2.5 服务健康检查

服务健康检查检查服务的状态,防止去使用有问题的服务,有两种方法添加服务健康检查

  1. 在服务定义文件里面添加配置段
  2. 通过 HTTP API 调用

1. 通过修改服务配置文件添加服务检查

$ echo '{"service": {"name": "web", "tags": ["rails"], "port": 80,
  "check": {"script": "curl localhost >/dev/null 2>&1", "interval": "10s"}}}' \
  >/data/tmp/consul-cli-0.d/web.json


2. 用 DNS 查询服务状态,有故障的服务不会被返回

$ dig @127.0.0.1 -p 8600 web1.service.consul

;; QUESTION SECTION:
;web1.service.consul.        IN    A


3. 用 HTTP API 检查服务状态

$ curl http://localhost:8500/v1/health/state/critical 2>/dev/null | python -m json.tool
[
    {
        "Node": "cli-0",
        "CheckID": "ping google",
        "Name": "ping google",
        "Status": "critical",
        "Notes": "",
        "Output": "",
        "ServiceID": "",
        "ServiceName": "",
        "ServiceTags": [],
        "CreateIndex": 17802,
        "ModifyIndex": 17802
    },
    {
        "Node": "cli-0",
        "CheckID": "service:web1",
        "Name": "Service 'web1' check",
        "Status": "critical",
        "Notes": "",
        "Output": "",
        "ServiceID": "web1",
        "ServiceName": "web1",
        "ServiceTags": [
            "rails-2"
        ],
        "CreateIndex": 17801,
        "ModifyIndex": 17801
    }
]




2.6 kv 数据存储

Consul 提供 KV 存储方式,可以方便的存储应用需要的数据、配置信息。可以使用 HTTP API 或者 consul kv 客户端来存取数据,下面的例子使用 consul kv cli

1. 设置、读取数据

# 存储数据
$ consul kv get redis/config/minconns 1
$ consul kv put redis/config/maxconn

# 获取数据
$ consul kv get redis/config/minconns

# 获取数据详细信息
$ consul kv get -detailed redis/config/minconns
CreateIndex      13
Flags            0
Key              redis/config/minconns
LockIndex        0
ModifyIndex      13
Session          -
Value            1

# 递归获取数据
$ consul kv get -recurse

# 删除数据
$ consul kv delete redis/config/minconns

# 设置附加数据进去
$ consul kv put -flags=42 redis/config/other 1234

# 读取数据,包括附加的元数据
$ consul kv get -detailed redis/config/other
CreateIndex      18677
Flags            42
Key              redis/config/other
LockIndex        0
ModifyIndex      18677
Session          -
Value            1234

# 更新数据根据 cas 锁
$ consul kv put -cas -modify-index=33 redis/config/minconns 8
Success! Data written to: redis/config/minconns

# 更新数据根据 cas 锁,由于上一次更新 index 已经不是33,本次更新会失败
$ consul kv put -cas -modify-index=33 redis/config/minconns 8
Error! Did not write to redis/config/minconns: CAS failed




2.7 web 界面展示

consul 启动的时候指定 ui 选项,会开启一个 web 管理界面,可以查看服务的状态,consul node 节点状态,

存储和修改 kv 数据,本文例子的访问地址,信息展示如下图

1. 服务信息图


2. consul 节点图


2.8 常用命令

consul 常用下面的命令查看集群的一些状态

# 查看集群成员状态
$ consul member

# 查看集群信息
$ consul info




3. 服务使用

3.1 python 使用示例

Python 3.6.3

#!/usr/bin/env python
# coding: utf-8


import consul

def test():
    # 创建链接
    c = consul.Consul(host='127.0.0.1', port=4101)

    # 存储 kv 数据
    c.kv.put('python-zy', 'python-zy')

    # 存储,读取 kv
    index, data = c.kv.get('python-zy', index=index)
    print(index, data['Value'])


    services = c.catalog.services()
    print(services)

    service = c.catalog.service('web')
    print(service)


if __name__ == '__main__':
    test()

运行结果

b'python-zy'

{'consul': [], 'web': ['rails-1'], 'web1': ['rails-2']}

[{'ID': '810d812f-f2d4-7963-33ee-2ae1491ca59e', 'Node': 'cli-0', '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-1'], 'ServiceAddress': '', 'ServicePort': 8888, 'ServiceEnableTagOverride': False, 'CreateIndex': 17879, 'ModifyIndex': 17879}]


3.2 nodejs 使用示例

nodejs 版本 v8.9.1

const consul_module = require('consul');
const consul = consul_module({port:'4101'});

function main()
{
    try {
        var lock = consul.lock({ key: 'test-node' });

        lock.on('acquire', function () {
            console.log('lock acquired');

            // 写入,读取 kv
            consul.kv.set('test-zy', 'test-zy', (err, result)=>{
                console.log(result);
                consul.kv.get('test-zy', (err, result)=>{
                    console.log(result);
                });

            });

            // 获取访问的服务
            consul.health.service('web', (err, result) => {
                console.log(result);
            });


            lock.release();
        });

        lock.on('release', function () {
            console.log('lock released');
        });

        lock.on('error', function (err) {
            console.log('lock error:', err);
        });

        lock.on('end', function (err) {
            console.log('lock released or there was a permanent failure');
        });

        lock.acquire();
    }
    catch(e){
        console.log(e);
    }
}


if (require.main == module) {
    main();
}

运行结果

lock acquired
[ { Node:
     { ID: '810d812f-f2d4-7963-33ee-2ae1491ca59e',
       Node: 'cli-0',
       Address: '127.0.0.1',
       Datacenter: 'dc1',
       TaggedAddresses: [Object],
       Meta: [Object],
       CreateIndex: 17879,
       ModifyIndex: 17879 },
    Service:
     { ID: 'web',
       Service: 'web',
       Tags: [Array],
       Address: '',
       Port: 8888,
       EnableTagOverride: false,
       CreateIndex: 17879,
       ModifyIndex: 17879 },
    Checks: [ [Object] ] } ]
true
lock released
lock released or there was a permanent failure
{ LockIndex: 0,
  Key: 'test-zy',
  Flags: 0,
  Value: 'test-zy',
  CreateIndex: 22175,
  ModifyIndex: 22220 }
right