当我们聊Consul服务发现的时候我们在聊什么

本文源自3月30『高效开发运维』微信群的在线分享,转载请在文章开头注明来自『高效开发运维』公众号。加群学习请关注『高效开发运维』公众号,并点击菜单中的“加群学习”或直接回复“加群”。

随着微服务概念深入人心,越来越多的解决方案选择使用微服务架构,这类架构的共同点是服务数量多,因此种类繁多的服务之间如何互相访问就变成了一个很现实的问题。目前比较流行的分布式存储有:Consul, etcd, ZooKeeper。今天主要分享下如何基于Consul来实现服务发现。

关于服务发现及Consul  

将应用部署到集群时,服务IP或端口是动态分配的,用户访问时不知道后端的IP及端口在访问这些服务时,使用一定的机制动态发现服务的IP及端口就是服务发现(Service Discovery)。

Consul是一个服务发现和配置管理的工具,是具有分布式、高可用、高度可扩展的服务。

Consul的特性  
   服务发现

Consul客户端可以提供其他服务的服务发现功能,使用dns和http,服务发现问题很容易被解决。

   监控检查

使用Consul可以提供任意数量的健康检查功能,无论是服务状态(web服务器返回 http 200)还是本地节点的信息(内存利用率超过90%),这些信息都可以由操作员进行设置,并由服务发现组件将服务流量从不健康的组件中移开。

   key/value存储

Consul的kv存储功能,通过http api可以很容易完成动态配置管理、协调服务、主选举、功能标记等服务。

   跨中心部署

Consul支持多数据中心感知,可以支撑任意数量的区域无需复杂配置。

节点  
   节点类型
  • server

    服务发现、健康检查、数据一致性存储,leader选举机制,所以奇数部署

  • agent

    健康检查

   Consul节点退出
  • 正常退出(至关重要)

    节点通过信号正常关闭,接到信号的Consul会通知其他节点,正常离开后上面的服务和健康检查将被移除

  • 异常退出

    没有通过信号正常关闭的节点,其他节点会由于没有接到信号,认为这个节点还是正常的,也不会移除其服务和健康检查。本文主要分享服务发现和健康检查两个特性。

ansible批量部署Consul  

为了更加方便快捷的部署,采用Consul进行docker化的方式实现,Consul本身安装很简单,只安装二进制的包即可使用。首先从组件下载页找到合适系统的二进制包下载,下载后将二进制的包拷贝安装在对应的可被执行的目录里,如:~/bin 或者 /usr/local/bin。Consul 还有web_ui,需要单独下载,并在启动指定目录即可。

   dockerfile

当我们聊Consul服务发现的时候我们在聊什么

   集群运行

Consul可以运行client和server 两种节点模式,每个数据中心必须有一个server节点,但如果高可用,需要部署3个以上。其它Consul全部运行在client模式。客户端连接会有一个非常轻的注册过程,运行健康检查并发给server节点。client运行在每个节点上。

ansible批量部署Consul server (agent与server类似)  
   Consul server ansible模板片段

当我们聊Consul服务发现的时候我们在聊什么

   Consul agent ansible模板片段

当我们聊Consul服务发现的时候我们在聊什么

上面启动的信息中每台主机启动的参数不同的主要是IP,所以使用ansbile的时候就用到了一个小技巧,在ansible的hosts配置文件中给每条主机信息添加一个变量 ansible_ssh_host,在ansible运行中直接引用hosts配置中的变量,其它相对来说比较固定的参数,填写到ansible的全局变量文件ansible/inventory/group_vars/all中,或执行playbook时传入就可以了。

   ansible hosts配置示例

当我们聊Consul服务发现的时候我们在聊什么

   base-Consul-master ansible role 片段

当我们聊Consul服务发现的时候我们在聊什么

   base-Consul-master playbook

当我们聊Consul服务发现的时候我们在聊什么

   调用playbook的执行脚本

当我们聊Consul服务发现的时候我们在聊什么

调用playbook是可以指定主机或主机组

   部署Consul master

当我们聊Consul服务发现的时候我们在聊什么

   指定ansible role中的tag 执行任务

当我们聊Consul服务发现的时候我们在聊什么

ansible批量注册服务健康检查到Consul  
Consul服务定义  

Consul中的服务可以在启动前就定义 ,提前写好定义服务的配置文件放置到 Consul配置目录Consul.d中, 如果这种方式需要更新,则需要重启或发送SIGHUP信号给Consul ,不太方便操作,因此推荐使用Consul api的方式来注册服务。注册服务的信息主要包含id, name, port, address, check每个服务可以由多个ip可以有多个端口的服务组成。需要注意的是:每个服务可以有多个check,check可以是端口检查、http检查、脚本检查。其中检查脚本通常是*做任何事情,以确定检查的状态。唯一的限制是,该退出代码都必须遵守此约定:

  • Exit code 0 - 检查结果是 passing

  • Exit code 1 - 检查结果是 warning

  • 其它 Exit code - 检查结果是 failing

注册服务通常是通过agent来注册,因为服务的健康检查与所在Consul agent是绑定的,如果 Consul agent所有宿主机挂掉,就认为这台主机上的服务也是挂的。

Consul健康检查参考:https://www.Consul.io/docs/agent/checks.html

ansible注册服务及健康检查到Consul agent模板reg_Consul.sh片段  

当我们聊Consul服务发现的时候我们在聊什么

注册的服务有问题,需要删除调用deregistry接口  

删除一个服务

当我们聊Consul服务发现的时候我们在聊什么

删除一个检查

当我们聊Consul服务发现的时候我们在聊什么

agent api 参考:https://www.Consul.io/docs/agent/http/agent.html注意:

  • 同一个服务 每个实例的id唯一

  • 同一个服务 每个检查项的id唯一

ansible批量注册服务到Consul  

当我们聊Consul服务发现的时候我们在聊什么

服务发现  

Consul启动后,并且服务集群状态正常,就可以对外提供DNS或者HTTP API来查询服务了。

http api  
   通过Consul server查询服务列表信息

当我们聊Consul服务发现的时候我们在聊什么

   通过Consul server查询单个服务信息

当我们聊Consul服务发现的时候我们在聊什么

   通过agent查询服务服务

当我们聊Consul服务发现的时候我们在聊什么

   只查询健康的服务

(不加passing参数会包含warning的服务,不包含critical的服务)

当我们聊Consul服务发现的时候我们在聊什么

agent操作api参考 https://www.consul.io/docs/agent/http/agent.html

Consul dns服务发现  

Consul自带一套dns 服务,所以可以处理简单的服务发现。通过dig命令查询Consul dns中的服务解析, critical的服务不会解析

当我们聊Consul服务发现的时候我们在聊什么

   使用dnsmasp转发Consul dns

Consul默认的dns服务端口是8600,常用的dns服务默认的端口为53,所以如果使用8600,不太方便,而且Consul 0.7.0版本后才支持修改默认Consul dns端口,就算可以修改Consul dns端口,但又可能会跟其它的dns服务冲突。

可以使用centos7自带的服务dnsmasp来转发Consul dns和其它dns服务在/etc/dnsmasq.d/dns-hosts.conf中添加 Consul dns地址

当我们聊Consul服务发现的时候我们在聊什么

配置NetworkManager  

当我们聊Consul服务发现的时候我们在聊什么

在resolv.conf中添加search service.Consul和默认的dns server指定到本机,

当我们聊Consul服务发现的时候我们在聊什么

这样即可直接在宿主机使用dns访问到Consul中的服务了,而且可以使用短域名访问,例如:

当我们聊Consul服务发现的时候我们在聊什么

使用registrator实现自动注册服务  

registrator通过在容器启动时检查容器来自动注册和注销任何Docker容器的服务。registrator支持自动注册的服务包括Consul,etcd和SkyDNS 2github地址:https://github.com/gliderlabs/registrator

直接使用官方镜像即可。

docker run方法

当我们聊Consul服务发现的时候我们在聊什么

服务自动注册的id name check等需要在被注册的服务启动环境变量指定。例如:

docker run -d --net host -p 5014:5014 -e SERVICE_5014_ID=cadivisor  -e SERVICE_5014_NAME=cadivisor cadivisor

注意:使用Consul做服务发现一般为host模式启动的服务,端口是固定的。由于端口问题,一台宿主机也只部署一个实例。host模式一般不添加 -p 或 --publish参数,但registrator 的默认注册规则,是有--publish都会自动注册到Consul, 所以host 模式 就算是不需要端口映射,也需要添加--publish参考指定端口,这样registrator 就会把服务注册到Consul了。

参考 :https://github.com/gliderlabs/registrator/blob/master/docs/user/backends.md

haproxy配合Consul dns实现服务发现  

haproxy做为PaaS平台服务的负载均衡服务,对外服务;配置backend服务时,配置的是Consul中的服务域名。

这里有个坑,原来使用haproxy 1.5版本, 后端服务使用域名时,启动后只解析一次(和nginx类似),这时如果解析到的服务挂掉,访问haproxy页面时会503。查询官网得知haproxy 1.6支持了动态dns 域名解析的配置,后升级为haproxy 1.6。

下面是动态DNS解析相关的配置内容:

当我们聊Consul服务发现的时候我们在聊什么

用户访问haproxy就会动态解析访问后端Consul中的服务。

基于Consul dns,实现mysql高可用切换  

数人云最初的Mysql主从切换是基于 haproxy+keepalived 来做高可用的。这种机制下,Mysql主从切换的简单需求需要引入2个开源组件,架构上也非常复杂。为了减轻架构复杂度和可维护性,将主从切换改为了使用Consul进行主从切换。Mysql是安装了两台,一主一从,设置权限时,主是读写的,从是普通用户只读权限。通过将服务注册到Consul来做健康检查,Mysql Master和Slave注册四个服务到Consul, 如下代码:

当我们聊Consul服务发现的时候我们在聊什么

正常情况mysql-vip.service.Consul解析到 主mysql ip。这里分两种情况切换到从解析:— 主Mysql挂掉后,ip解析到从mysql ip,mysql-vip 切换到从Mysql IP 。— 主库的Consul 挂掉也会导致服务无法解析,即使 mysql-master正常,如果Consul挂掉也会被**切换。

请注意:因为Mysql从是只读的,相当于降级服务。

接下来再分享一下Consul实践中遇到的一些坑。

Consul实践遇到的坑  
调用deregistry接口删除Consul服务后,诡异的出现了  

初期进行http api 调试测试时,注册了一些服务到Consul, 后来想删除就调用,ip:8500/v1/catalog/deregister 接口进行删除,调用该接口返回成功。但过一会儿去web ui查询, 发现刚才删除的服务还在,后来查询官方文档,原来server端catalog和 agent端都有deregister接口,我们的服务都是通过agent注册的,所以在catalog 删除不生效,只能通过agent的 deregistry接口来删除。

清理Consul持久化数据异常  

测试Consul server服务的高可用,先停止其中一台Consul server节点的容器,并清理Consul 的持久化数据,其它服务的Consul节点正常,达到预期结果,后来尝试恢复这个Consul server,恢复后发现从Consul ui查发现 少了一些服务节点,查询过后,正好是清理数据的这台Consul server上的服务在 Consul 中消失了,因此又重新在这个节点进行相应的服务注册才真正恢复正常。

健康检查状态码的坑  

健康检查脚本中使用grep检查结果内容,如果匹配则正常,不匹配为异常,例如: echo result|grep ok,有一次,一个服务节点异常,grep也没有匹配,当时认为这样就不会被解析了,后来发现这个服务ip还是会被解析,查询问题发现grep 不匹配的error code 为1 ,code 1 只是warning ,还是会被解析,所以修改检查脚本内容,如果不匹配,手动返回error code 2,之后这个异常服务才不解析。

使用registrator自动注册的坑  

在数人云的PaaS平台发布的服务,一般服务容器名称都是随机的,registrator 默认的自动注册规则是把容器名注册为 Consul中的service id,这样在Consul就有可能同一个服务被注册多次,实际上是同 个服务不同实例。所以需要在启动容器的环境变量中指定SERVICE_xxx_NAME ,这样registrator注册的时候就不会同是有多个相同的服务但server name 的不同 ,同理,如果需要consul 中的 service id 保持 致,则需要启动容 时指定SERVICE_xxx_ID。

当我们聊Consul服务发现的时候我们在聊什么

registrator 默认 自动注册的服务在consul中 带健康检查的,所以容器实例挂掉,consul的  页面上是看不 出的,会导致consul dns解析到异常的容器实例 上,所以需要在启动容 时需 要添加类似下面的环境变量。

Consul agent节点异常  

部署客户环境时遇到一个诡异的问题,有两个节点总是异常,健康检查这两个节点时好时坏,大部分时间是坏的,少部分时间显示正常状态。

排查问题时,发现主机之间响应时间很快,Consul server 与Consul agnet 之间的Consul端口都是可以通的。

查询Consul server日志,看到 server 日志中有error

当我们聊Consul服务发现的时候我们在聊什么

以为是有端口漏开放了,telnet 测试后,发现Consul server到agent都是通的,谷歌下这个错误,发现有可能是因为没有开通udp端口导致的,后来又使用nmap测试Consul相关的udp端口是不是开放的,测试后发现udp 果然没有开通。于是就向客户申请开通相应的udp端口,开通后,再进行测试,发现问题还是存在。

这就不得不再想其它办法,后来又查询异常的Consul agent节点日志发现,发现Consul agent 也报类似的错误,乍一看跟server端的日志是一样的,但仔细研究发现关键问题,异常节点的agent报了很多访问其它agent节点8301端口的 time out的日志:

当我们聊Consul服务发现的时候我们在聊什么才发现agent不仅访问server, 还需要访问同级的其它agent 8301端口进行健康检查,后来开放8301所有Consul节点的端口访问后才正常。

比起另外两个同样流行的分布式存储etcd和ZooKeeper, Consul从设计上就考虑到了很多服务发现的需求, 比如说健康检查, 服务注册, DNS等。所以基于Consul来实现服务发现的功能还是有很多的想象空间的。

QA环节  

Q1:感谢老师!Consul, etcd, ZooKeeper能比较一下吗?为什么没有选用后两者呢?

A1:ZooKeeper 处理数据 不支持 resf api , 需要操作的时候比较繁琐,功能也比较单一,etcd 服务功能也相对单一,需要依赖其它 组件 配合使用Consul 本身自带服务发现、健康检查 、dns解析 功能相对来说强大一些,不需要依赖其它的组件就可能实现很多功能

Q2:就服务发现和zk相比,会有多大的延迟?

A2:  就服务发现来说,Consul 支持 api 和 dns 两个方式获取 服务, dns 解析基本无消耗。 api 操作的话,具体的数据我没有进行严格测试,但zk是java服务本身需要的内存比较多,java gc 垃圾回收也会影响一些性能。Consul 是go 语言开发性能会有一些优势。

Q3: 这里问一个不是很相关的问题,老师怎么看待Docker和微服务更配这样的说法?

A3: 以前是所有功能集成到一个服务中,微服务本身需要拆分服务,需要部署、更新、维护的服务数量会几何增加,如果是传统的方式运维的话,操作起来就是个挑战,Docker化之后,部署、迁移、交付都会变简单。

Q4:Consul最大承受多大规模,有瓶颈吗?

Q4: 我没有针对 Consul服务 做过压力测试,我们一般是会对服务进行压力测试,就现在我们部署的集群规模而言,支持几百来的Consul 节点是没有问题的。理论上可以更多。

Q5:内部的web服务怎么注册?用后台脚本周期性地检测web服务正常后,再请求接口注册服务吗?失败取消

A5: 如果 使用了 registrator ,docker服务启动时,添加相应的环境变量,就会自动注册到Consul,也会自动删除,不过registrator偶尔异常情况会有不同步的现象,我们在 registrator 启动时添加同步参数定时同步,如果没有使用 registrator ,就需要自己处理注册和删除的逻辑,例如我们使用marathon /mesos 做为调试的核心基础组件,可以监听marathon 的事件获取 容器实例 启动在哪,删除了哪些,就可以相应对 Consul做操作Consul 也支持异常服务定时清理,在注册添加时添加DeregisterCriticalServiceAfter 参数 就可以

Q6:“每个数据中心必须有一个server节点,但如果高可用,需要部署3个以上”为啥一定要三个以上

A6: Consul  server 节点也分为 leader 和 follower 节点,leader 选举一般为基数选举,1个就不是高可用了,所以最少3个,遵循2N+1 原则,支持宕机  N 个节点。

Q7: Consul能够支撑多大的高并发系统?

A7: 这个问题跟上面的问题稍有些重合,其实高并发是指的业务,业务一般在 启动、停止、迁移 、或业务之间相互访问时 Consul 才会有些数据交互 ,Consul的健康检查 和 访问都是分摊到每个宿主机上, Consul 的压力 对比业务来说可以忽略不计。

作者介绍

刘金烨,数人云运维工程师。早期做 Java 开发,后转职为运维开发,曾就职于仙掌软件、金山西山居,现就职于数人云。负责数人云平台 Mesos/Marathon/Docker 环境维护、运维自动化建设。


当我们聊Consul服务发现的时候我们在聊什么