consul简介
这篇文档简单介绍一下consul这款软件的功能以及原理。
注: 本篇博客主要介绍简单的功能使用,在本地环境快速搭建起运行环境。如果要在产品环境部署,请参考这篇文章: https://www.digitalocean.com/community/tutorials/how-to-configure-consul-in-a-production-environment-on-ubuntu-14-04
HashiCorp的CTO在infoq上有一个分享,ppt在此(需爬墙): http://www.slideshare.net/InfoQ/consul-serviceoriented-at-scale 。极具参考价值。介绍了consul的用例,以及设计思想等等,强烈推荐一阅。看过这篇ppt以及下面的访谈记录,再看consul的文档就十分清晰了
consul是什么
consul是HashiCorp公司推出的一款开源工具,用于实现分布式系统的服务发现与配置。与其他类似产品相比,提供更“一站式”的解决方案。consul内置有KV存储,服务注册/发现,健康检查,HTTP+DNS API,Web UI等多种功能。
HashiCorp官方公布的一张consul的架构图如下:
consul的优势
- 使用 Raft 算法来保证一致性, 比复杂的 Paxos 算法更直接. 相比较而言, zookeeper 采用的是 Paxos, 而 etcd 使用的则是 Raft.
- 支持多数据中心,内外网的服务采用不同的端口进行监听。 多数据中心集群可以避免单数据中心的单点故障,而其部署则需要考虑网络延迟, 分片等情况等. zookeeper 和 etcd 均不提供多数据中心功能的支持.
- 支持健康检查. etcd 不提供此功能.
- 支持 http 和 dns 协议接口. zookeeper 的集成较为复杂, etcd 只支持 http 协议.
- 官方提供web管理界面, etcd 无此功能.
与其他同类型的产品详细对比,官方有一个比较详尽的文档: https://www.consul.io/intro/vs/zookeeper.html
简单来说,相比zookeeper,consul要更轻量,依赖轻(Go VS Java),Raft算法要比Paxos算法简单的多,而且效率高。
相比etcd,提供了更多的功能,比如DNS server,多数据中心同步,WebUI等等。
consul的简单使用
consul的使用非常简单,采用Go语言编写,编译出的文件无其他依赖。因此只需要从官方下载编译好的二进制可执行程序直接运行就可以了。
# consul
usage: consul [--version] [--help] <command> [<args>]
Available commands are:
agent Runs a Consul agent
configtest Validate config file
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
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
reload Triggers the agent to reload configuration files
rtt Estimates network round trip time between nodes
version Prints the Consul version
watch Watch for changes in Consul
consul的运行方式
consul以agent
形式运行。agent有两种模式: server
和client
。
server的作用是提供一致性的保证,存储着Raft信息。几乎可以认为server是数据存储中心,绝大多数的运行数据都保存在server。每个数据中心至少要保证一个server节点。官方建议每个数据中心保持3,5或7个server节点,以保证高可用。以server方式运行:
consul agent -server -data-dir /tmp/consul
client就比server轻量的多,基本上只提供和server交互的接口,作为访问server的代理。以client方式运行:
# 去掉-server参数就是client模式,并且要join到一个cluster中。
# 也可以使用consul join命令手工加入到一个集群中
consul agent -data-dir /tmp/consul -join X.X.X.X
以下功能演示均需要有server节点,因此使用以下命令启动server:
consul agent -server -ui -bootstrap -client=0.0.0.0 -data-dir /tmp/consul
加上-ui
参数可以启用内置的WebUI,使用浏览器访问http://localhost:8500
即可打开这个WebUI。
服务注册
服务需要先进行注册,才能实现健康检查,服务发现等这些功能。
服务注册的方法有两种,一种是写到配置文件中,启动时加载。另一种是在consul启动之后通过HTTP API动态添加。
服务注册的方法相当简单,写一个json配置文件,定义服务名即可,以定义一个rtds
服务为例:
{
"service": {
"name": "rtds"
}
}
保存为rtds.json
,然后重启consul并加载这个配置文件:
consul agent -server -ui -bootstrap -client=0.0.0.0 -data-dir /tmp/consul -config-file rtds.json
打开webui(8500端口)就可以看到这个service了。也可以通过HTTP API列出这个service:
$ curl 172.16.250.11:8500/v1/agent/services?pretty
{
"consul": {
"ID": "consul",
"Service": "consul",
"Tags": [],
"Address": "",
"Port": 8300,
"EnableTagOverride": false,
"CreateIndex": 0,
"ModifyIndex": 0
},
"rtds": {
"ID": "rtds",
"Service": "rtds",
"Tags": null,
"Address": "",
"Port": 0,
"EnableTagOverride": false,
"CreateIndex": 0,
"ModifyIndex": 0
}
}
服务动态注册的方式,向HTTP API发起PUT请求注册新服务,body部分和json配置文件格式一致:
$ curl 172.16.250.11:8500/v1/agent/services?pretty
{
"consul": {
"ID": "consul",
"Service": "consul",
"Tags": [],
"Address": "",
"Port": 8300,
"EnableTagOverride": false,
"CreateIndex": 0,
"ModifyIndex": 0
}
}
$ curl -XPUT -d '{"name":"rtds"}' 172.16.250.11:8500/v1/agent/service/register
$ curl 172.16.250.11:8500/v1/agent/services?pretty
{
"consul": {
"ID": "consul",
"Service": "consul",
"Tags": [],
"Address": "",
"Port": 8300,
"EnableTagOverride": false,
"CreateIndex": 0,
"ModifyIndex": 0
},
"rtds": {
"ID": "rtds",
"Service": "rtds",
"Tags": null,
"Address": "",
"Port": 0,
"EnableTagOverride": false,
"CreateIndex": 0,
"ModifyIndex": 0
}
}
服务发现
consul对服务发现提供两种接口: HTTP API和DNS查询。
先看下HTTP API。列出已注册的服务GET请求/v1/catalog/services
即可列出。追加?dc=<dc_name>
可以列出指定数据中心的注册服务。 用GET请求/v1/catalog/service/<service_name>
即可列出注册此服务的全部节点信息,同样可以追加?dc=
参数:
$ curl 172.16.250.11:8500/v1/catalog/service/rtds?pretty
[
{
"Node": "shifudaotest",
"Address": "172.16.250.11",
"ServiceID": "rtds",
"ServiceName": "rtds",
"ServiceTags": [],
"ServiceAddress": "",
"ServicePort": 0,
"ServiceEnableTagOverride": false,
"CreateIndex": 276,
"ModifyIndex": 277
}
]
DNS API: consul会占用一个DNS服务器端口8600
,提供DNS查询服务。每一个注册的服务,会在consul提供一条NAME.service.consul
的解析记录。用DNS查询工具就可以进行查询:
$ dig @172.16.250.11 -p 8600 rtds.service.consul # 使用dig工具查询rtds.service.consul解析记录
; <<>> DiG 9.9.5-3ubuntu0.8-Ubuntu <<>> @172.16.250.11 -p 8600 rtds.service.consul
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20558
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available
;; QUESTION SECTION:
;rtds.service.consul. IN A
;; ANSWER SECTION:
rtds.service.consul. 0 IN A 172.16.250.11
;; Query time: 47 msec
;; SERVER: 172.16.250.11#8600(172.16.250.11)
;; WHEN: Fri May 06 20:13:58 CST 2016
;; MSG SIZE rcvd: 72
可以看到rtds.service.consul有一条A记录,指向172.16.250.11。
现在把rtds服务注销掉。可以看到rtds.service.consul
已经没有任何解析记录了。
$ curl 172.16.250.11:8500/v1/agent/service/deregister/rtds # 将rtds服务注销
$ dig @172.16.250.11 -p 8600 rtds.service.consul
; <<>> DiG 9.9.5-3ubuntu0.8-Ubuntu <<>> @172.16.250.11 -p 8600 rtds.service.consul
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 62263
;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
;; WARNING: recursion requested but not available
;; QUESTION SECTION:
;rtds.service.consul. IN A
;; AUTHORITY SECTION:
consul. 0 IN SOA ns.consul. postmaster.consul. 1462537218 3600 600 86400 0
;; Query time: 5 msec
;; SERVER: 172.16.250.11#8600(172.16.250.11)
;; WHEN: Fri May 06 20:21:05 CST 2016
;; MSG SIZE rcvd: 105
健康检查
consul对注册的服务都支持健康检查。方法是在service
下面再嵌套一个check
配置即可。
{
"service": {
"name": "service_name",
"check": {
}
}
}
consul支持5种check方式:
- Script + Interval: 周期性运行某个可执行程序,根据程序退出码确定服务是否存活
- HTTP + Interval: 周期性访问某个http地址,根据返回码是否为2xx确定服务是否存活。429将警告
- TCP + Interval: 周期性发起TCP请求,根据是否成功建立连接TCP连接确定服务是否存活
- Time to Live (TTL): 类似于心跳检测。服务状态是用过周期性向HTTP接口发起PUT请求确定的。超期没有更新数据就判断服务挂了
- Docker + Interval: 通过docker的api检测docker内服务的状态
如果某个服务需要检测多项,可以使用checks
配置:
{
"checks": [
{
"id": "chk1",
"name": "mem",
"script": "/bin/check_mem",
"interval": "5s"
},
{
"id": "chk2",
"name": "/health",
"http": "http://localhost:5000/health",
"interval": "15s"
},
{
"id": "chk3",
"name": "cpu",
"script": "/bin/check_cpu",
"interval": "10s"
},
...
]
}
比如rtds会监听TCP 3130
端口,我们通过周期检测3130端口的存活性判断rtds的存活,继续修改rtds.json
如下:
{
"service": {
"name": "rtds",
"check": {
"name": "rtds TCP on port 3130",
"tcp": "localhost:3130",
"interval": "10s",
"timeout": "1s"
}
}
}
使用以下命令重启consul,加载rtds.json
配置:
consul agent -server -ui -bootstrap -client=0.0.0.0 -data-dir /tmp/consul -config-file rtds.json
也可以像服务注册那样,不使用配置文件,启动之后通过HTTP API添加服务与健康检测策略。操作方法完全一样,这里不做演示。
rtds未启动时:
$ curl 172.16.250.11:8500/v1/health/checks/rtds?pretty
[
{
"Node": "shifudaotest",
"CheckID": "service:rtds",
"Name": "Service 'rtds' check",
"Status": "critical", # 服务未启动,所以状态显示此服务挂了
"Notes": "",
"Output": "",
"ServiceID": "rtds",
"ServiceName": "rtds",
"CreateIndex": 444,
"ModifyIndex": 462
}
]
$ nslookup -port=8600 rtds.service.consul 172.16.250.11 # nslookup也是一个dns测试工具
Server: 172.16.250.11
Address: 172.16.250.11#8600
# 服务挂了,所以没有rtds.service.consul的解析记录
启动rtds,等上10秒(配置的检测周期为10秒):
$ curl 172.16.250.11:8500/v1/health/checks/rtds?pretty
[
{
"Node": "shifudaotest",
"CheckID": "service:rtds",
"Name": "Service 'rtds' check",
"Status": "passing", # 3130端口可以成功建立TCP连接,于是passing了
"Notes": "",
"Output": "TCP connect localhost:3130: Success",
"ServiceID": "rtds",
"ServiceName": "rtds",
"CreateIndex": 444,
"ModifyIndex": 499
}
]
$ nslookup -port=8600 rtds.service.consul 172.16.250.11
Server: 172.16.250.11
Address: 172.16.250.11#8600
Name: rtds.service.consul
Address: 172.16.250.11
KV存储
这个功能就没太多要说明的了,基本用法就跟redis的用法差不多。
KV的API路径是/v1/kv/<key>
,支持GET
,PUT
,DELETE
三种请求。如果key
最后跟一个/
,代表创建一级目录。
$ curl -X PUT -d 'test' http://localhost:8500/v1/kv/web/key1
true
$ curl -X PUT -d 'test' http://localhost:8500/v1/kv/web/key2?flags=42
true
$ curl -X PUT -d 'test' http://localhost:8500/v1/kv/web/sub/key3
true
$ curl http://localhost:8500/v1/kv/?recurse
[{"CreateIndex":97,"ModifyIndex":97,"Key":"web/key1","Flags":0,"Value":"dGVzdA=="},
{"CreateIndex":98,"ModifyIndex":98,"Key":"web/key2","Flags":42,"Value":"dGVzdA=="},
{"CreateIndex":99,"ModifyIndex":99,"Key":"web/sub/key3","Flags":0,"Value":"dGVzdA=="}]
需要注意的consul返回的value是base64
编码,官方的解释是为了防止出现非UTF-8字符,所以统一编码成base64。第二个范例追加了一个flags
参数,这个flags
并非是由consul内部使用,而是提供给客户端的保留字段,客户端可以自定义其他含义。 如果追加?raw
参数,将直接返回未经base64编码的Value
值。
此外,consul的KV存储还支持Check-And-Set
操作(当ModifyIndex
匹配时才进行修改键值)。详情参考官方文档: https://www.consul.io/intro/getting-started/kv.html
ACL
consul提供访问控制列表(Access Control List)功能,通过ACL可以实现权限控制。consul的ACL是Capability-based security(能力基础安全)。
也就是这种ACL是每一级路径都可以而配置单独权限,默认策略是子权限继承父权限。比如/kv/key/
的权限是deny
,那么/kv/key/sub1
这样的路径无法访问。如果再追加一条规则/kv/key/sub2
的权限是read
,那么/kv/key/sub2/key
这样的key有只读权限,其余的key均无法访问。通过带上?token=
参数验证ACL权限,ACL配置是针对每个token的。如果不带token
参数,则默认anonymous
。
启用ACL只能在配置文件中指定。创建一个config.json
文件,写入以下内容:
{
"acl_datacenter": "dc1",
"acl_default_policy": "deny",
"acl_master_token": "master"
}
对dc1
这个数据中心(默认的数据中心)启动ACL,默认的ACL规则是deny
(即不指定权限时,默认为deny),拥有管理权限的token是master
。
加载这个配置启动consul agent:
consul agent -server -ui -bootstrap -client=0.0.0.0 -data-dir /tmp/consul -config-file config.json
现在再请求kv
,发现会返回404:
curl -v '172.16.250.11:8500/v1/kv/?recurse' # -v参数可以看到返回码是404
带上master token再请求这个API,就没问题:
$ curl '172.16.250.11:8500/v1/kv/?token=master&recurse'
[{"LockIndex":0,"Key":"web/key1","Flags":0,"Value":"bW9kaWZpZWQ=","CreateIndex":633,"ModifyIndex":634}]
现在创建一个token mytoken
,对kv/web/
有只读权限,对service/rtds
有读写权限,可以这么做:
$ curl -XPUT -d @- '172.16.250.11:8500/v1/acl/create?token=master' <<EOF
{
"Type": "client",
"Name": "mytoken",
"Rules": "service \"rtds\" {\n policy = \"write\"\n }\n key \"web/\" {\n policy = \"read\"\n }"
}
EOF
{"ID":"1f8799ce-6f45-427c-f8e6-3878f9eda582"}
consul会返回一个ID
: 1f8799ce-6f45-427c-f8e6-3878f9eda582
。将这个值作为token
参数传递给HTTP API就拥有了对应的权限。
$ curl '172.16.250.11:8500/v1/kv/?recurse&token=1f8799ce-6f45-427c-f8e6-3878f9eda582&pretty'
[
{
"LockIndex": 0,
"Key": "web/key1",
"Flags": 0,
"Value": "bW9kaWZpZWQ=",
"CreateIndex": 633,
"ModifyIndex": 634
}
]
可以看到带上这个token以后,就可以读取web/key1
的值了,而之前是404。
其他功能
以上几个功能都是WebUI上可以可视化操作的功能,是最常见的功能。其他还有一些只能用API操作的功能。比如consul exec
直接调用RPC
接口,而非HTTP
接口。consul event
向消息总线发送自定义消息。consul watch
监听消息总线异步响应,甚至还支持分布式锁等等。这些功能可以详细参考官方文档,用到的时候再查阅官方文档。