Consul系列:让服务Running in anywhere
引言
随着微服务概念深入人心,越来越多的解决方案选择使用微服务架构,这类架构的共同点是服务数量多,因此种类繁多的服务之间如何互相访问就变成了一个很现实的问 题。目前比较流行的分布式存储比如:Consul, etcd, ZooKeeper,如何结合当前的业务场景,利用这些工具实现更加自动化的运维化方案、简化我们的流程呢?本文主要 给大家介绍在服务发现和配置管理方面的consul工具,同时也会为大家带来一些应用场景案例参考。
关于服务发现及Consul
将应用部署到集群时,服务IP或端口是动态分配的,用户访问时不知道后端的IP及端口,在访问这些服务时,使用一定的机制动态发现服务的IP及端口就是服务发现(ServiceDiscovery)。
Consul是一个服务发现和配置管理的工具,是具有分布式、高可用、高度可扩展的服务。
Consul的特性
- 服务发现:Consul客户端可以提供其他服务的服务发现功能,使用dns和http,服务发现问题很容易被解决。
- 监控检查:使用Consul可以提供任意数量的健康检查功能,无论是服务状态(web服务器返回 http 200)还是本地节点的信息(内存利用率超过90%),这些信息都可以 由我们进行自定义设置,并由服务发现组件将服务流量从不健康的组件中移开。
- key/value存储:Consul的kv存储功能,通过http api可以很容易完成动态配置管理、协调服务、主选举、功能标记等服务。
- 跨中心部署:Consul支持多数据中心感知,可以支撑任意数量的区域无需复杂配置。
节点
-
节点类型
-
server:
服务发现、健康检查、数据一致性存储,leader选举机制
,所以奇数部署。每个数据中心至少有一个 agent 运行在 server 模式,推荐为 3 个或 5个server节点
。 -
agent:运行在 consul 集群所有节点上的核心服务,其可运行在 client 或 server 模式,通过
consul agent
命令来启动。由于所有节点必须运行 agent,指定节点运行于 client 或者 server 模式是很简单地,除非有其它的 agent 实例。所有的 agent 都能运行 DNS 或者 HTTP 接口,并负责运行时检查和保持服务 同步
。 -
client:一个 client 是一个
转发所有 RPC 到 server
的 agent 。这个 client 是相对无状态的。client 唯一执行的后台活动是加入 LAN gossip 池。这有一个 最低的资源开销并且仅消耗少量的网络带宽。
-
server:
-
Consul节点退出
- 正常退出(至关重要):节点通过信号正常关闭,接到信号的Consul会通知其他节点,正常离开后上面的服务和健康检查将被移除
- 异常退出:没有通过信号正常关闭的节点,其他节点会由于没有接到信号,认为这个节点还是正常的,也不会移除其服务和健康检查。
架构
image.pngConsul vs. Other Software
image.pngConsul部署
可以参考Consul系列:Consul Server部署及用法介绍.
集群运行
Consul可以运行client和server 两种节点模式,每个数据中心必须有一个server节点,但如果高可用,需要部署3个以上。其它Consul全部运行在client模式。客户端连接 会有一个非常轻的注册过程,运行健康检查并发给server节点。
- Consul server模板片段
#supervisor管理consul服务启动
[program:consul]
command=/usr/local/bin/consul agent -config-dir /home/项目用户/consul/conf -data-dir /home/项目用户/consul/data process_name=%(program_name)s
user=项目用户
numprocs=1
autostart=true
autorestart=true
startsecs=2
startretries=3
redirect_stderr=true
stdout_logfile_backups=10
stdout_logfile_maxbytes=300MB stdout_logfile=/home/项目用户/var/log/supervisor/s_%(program_name)s.log
#默认加载的config.json配置 {
"acl_token":"你的acl token", "bootstrap_expect":1, "client_addr":"0.0.0.0", "datacenter":"dc名称,一个数据中心统一使用一个", "enable_debug":false, "enable_syslog":false,
"log_level":"INFO", "node_name":"这里定义consul节点名称,通常建议hostname比较容易区分",, "raft_protocol":3,
"rejoin_after_leave":true,
"retry_interval":"3s", "retry_join":["server节点地址1","server节点地址2","....."], "server":true,
"ui":true
}
- Consul client模板片段
#supervisor管理consul服务启动
[program:consul]
command=/usr/local/bin/consul agent -config-dir /home/项目用户/consul/conf -data-dir /home/项目用户/consul/data process_name=%(program_name)s
user=项目用户
numprocs=1
autostart=true
autorestart=true
startsecs=2
startretries=3
redirect_stderr=true
stdout_logfile_backups=10
stdout_logfile_maxbytes=300MB
stdout_logfile=/home/项目用户/var/log/supervisor/s_%(program_name)s.log
#默认加载的config.json配置 {
"acl_token":"你的acl token", "bootstrap_expect":0, "client_addr":"0.0.0.0", "datacenter":"dc名称,一个数据中心统一使用一个", "enable_debug":false, "enable_syslog":false,
"log_level":"INFO", "node_name":"这里定义consul节点名称,通常建议hostname比较容易区分",, "raft_protocol":3,
"rejoin_after_leave":true,
"retry_interval":"3s", "retry_join":["server节点地址1","server节点地址2","....."], "server":false,
"ui":false
}
注册服务健康检查到Consul
Consul服务定义
Consul中的服务一种是可以在启动前就定义 ,提前写好定义服务的配置文件放置到 Consul配置的-config-dir指定目录中, 如果这种方式需要更新,需要使用重载命令 consul reload。另外一种是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所在宿主机挂掉,就认为这台主机上的服务也是挂的。
注册服务及健康检查
- 注消(deregister)agent 服务:-X PUT http://$IPADDR:8500/v1/agent/service/deregister/:service_id
- 注销(deregister)检查:-X PUT http://$IPADDR:8500/v1/agent/check/deregister/:check_id
- 注册服务:json文件注册方式。
{
"service": {
"name": "test",
"tags": ["test service"],
"port": 31030,
"address": "10.10.169.72",
"id": "test",
"check": {
"id": "api",
"http": "http://10.10.169.72:31030",
"interval": "3s"
}
}
}
- 注册健康检查:json文件注册方式。
# cat http.json
{
"service": {
"name": "web",
"tags": ["test web site"],
"port": 8099,
"check": {
"id": "web",
"name": "web on port 8099",
"tcp": "localhost:8099",
"interval": "10s",
"timeout": "1s"
}
}
}
注意:
- 同一个服务 每个实例的id唯一
- 同一个服务 每个检查项的id唯一
服务发现
Consul启动后,并且服务集群状态正常,就可以对外提供DNS或者HTTP API来查询服务了。
http api
- 查询所有节点的服务列表信息:-X GET http://$IPADDR:8500/v1/catalog/services
- 查询本节点注册的服务信息:-X GET http://$IPADDR:8500/v1/agent/services
- 通过agent查询异常服务:X GET http://$IPADDR:8500/v1/health/state/critical
Consul dns服务发现
Consul自带一套dns 服务,所以可以处理简单的服务发现。通过dig命令查询Consul dns中的服务解析, critical的服务不会解析。
- 使用DNSmasq转发Consul dns
- Consul默认的dns服务端口是8600,常用的dns服务默认的端口为53,所以如果使用8600,不太方便。就算可以修改Consul dns端口,但又可能会跟其它的 dns服务冲突。
-
可以使用puppet模块管理的服务DNSmasq来转发Consul dns和其它dns服务在/etc/dnsmasq.conf中添加 Consul dns地址
image.png
- 配置NetworkManager
-
在resolv.conf中添加search service.Consul和默认的dns server指定到本机,例如:
image.png -
这样即可直接在该节点使用dns访问到Consul中的服务了,而且可以使用短域名访问,例如:
image.png
-
案例分享
haproxy配合Consul dns实现服务发现
haproxy做为PaaS平台服务的负载均衡服务,对外服务;配置backend服务时,配置的是Consul中的服务域名。
这里有个坑,原来使用haproxy 1.5版本, 后端服务使用域名时,启动后只解析一次(和nginx类似),这时如果解析到的服务挂掉,访问haproxy页面时会503。查询官网得知haproxy 1.6支持了动态dns 域名解析的配置,后升级为haproxy 1.6。
下面是动态DNS解析相关的配置内容:
resolvers consuldns
nameserver dns1 127.0.0.1:53
resolve_retries 200
timeout retry 1s
hold valid 10s
frontend uq
balance leastconn
cookie JSESSIONID prefix
bind 0.0.0.0:10000 accept-proxy
capture request header Host len 128
option httplog
log-format %si:%sp\ %ci\ %ft\ %hrl\%r\ %ST\ %B\ %Tt
# 自定义ACL(路由)策略
acl host_uq-xyteam hdr_dom(host) -i test.x.example.com
use_backend test if host_test
#自定义转发的realserver地址
backend test
server test test.service.consul:10000 resolvers mydns maxconn 50000 check inter 2000 rise 2 fall 100
用户访问haproxy就会动态解析访问后端Consul中的服务。
基于Consul dns,实现推送服务的高可用切换
推送的unipush服务最初是基于常见的haproxy来做高可用的及入口的统一。为了减轻架构复杂度和更加的自动化,实现自动的阔缩容,通过将推送服务注册到Consul来做健康检查,并且去掉haproxy使用consul dns作为轻量级的代理。配置如下:
# cat service.json
{
"service": {
"name": "test_recv",
"port": 30121,
"check": {
"id": "up",
"name": "up on port 30121",
"tcp": "42.186.52.150:30121",
"interval": "60s",
"timeout": "30s"
}
}
}
正常情况该域名会解析到所有的test服务地址,如果一旦发生个别服务异常、节点故障等情况,dns会立即将故障服务节点踢出解析。
root@localhost:~$ dig @127.0.0.1 -p 53 test_recv.service.consul
; <<>> DiG 9.8.4-rpz2+rl005.12-P1 <<>> @127.0.0.1 -p 53 test_recv.service.consul ; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16206
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;test_recv.service.consul. IN A
;; ANSWER SECTION:
test_recv.service.consul. 0 IN A 10.192.4.163
test_recv.service.consul. 0 IN A 10.192.4.150
test_recv.service.consul. 0 IN A 10.192.4.240
;; Query time: 3 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sat Nov 17 12:20:29 2018
;; MSG SIZE rcvd: 93
基于Consul template,实现服务配置文件动态渲染
日常运维中由于各种情况,我们少不了会对各类服务的配置文件进行变更。例如服务迁移、增加配置参数项等。利用consul,我们可以使用K/V、services等等集群内的数据信息渲染我们的配置文件内容。 实时监听unipush_recv这个service的变化,一旦出现变化,立即触发渲染,例如新增、故障等等。
实时监听unipush_recv这个service的变化,一旦出现变化,立即触发渲染,例如新增、故障等等。
{{range service "unipush_recv"}} server {{.Node}} {{.Address}}:{{.Port}} maxconn 50000 check inter 2s addr {{.Address}} port {{.Port}}{{end}}
listen niepush_4
....
渲染后的内容,会将定义的变量用对应的service信息进行填充。
server test-up02 10.192.4.240:30121 maxconn 50000 check inter 2s addr 10.192.4.240 port 30121
server test-up04 10.192.4.150:30121 maxconn 50000 check inter 2s addr 10.192.4.150 port 30121
server test-up06 10.192.4.147:30121 maxconn 50000 check inter 2s addr 10.192.4.147 port 30121
......
最后在渲染后指定后续的处理逻辑:reload重载。
[program:consul-template]
command=/usr/local/bin/consul-template -consul-addr 127.0.0.1:8500 -template "/home/test/haproxy/conf/test_recv.ctmpl:/home/test/haproxy/conf/test_recv.conf:haproxy reload"
process_name=%(program_name)s
user=test
numprocs=1
autostart=true
autorestart=true
startsecs=2
startretries=3
redirect_stderr=true
stdout_logfile_backups=10
stdout_logfile_maxbytes=300MB
stdout_logfile=/home/test/var/log/supervisor/s_%(program_name)s.log stderr_logfile=/home/test/var/log/supervisor/s_%(program_name)s.log
同样不仅限于service,包括例如可以使用K/V的渲染,利用WEB UI界面可修改的K/V的特性,提供产品同学自助的文件变更&服务更新生效的功能。根据不同的场景、利用 consul的watch、template、check、service等功能组合出业务更加自动化的运维方案。
Consul监控
Consul开放了API可以直接获取Consul集群性能数据,可以脚本化拿到后进行渲染,效果如下:
image.png
Consul告警
Consul的服务相关问题都会有日志记录,所有重要的一点就是做日志监控,并且将日志中的error信息进行告警,效果如下:
image.png
访问安全控制
WEB UI
web ui一般采用apache+web ui的模式,从apache这里设置用户密码认证&ssl证书,再转发到一个web ui接口,这个web ui接口进行白名单限制。
image.png
API
采用防火墙限制端口(8300/8301/8302)处理.
Consul实践遇到的坑
调用deregistry接口删除Consul服务后,诡异的出现了
初期进行http api 调试测试时,注册了一些服务到Consul, 后来想删除就调用,ip:8500/v1/catalog/deregister 接口进行删除,调用该接口返回成功。但过一会儿去web ui查询, 发现刚才删除的服务还在,后来查询官方文档,原来server端catalog和 agent端都有deregister接口,我们的服务都是通过agent注册的,所以在catalog 删除不 生效,只能通过agent的 deregistry接口来删除。
总的来说目前已经尝试的有两种解决方案:
- 在新的另外的节点注册一个相同的service,包括id相同进行覆盖。
- 在注册该servive的agent上调用deregister删除。
清理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自动注册的坑
推送服务这边通常注册时候,为了和cmdb信息统一,registrator 默认的注册规则是服务名注册为 Consul中的service id。service和check id如果是不同的端口服务,不 能一样,否则在Consul同一个节点上就有可能同一个服务被注册多次,实际上是同 个服务不同实例,影响例如服务未注册上,检查的是其它进程端口等。不同的节点上的 service id或check id可以相同,同一台节点上的service的id不能一样,统一check之间的id也不能一样。
同一个节点设置为相同的id:
image.png
同一个节点设置为不同的id:
image.png
Consul agent节点异常
部署客户环境时遇到一个诡异的问题,有两个节点总是异常,健康检查这两个节点时好时坏,大部分时间是坏的,少部分时间显示正常状态。
排查问题时,发现主机之间响应时间很快,Consul server 与Consul agnet 之间的Consul端口都是可以通的。查询Consul server日志,看到 server 日志中有error:
image.png
以为是有端口漏开放了,telnet 测试后,发现Consul server到agent都是通的,谷歌下这个错误,发现有可能是因为没有开通udp端口导致的,后来又使用nmap测试 Consul相关的udp端口是不是开放的,测试后发现udp 果然没有开通。于是就向客户申请开通相应的udp端口,开通后,再进行测试,发现问题还是存在。
这就不得不再想其它办法,后来又查询异常的Consul agent节点日志发现,发现Consul agent 也报类似的错误,乍一看跟server端的日志是一样的,但仔细研究发现关键问 题,异常节点的agent报了很多访问其它agent节点8301端口的 time out的日志:
image.png
才发现agent不仅访问server, 还需要访问同级的其它agent 8301端口进行健康检查,后来开放8301所有Consul节点的端口访问后才正常。
比起另外两个同样流行的分布式存储etcd和ZooKeeper, Consul从设计上就考虑到了很多服务发现的需求, 比如说健康检查, 服务注册, DNS等。所以基于Consul来实现服
务发现的功能还是有很多的想象空间的。
更多思考
Q1:Consul, etcd, ZooKeeper能比较一下吗?为什么没有选用后两者呢?
A1:ZooKeeper 处理数据 不支持 resf api , 需要操作的时候比较繁琐,功能也比较单一,etcd 服务功能也相对单一,需要依赖其它 组件 配合使用Consul 本身自
Q2:就服务发现和zk相比,会有多大的延迟?
A2: 就服务发现来说,Consul 支持 api 和 dns 两个方式获取 服务, dns 解析基本无消耗。 api 操作的话,具体的数据我没有进行严格测试,但zk是java服务本身 需要的内存比较多,java gc 垃圾回收也会影响一些性能。Consul 是go 语言开发性能会有一些优势。
Q3: 这里问一个不是很相关的问题,怎么看待Docker和微服务更配这样的说法?
A3: 以前是所有功能集成到一个服务中,微服务本身需要拆分服务,需要部署、更新、维护的服务数量会几何增加,如果是传统的方式运维的话,操作起来就是个挑
战,Docker化之后,部署、迁移、交付都会变简单。
Q4:Consul最大承受多大规模,有瓶颈吗?
A4: 我没有针对 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 的压力 对比业务来说可以忽略不计。