微服务注册发现配置中心-consul

17,892 阅读13分钟

Consul详解

近期在微服务业务中用的注册中心,在此简单记录下以备后用。

一 概述

1.1 概念

Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。Consul 是分布式的、高可用的、 可横向扩展的。

1.2 特征

  • 服务发现: Consul 提供了通过 DNS 或者 HTTP 接口的方式来注册服务和发现服务。一些外部的服务通过 Consul 很容易的找到它所依赖的服务。
  • 健康检测: Consul 的 Client 提供了健康检查的机制,可以通过用来避免流量被转发到有故障的服务上。
  • Key/Value 存储: 应用程序可以根据自己的需要使用 Consul 提供的 Key/Value 存储。 Consul 提供了简单易用的 HTTP 接口,结合其他工具可以实现动态配置、功能标记、领袖选举等等功能。
  • 多数据中心: Consul 支持开箱即用的多数据中心. 这意味着用户不需要担心需要建立额外的抽象层让业务扩展到多个区域。

1.3 架构图及解析

1.3.1 内部架构及原理

1.3.1.1架构图

图片描述

1.2.1.2 图解

首先Consul支持多数据中心,在上图中有两个DataCenter,他们通过Internet互联,同时请注意为了提高通信效率,只有Server节点才加入跨数据中心的通信。

在单个数据中心中,Consul分为Client和Server两种节点(所有的节点也被称为Agent),Server节点保存数据,Client负责健康检查及转发数据请求到Server;Server节点有一个Leader和多个Follower,Leader节点会将数据同步到Follower,Server的数量推荐是3个或者5个,在Leader挂掉的时候会启动选举机制产生一个新的Leader。

集群内的Consul节点通过gossip协议(流言协议)维护成员关系,也就是说某个节点了解集群内现在还有哪些节点,这些节点是Client还是Server。单个数据中心的流言协议同时使用TCP和UDP通信,并且都使用8301端口。跨数据中心的流言协议也同时使用TCP和UDP通信,端口使用8302。

集群内数据的读写请求既可以直接发到Server,也可以通过Client使用RPC转发到Server,请求最终会到达Leader节点,在允许数据轻微陈旧的情况下,读请求也可以在普通的Server节点完成,集群内数据的读写和复制都是通过TCP的8300端口完成。

Consul 集群间使用了 Gossip 协议通信和 raft 一致性算法

  • Gossip —— Gossip protocol 也叫 Epidemic Protocol (流行病协议),实际上它还有很多别名,比如:“流言算法”、“疫情传播算法”等。 这个协议的作用就像其名字表示的意思一样,非常容易理解,它的方式其实在我们日常生活中也很常见,比如电脑病毒的传播,森林大火,细胞扩散等等。
  • Client —— 一个 Client 是一个转发所有 RPC 到 server 的代理。这个 client 是相对无状态的。client 唯一执行的后台活动是加入 LAN gossip 池。这有一个最低的资源开销并且仅消耗少量的网络带宽。
  • Server —— 一个 server 是一个有一组扩展功能的代理,这些功能包括参与 Raft 选举,维护集群状态,响应 RPC 查询,与其他数据中心交互 WAN gossip 和转发查询给 leader 或者远程数据中心。
  • DataCenter —— 虽然数据中心的定义是显而易见的,但是有一些细微的细节必须考虑。例如,在 EC2 中,多个可用区域被认为组成一个数据中心。我们定义数据中心为一个私有的,低延迟和高带宽的一个网络环境。这不包括访问公共网络,但是对于我们而言,同一个 EC2 中的多个可用区域可以被认为是一个数据中心的一部分。
  • Consensus —— 一致性,使用 Consensus 来表明就 leader 选举和事务的顺序达成一致。为了以容错方式达成一致,一般有超过半数一致则可以认为整体一致。Consul 使用 Raft 实现一致性,进行 leader 选举,在 consul 中的使用 bootstrap 时,可以进行自选,其他 server 加入进来后 bootstrap 就可以取消。
  • LAN Gossip —— 它包含所有位于同一个局域网或者数据中心的所有节点。
  • WAN Gossip —— 它只包含 Server。这些 server 主要分布在不同的数据中心并且通常通过因特网或者广域网通信。
  • RPC——远程过程调用。这是一个允许 client 请求 server 的请求/响应机制。

1.3.2 Consul服务发现原理

1.3.2.1 原理图

图片描述

1.3.2.2 图解

首先需要有一个正常的Consul集群,有Server,有Leader。这里在服务器Server1、Server2、Server3上分别部署了Consul Server,假设他们选举了Server2上的Consul Server节点为Leader。这些服务器上最好只部署Consul程序,以尽量维护Consul Server的稳定。

然后在服务器Server4和Server5上通过Consul Client分别注册Service A、B、C,这里每个Service分别部署在了两个服务器上,这样可以避免Service的单点问题。服务注册到Consul可以通过HTTP API(8500端口)的方式,也可以通过Consul配置文件的方式。Consul Client可以认为是无状态的,它将注册信息通过RPC转发到Consul Server,服务信息保存在Server的各个节点中,并且通过Raft实现了强一致性。

最后在服务器Server6中Program D需要访问Service B,这时候Program D首先访问本机Consul Client提供的HTTP API,本机Client会将请求转发到Consul Server,Consul Server查询到Service B当前的信息返回,最终Program D拿到了Service B的所有部署的IP和端口,然后就可以选择Service B的其中一个部署并向其发起请求了。如果服务发现采用的是DNS方式,则Program D中直接使用Service B的服务发现域名,域名解析请求首先到达本机DNS代理,然后转发到本机Consul Client,本机Client会将请求转发到Consul Server,Consul Server查询到Service B当前的信息返回,最终Program D拿到了Service B的某个部署的IP和端口。

图中描述的部署架构笔者认为是最普适最简单的方案,从某些默认配置或设计上看也是官方希望使用者采用的方案,比如8500端口默认监听127.0.0.1,当然有些同学不赞同,后边会提到其他方案。

1.4 为什么使用服务发现

防止硬编码、容灾、水平扩缩容、提高运维效率等等,只要你想使用服务发现总能找到合适的理由。

一般的说法是因为使用微服务架构。传统的单体架构不够灵活不能很好的适应变化,从而向微服务架构进行转换,而伴随着大量服务的出现,管理运维十分不便,于是开始搞一些自动化的策略,服务发现应运而生。所以如果需要使用服务发现,你应该有一些对服务治理的痛点。

但是引入服务发现就可能引入一些技术栈,增加系统总体的复杂度,如果你只有很少的几个服务,比如10个以下,并且业务不怎么变化,吞吐量预计也很稳定,可能就没有必要使用服务发现。

二 consul与其他框架差异

名称 优点 缺点 接口 一致性算法
zookeeper 1.功能强大,不仅仅只是服务发现 2.提供 watcher 机制能实时获取服务提供者的状态 3.dubbo 等框架支持 1.没有健康检查 2.需在服务中集成 sdk,复杂度高 3.不支持多数据中心 sdk Paxos
consul 1.简单易用,不需要集成 sdk 2.自带健康检查 3.支持多数据中心 4.提供 web 管理界面 1.不能实时获取服务信息的变化通知 http/dns Raft
etcd 1.简单易用,不需要集成 sdk 2.可配置性强 1.没有健康检查 2.需配合第三方工具一起完成服务发现 3.不支持多数据中心 http Raft

三 安装部署

3.1 物理服务器安装部署

3.1.1 linux安装

wget https://releases.hashicorp.com/consul/1.5.1/consul_1.5.1_linux_amd64.zip
unzip consul_0.8.1_linux_amd64.zip
mv consul /usr/local/bin/

3.1.2 superviosr启动

  • 配置
mkdir -p /data/consul/{data,logs}

cat < /etc/supervisord.d/consul.conf >EOF
[program:consul]
command=consul agent -server -bootstrap-expect 3 -data-dir /data/consul/data -bind=172.16.100.2 -ui -client 0.0.0.0 -advertise=172.16.100.2 -node=go2cloud-platform-test -rejoin
user=root
stdout_logfile=/data/consul/logs/consul.log
autostart=true
autorestart=true
startsecs=60
stopasgroup=true
ikillasgroup=true
startretries=1
redirect_stderr=true
EOF
  • ⚠️:启动后需要手动在其他两个节点手动加入consul join 172.16.100.2

  • 查看

[root@go2cloud_platform_pord conf.d]# supervisorctl status consul
consul                           RUNNING   pid 11838, uptime 0:13:28
  • 其他命令
# 查看集群成员
consul members

# 查看集群状态
consul info

# 帮助
consul agent -h

图片描述

3.2 Docker部署

# 拉取镜像
docker pull consul

然后就可以启动集群了,这里启动4个Consul Agent,3个Server(会选举出一个leader),1个Client。

#启动第1个Server节点,集群要求要有3个Server,将容器8500端口映射到主机8900端口,同时开启管理界面
docker run -d --name=consul1 -p 8500:8500 -e CONSUL_BIND_INTERFACE=eth0 consul agent --server=true --bootstrap-expect=3 --client=0.0.0.0 -ui
 
#启动第2个Server节点,并加入集群
docker run -d --name=consul2 -e CONSUL_BIND_INTERFACE=eth0 consul agent --server=true --client=0.0.0.0 --join 172.17.0.2
 
#启动第3个Server节点,并加入集群
docker run -d --name=consul3 -e CONSUL_BIND_INTERFACE=eth0 consul agent --server=true --client=0.0.0.0 --join 172.17.0.2
 
#启动第4个Client节点,并加入集群
docker run -d --name=consul4 -e CONSUL_BIND_INTERFACE=eth0 consul agent --server=false --client=0.0.0.0 --join 172.17.0.2

第1个启动容器的IP一般是172.17.0.2,后边启动的几个容器IP会排着来:172.17.0.3、172.17.0.4、172.17.0.5。

这些Consul节点在Docker的容器内是互通的,他们通过桥接的模式通信。但是如果主机要访问容器内的网络,需要做端口映射。在启动第一个容器时,将Consul的8500端口映射到了主机的8900端口,这样就可以方便的通过主机的浏览器查看集群信息。

图片描述

  • 配置文件方式注册服务
# 编写service.json
{
  "services": [
    {
      "id": "hello1",
      "name": "hello",
      "tags": [
        "primary"
      ],
      "address": "172.17.0.5",
      "port": 5000,
      "checks": [
        {
        "http": "http://localhost:5000/",
        "tls_skip_verify": false,
        "method": "Get",
        "interval": "10s",
        "timeout": "1s"
        }
      ]
    }
  ]
}
# 将json文件拷贝进容器内
docker cp myservice.json consul1:/consul/config
# 重载配置文件
docker exec consul1 consul reload

此时已经有了服务,只是服务不可用,consul发送给服务的请求不可达

图片描述

3.3 k8s中部署

可以自己去写yaml资源清单文件或者利用官方提供好的helm的charts来安装

  • 利用helm进行安装
# 查询helm 
[root@master opt]# helm search consul
NAME                                    CHART VERSION   APP VERSION     DESCRIPTION                                                 
apphub/consul                           5.3.3           1.6.0           Highly available and distributed service discovery and ke...
apphub/prometheus-consul-exporter       0.1.4           0.4.0           A Helm chart for the Prometheus Consul Exporter             
bitnami/consul                          5.3.3           1.6.0           Highly available and distributed service discovery and ke...
incubator/ack-consul                    0.5.0           0.5.0           Install and configure Consul on Kubernetes.                 
stable/consul                           3.8.1           1.5.3           Highly available and distributed service discovery and ke...
stable/prometheus-consul-exporter       0.1.4           0.4.0           A Helm chart for the Prometheus Consul Exporter      
# 安装consul
[root@master opt]# helm install stable/consul -n consul
NAME:   consul
LAST DEPLOYED: Fri Nov 22 09:52:52 2019
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/ConfigMap
NAME          DATA  AGE
consul-tests  1     3m4s

==> v1/Pod(related)
NAME      READY  STATUS             RESTARTS  AGE
consul-0  0/1    ContainerCreating  0         3m4s

==> v1/Secret
NAME               TYPE    DATA  AGE
consul-gossip-key  Opaque  1     3m4s

==> v1/Service
NAME       TYPE       CLUSTER-IP      EXTERNAL-IP  PORT(S)                                                                           AGE
consul     ClusterIP  None            <none>       8500/TCP,8400/TCP,8301/TCP,8301/UDP,8302/TCP,8302/UDP,8300/TCP,8600/TCP,8600/UDP  3m4s
consul-ui  NodePort   10.107.180.193  <none>       8500:30082/TCP                                                                    3m4s

==> v1beta1/PodDisruptionBudget
NAME        MIN AVAILABLE  MAX UNAVAILABLE  ALLOWED DISRUPTIONS  AGE
consul-pdb  N/A            1                0                    3m4s

==> v1beta1/StatefulSet
NAME    READY  AGE
consul  0/3    3m4s


NOTES:
1. Watch all cluster members come up.
  $ kubectl get pods --namespace=default -w
2. Test cluster health using Helm test.
  $ helm test consul
3. (Optional) Manually confirm consul cluster is healthy.
  $ CONSUL_POD=$(kubectl get pods -l='release=consul' --output=jsonpath={.items[0].metadata.name})
  $ kubectl exec $CONSUL_POD consul members --namespace=default | grep server

  • 查看运行状态

我们可以看到helm安装consul使用的是statefulset,服务使用的是NodePort方式

# 查看运行状态
[root@master opt]# kubectl get pods --show-labels -l release=consul
NAME       READY   STATUS    RESTARTS   AGE     LABELS
consul-0   1/1     Running   0          3m41s   chart=consul-3.8.1,component=consul-consul,controller-revision-hash=consul-6969c79b5c,heritage=Tiller,release=consul,statefulset.kubernetes.io/pod-name=consul-0
consul-1   1/1     Running   0          2m56s   chart=consul-3.8.1,component=consul-consul,controller-revision-hash=consul-6969c79b5c,heritage=Tiller,release=consul,statefulset.kubernetes.io/pod-name=consul-1
consul-2   1/1     Running   0          2m17s   chart=consul-3.8.1,component=consul-consul,controller-revision-hash=consul-6969c79b5c,heritage=Tiller,release=consul,statefulset.kubernetes.io/pod-name=consul-2

# 查看svc
[root@master opt]# kubectl  get svc -l release=consul
NAME        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                                                            AGE
consul      ClusterIP   None             <none>        8500/TCP,8400/TCP,8301/TCP,8301/UDP,8302/TCP,8302/UDP,8300/TCP,8600/TCP,8600/UDP   5m23s
consul-ui   NodePort    10.107.180.193   <none>        8500:30082/TCP                                                                     5m23s

# 查看server
[root@master opt]# CONSUL_POD=$(kubectl get pods -l='release=consul' --output=jsonpath={.items[0].metadata.name})
[root@master opt]# kubectl exec $CONSUL_POD consul members --namespace=default | grep server
consul-0  10.244.1.67:8301   alive   server  1.5.3  2         dc1  <all>
consul-1  10.244.2.193:8301  alive   server  1.5.3  2         dc1  <all>
consul-2  10.244.1.68:8301   alive   server  1.5.3  2         dc1  <all>
  • 网页进行查看

图片描述
图片描述

四 使用

在此演示利用python来使用consul的服务器发现与注册,已经consul的简单配置中心

4.1 服务发现与注册

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import json
import requests
from consul import Consul, Check
from random import randint


# consul 操作类
class ConsulClient():
    def __init__(self, host=None, port=None, token=None):  # 初始化,指定consul主机,端口,和token
        self.host = host  # consul 主机
        self.port = port  # consul 端口
        self.token = token
        self.consul = Consul(host=host, port=port)

    def register(self, name, service_id, local_ip, local_port, consul_health_url, tags=None, interval=None):  # 注册服务 注册服务的服务名  端口  以及 健康监测端口

        # 心跳检测url
        health_check_url = ''.join(["http://", local_ip, ":", str(local_port), consul_health_url])

        # 健康检查的ip,端口,检查时间
        http_check = Check.http(health_check_url, "10s")

        return self.consul.agent.service.register(name, service_id=service_id, address=local_ip, port=int(local_port),
                                           check=http_check, tags=tags, interval=interval)

    def deregister(self, service_id):
        # 此处有坑,源代码用的get方法是不对的,改成put,两个方法都得改
        de_result = self.consul.agent.service.deregister(service_id)
        check_result = self.consul.agent.check.deregister(service_id)

        return de_result, check_result

    def getService(self, name):  # 负债均衡获取服务实例
        self.port = str(self.port)
        url = 'http://' + self.host + ':' + self.port + '/v1/catalog/service/' + name  # 获取 相应服务下的DataCenter
        dataCenterResp = requests.get(url)
        if dataCenterResp.status_code != 200:
            raise Exception('can not connect to consul ')
        listData = json.loads(dataCenterResp.text)
        dcset = set()  # DataCenter 集合 初始化
        for service in listData:
            dcset.add(service.get('Datacenter'))
        serviceList = []  # 服务列表 初始化
        for dc in dcset:
            if self.token:
                url = 'http://' + self.host + ':' + self.port + '/v1/health/service/' + name + '?dc=' + dc + '&token=' + self.token
            else:
                url = 'http://' + self.host + ':' + self.port + '/v1/health/service/' + name + '?dc=' + dc + '&token='
            resp = requests.get(url)
            if resp.status_code != 200:
                raise Exception('can not connect to consul ')
            text = resp.text
            serviceListData = json.loads(text)

            for serv in serviceListData:
                status = serv.get('Checks')[1].get('Status')
                if status == 'passing':  # 选取成功的节点
                    address = serv.get('Service').get('Address')
                    port = serv.get('Service').get('Port')
                    serviceList.append({'port': port, 'address': address})
        if len(serviceList) == 0:
            raise Exception('no serveice can be used')
        else:
            service = serviceList[randint(0, len(serviceList) - 1)]  # 随机获取一个可用的服务实例
            return service['address'], int(service['port'])

    def getServices(self):
        return self.consul.agent.services()


if __name__ == '__main__':
    host = '10.234.2.204'
    port = '30082'
    server_name = 'myapp'
    server_id = server_name + '-8500'

    c = ConsulClient(host, port)

    # print(c.deregister(server_id))
    # print(c.register(server_name, server_id, 'x.x.x.x', 8012, '/ops-audit/health'))

    print(c.consul.agent.services())
    print(c.getService(server_name))

    from apps.jumpserver.conf import get_consul_server
    print(get_consul_server('cmp', 'SMARTOPS_API_URL'))

    # server_name2 = 'myconsulapp'
    # local_port = '8000'
    # server_id2 = server_name2 + '-' + local_port
    # print(c.register(server_name2, server_id2, '10.234.2.186', '8000', '/ops-audit/health'))

注意,需要写在服务启动的时候去注册,在利用到服务发现的地方查询可用的后端服务器来获取服务的ip及端口使用。

在服务停止的时候去注销服务。

图片描述

4.2 配置中心

  • 在consul页面Key/Value配置
def get_config(CONSUL_HOST, CONSUL_PORT, KEY_NAME):
    c = consul.Consul(CONSUL_HOST, CONSUL_PORT)
    index = None
    index, data = c.kv.get(KEY_NAME, index=index)
    return yaml.safe_load(data.get("Value").decode(encoding='utf-8'))

一般情况下需要去启一个进程一直watch consul的配置来实时更新应用中的配置,或者每次利用配置的时候去单独调用获取配置。

参考链接

www.consul.io/

elfgzp.cn/2019/03/11/…

blog.51cto.com/tianshili/1…

blog.didispace.com/consul-serv…