Consul 原理
架构介绍
首先,从架构上,图片被两个datacenter分成了上下两部分;但这两部分又并不是完全隔离的,他们之间通过WAN GOSSIP在Internet上交互报文。因此,我们了解到consul是可以支持多个数据中心之间基于WAN来做同步的。
再看单个datacenter内部,节点被划分为两种颜色,其中红色为server,紫色为client。它们之间通过GRPC通信(主要用于业务数据)。除此之外,server和client之间,还有一条LAN GOSSIP通信,这是用于当LAN内部发生了拓扑变化时,存活的节点们能够及时感知,比如server节点down掉后,client就会触发将对应server节点从可用列表中剥离出去。
当然,server与server之间,client与client之间,client与server之间,在同一个datacenter中的所有consul agent会组成一个LAN网络(当然它们之间也可以按照区域划分segment),当LAN网中有任何角色变动,或者有用户自定义的event产生的时候,其他节点就会感知到,并触发对应的预置操作。
所有的server节点共同组成了一个集群,他们之间运行raft协议,通过共识仲裁选举出leader。所有的业务数据都通过leader写入到集群中做持久化,当有半数以上的节点存储了该数据后,server集群才会返回ACK,从而保障了数据的强一致性。当然,server数量大了之后,也会影响写数据的效率。所有的follower会跟随leader的脚步,保障其有最新的数据副本。
同一个consul agent程序,通过启动的时候指定不同的参数来运行server或client模式。这两种模式下,各自所负责的事务具体如下。
Server节点
- 参与共识仲裁(raft)
- 存储集群状态(日志存储)
- 处理查询
- 维护与周边(LAN/WAN)各节点关系
Agent节点
- 负责通过该节点注册到consul的微服务的健康检查
- 将客户端注册请求以及查询转化为对server的RPC请求
- 维护与周边(LAN/WAN)各节点关系
服务端口
端口 | 作用 |
---|---|
8300 | RPC exchanges |
8301 | LAN GOSSIP |
8302 | WAN GOSSIP |
8400 | RPC exchanges by the CLI |
8500 | Used for HTTP API and web interface |
8600 | Used for DNS server |
实现原理
纵观consul的实现,其核心在于两点:
- 集群内节点间信息的高效同步机制,其保障了拓扑变动以及控制信号的及时传递
- server集群内日志存储的强一致性
它们主要基于以下两个协议来实现:
- 使用gossip协议在集群内传播信息
- 使用raft协议来保障日志的一致性
Serf
serf是hashicorp基于GOSSIP协议来实现的一个用于分布式集群成员管理,失败检测以及编排的工具,当前最新版本为v0.8.1。
集群管理
这台机器上有两个IP地址,一个是172.20.20.10,另一个为172.20.20.10。我准备启动两个serf agent进程,分别绑定到不同的两个IP地址上,各自叫做agent-one和agent-two。
由于它们启动之后,相互之间是不知道彼此的,我通过执行serf join
来把它们组成一个LAN serf。这样它们就可以彼此检测到彼此,通过查看serf members可以看到所有的节点以及其健康状况。
1 $ serf agent -node=agent-one -bind=172.20.20.10 2 3 $ serf agent -node=agent-two -bind=172.20.20.11 4 5 $ serf join 172.20.20.11 6 7 $ serf members 8 agent-one 172.20.20.10:7946 alive 9 agent-two 172.20.20.11:7946 alive
事件响应
在前面的步骤中,我们将两个serf进程加入到了同一个LAN中,接下来我们将进行一些更加激动人心的实践。接下来,我们创建了一个脚本(handler.sh),大致内容为:当脚本被调用的时候,会打印出一些具体的信息。然后,我们在启动serf agent的时候,通过参数将该脚本传递给serf agent。这样当收该serf节点收到event时,就会调用用户指定的handler(即执行脚本)。
$ cat handler.sh #!/bin/bash echo echo "New event: ${SERF_EVENT}. Data follows..." while read line; do printf "${line}\n" done $ serf agent -log-level=debug -event-handler=handler.sh
发送自定义event
$ serf event hello-there
Event类型
serf指定了下面这些类型的event,各自的作用如下所示:
1 member-join One or more members have joined the cluster. 2 member-leave One or more members have gracefully left the cluster. 3 member-failed One or more members have failed, meaning that they did not properly respond to ping requests. 4 member-update One or more members have updated, likely to update the associated tags 5 member-reap Serf has removed one or more members from its list of members. This means a failed node exceeded the reconnect_timeout, or a left node reached the tombstone_timeout. 6 user A custom user event, covered later in this guide. 7 query A query event, covered later in this guide
Raft
由于介绍raft协议的文章已经比较多,我这里就不在详述。这里重点分析一下在consul中,raft协议运作的一些实践和日志。
节点状态变更
- 在节点数达到bootstrap-expect的数时,开始启用raft选举
- 在节点数超过bootstrap-expect数时,其他节点为follower
- 在leader被干掉后,raft如果判断到节点数依然大于等于bootstrap-expect时,重新选举
- 逐一干掉节点,当节点数少于bootstrap-expect时,raft协议不再选举,将维持先前的状态。
Raft选举日志分析
1 # 选举日志信息 (bootstrap) 2 3 ==> Starting Consul agent... 4 bootstrap_expect > 0: expecting 3 servers 5 ==> Consul agent running! 6 Version: 'v1.4.0' 7 Node ID: 'f217ca95-e83c-9a1f-9e87-3b5c1f5a82a3' 8 Node name: '42ddc7aa3bb6' 9 Datacenter: 'dc1' (Segment: '<all>') 10 Server: true (Bootstrap: false) 11 Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, gRPC: -1, DNS: 8600) 12 Cluster Addr: 172.17.0.2 (LAN: 8301, WAN: 8302) 13 Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false 14 ==> Log data will now stream in as it occurs: 15 2018/12/03 07:21:34 [INFO] raft: Initial configuration (index=0): [] 16 2018/12/03 07:21:34 [INFO] serf: EventMemberJoin: 42ddc7aa3bb6.dc1 172.17.0.2 17 2018/12/03 07:21:34 [INFO] serf: EventMemberJoin: 42ddc7aa3bb6 172.17.0.2 18 2018/12/03 07:21:34 [INFO] raft: Node at 172.17.0.2:8300 [Follower] entering Follower state (Leader: "") 19 2018/12/03 07:21:34 [INFO] consul: Adding LAN server 42ddc7aa3bb6 (Addr: tcp/172.17.0.2:8300) (DC: dc1) 20 2018/12/03 07:21:34 [INFO] consul: Handled member-join event for server "42ddc7aa3bb6.dc1" in area "wan" 21 2018/12/03 07:21:34 [INFO] agent: Started DNS server 127.0.0.1:8600 (tcp) 22 2018/12/03 07:21:34 [INFO] agent: Started DNS server 127.0.0.1:8600 (udp) 23 2018/12/03 07:21:34 [INFO] agent: Started HTTP server on 127.0.0.1:8500 (tcp) 24 2018/12/03 07:21:34 [INFO] agent: started state syncer 25 2018/12/03 07:21:34 [INFO] agent: Retry join LAN is supported for: aliyun aws azure digitalocean gce k8s os packet scaleway softlayer triton vsphere 26 2018/12/03 07:21:34 [INFO] agent: Joining LAN cluster... 27 2018/12/03 07:21:34 [INFO] agent: (LAN) joining: [172.17.0.2] 28 2018/12/03 07:21:34 [INFO] agent: (LAN) joined: 1 Err: <nil> 29 2018/12/03 07:21:34 [INFO] agent: Join LAN completed. Synced with 1 initial agents 30 # node数量没有达到,无法开始选举 31 2018/12/03 07:21:41 [ERR] agent: failed to sync remote state: No cluster leader 32 2018/12/03 07:21:43 [WARN] raft: no known peers, aborting election 33 2018/12/03 07:21:54 [INFO] serf: EventMemberJoin: 4eb2b75f454a 172.17.0.3 34 2018/12/03 07:21:54 [INFO] consul: Adding LAN server 4eb2b75f454a (Addr: tcp/172.17.0.3:8300) (DC: dc1) 35 2018/12/03 07:21:54 [INFO] serf: EventMemberJoin: 4eb2b75f454a.dc1 172.17.0.3 36 2018/12/03 07:21:54 [INFO] consul: Handled member-join event for server "4eb2b75f454a.dc1" in area "wan" 37 2018/12/03 07:21:58 [INFO] serf: EventMemberJoin: b603f61d1449 172.17.0.4 38 2018/12/03 07:21:58 [INFO] consul: Adding LAN server b603f61d1449 (Addr: tcp/172.17.0.4:8300) (DC: dc1) 39 # node数量达到,开始选举 40 2018/12/03 07:21:58 [INFO] consul: Found expected number of peers, attempting bootstrap: 172.17.0.2:8300,172.17.0.3:8300,172.17.0.4:8300 41 2018/12/03 07:21:58 [INFO] serf: EventMemberJoin: b603f61d1449.dc1 172.17.0.4 42 2018/12/03 07:21:58 [INFO] consul: Handled member-join event for server "b603f61d1449.dc1" in area "wan" 43 2018/12/03 07:22:03 [WARN] raft: Heartbeat timeout from "" reached, starting election 44 # 状态迁移 45 2018/12/03 07:22:03 [INFO] raft: Node at 172.17.0.2:8300 [Candidate] entering Candidate state in term 2 46 # 获胜 47 2018/12/03 07:22:03 [INFO] raft: Election won. Tally: 2 48 2018/12/03 07:22:03 [INFO] raft: Node at 172.17.0.2:8300 [Leader] entering Leader state 49 2018/12/03 07:22:03 [INFO] raft: Added peer 5b0b26fb-5e62-c390-0ced-b80e0f3293ef, starting replication 50 2018/12/03 07:22:03 [INFO] raft: Added peer 3844affd-9b4e-ad3d-84f3-25fb77806e7c, starting replication 51 2018/12/03 07:22:03 [INFO] consul: cluster leadership acquired 52 2018/12/03 07:22:03 [INFO] consul: New leader elected: 42ddc7aa3bb6 53 2018/12/03 07:22:03 [WARN] raft: AppendEntries to {Voter 3844affd-9b4e-ad3d-84f3-25fb77806e7c 172.17.0.4:8300} rejected, sending older logs (next: 1) 54 2018/12/03 07:22:03 [WARN] raft: AppendEntries to {Voter 5b0b26fb-5e62-c390-0ced-b80e0f3293ef 172.17.0.3:8300} rejected, sending older logs (next: 1) 55 2018/12/03 07:22:03 [INFO] raft: pipelining replication to peer {Voter 3844affd-9b4e-ad3d-84f3-25fb77806e7c 172.17.0.4:8300} 56 2018/12/03 07:22:03 [INFO] raft: pipelining replication to peer {Voter 5b0b26fb-5e62-c390-0ced-b80e0f3293ef 172.17.0.3:8300} 57 2018/12/03 07:22:03 [INFO] consul: member '42ddc7aa3bb6' joined, marking health alive 58 2018/12/03 07:22:03 [INFO] consul: member '4eb2b75f454a' joined, marking health alive 59 2018/12/03 07:22:03 [INFO] consul: member 'b603f61d1449' joined, marking health alive 60 ==> Failed to check for updates: Get https://checkpoint-api.hashicorp.com/v1/check/consul?arch=amd64&os=linux&signature=2ba01aad-86ad-32b1-2cff-dc77537fa0dd&version=1.4.0: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) 61 2018/12/03 07:22:05 [INFO] agent: Synced node info 62 2018/12/03 07:27:25 [INFO] serf: EventMemberJoin: 212149acfdcd 172.17.0.5 63 2018/12/03 07:27:25 [INFO] consul: Adding LAN server 212149acfdcd (Addr: tcp/172.17.0.5:8300) (DC: dc1) 64 # 添加的新节点为novoter角色,无法参与选举 65 2018/12/03 07:27:25 [INFO] raft: Updating configuration with AddNonvoter (45c059ba-bd5c-5a00-f7d6-e490324e7b52, 172.17.0.5:8300) to [{Suffrage:Voter ID:f217ca95-e83c-9a1f-9e87-3b5c1f5a82a3 Address:172.17.0.2:8300} {Suffrage:Voter ID:5b0b26fb-5e62-c390-0ced-b80e0f3293ef Address:172.17.0.3:8300} {Suffrage:Voter ID:3844affd-9b4e-ad3d-84f3-25fb77806e7c Address:172.17.0.4:8300} {Suffrage:Nonvoter ID:45c059ba-bd5c-5a00-f7d6-e490324e7b52 Address:172.17.0.5:8300}] 66 2018/12/03 07:27:25 [INFO] serf: EventMemberJoin: 212149acfdcd.dc1 172.17.0.5 67 2018/12/03 07:27:25 [INFO] consul: Handled member-join event for server "212149acfdcd.dc1" in area "wan" 68 2018/12/03 07:27:25 [INFO] raft: Added peer 45c059ba-bd5c-5a00-f7d6-e490324e7b52, starting replication 69 2018/12/03 07:27:25 [WARN] raft: AppendEntries to {Nonvoter 45c059ba-bd5c-5a00-f7d6-e490324e7b52 172.17.0.5:8300} rejected, sending older logs (next: 1) 70 2018/12/03 07:27:25 [INFO] consul: member '212149acfdcd' joined, marking health alive 71 2018/12/03 07:27:25 [INFO] raft: pipelining replication to peer {Nonvoter 45c059ba-bd5c-5a00-f7d6-e490324e7b52 172.17.0.5:8300} 72 2018/12/03 07:27:28 [INFO] serf: EventMemberJoin: edb64d232050 172.17.0.6 73 2018/12/03 07:27:28 [INFO] consul: Adding LAN server edb64d232050 (Addr: tcp/172.17.0.6:8300) (DC: dc1) 74 # 添加的新节点为novoter角色,无法参与选举 75 2018/12/03 07:27:28 [INFO] raft: Updating configuration with AddNonvoter (46ebd85c-5e96-f9bd-81e4-0a82d3b405c7, 172.17.0.6:8300) to [{Suffrage:Voter ID:f217ca95-e83c-9a1f-9e87-3b5c1f5a82a3 Address:172.17.0.2:8300} {Suffrage:Voter ID:5b0b26fb-5e62-c390-0ced-b80e0f3293ef Address:172.17.0.3:8300} {Suffrage:Voter ID:3844affd-9b4e-ad3d-84f3-25fb77806e7c Address:172.17.0.4:8300} {Suffrage:Nonvoter ID:45c059ba-bd5c-5a00-f7d6-e490324e7b52 Address:172.17.0.5:8300} {Suffrage:Nonvoter ID:46ebd85c-5e96-f9bd-81e4-0a82d3b405c7 Address:172.17.0.6:8300}] 76 2018/12/03 07:27:28 [INFO] serf: EventMemberJoin: edb64d232050.dc1 172.17.0.6 77 2018/12/03 07:27:28 [INFO] consul: Handled member-join event for server "edb64d232050.dc1" in area "wan" 78 2018/12/03 07:27:28 [INFO] raft: Added peer 46ebd85c-5e96-f9bd-81e4-0a82d3b405c7, starting replication 79 2018/12/03 07:27:28 [INFO] consul: member 'edb64d232050' joined, marking health alive 80 2018/12/03 07:27:28 [WARN] raft: AppendEntries to {Nonvoter 46ebd85c-5e96-f9bd-81e4-0a82d3b405c7 172.17.0.6:8300} rejected, sending older logs (next: 1) 81 2018/12/03 07:27:28 [INFO] raft: pipelining replication to peer {Nonvoter 46ebd85c-5e96-f9bd-81e4-0a82d3b405c7 172.17.0.6:8300} 82 2018/12/03 07:27:43 [INFO] autopilot: Promoting Server (ID: "45c059ba-bd5c-5a00-f7d6-e490324e7b52" Address: "172.17.0.5:8300") to voter 83 2018/12/03 07:27:43 [INFO] raft: Updating configuration with AddStaging (45c059ba-bd5c-5a00-f7d6-e490324e7b52, 172.17.0.5:8300) to [{Suffrage:Voter ID:f217ca95-e83c-9a1f-9e87-3b5c1f5a82a3 Address:172.17.0.2:8300} {Suffrage:Voter ID:5b0b26fb-5e62-c390-0ced-b80e0f3293ef Address:172.17.0.3:8300} {Suffrage:Voter ID:3844affd-9b4e-ad3d-84f3-25fb77806e7c Address:172.17.0.4:8300} {Suffrage:Voter ID:45c059ba-bd5c-5a00-f7d6-e490324e7b52 Address:172.17.0.5:8300} {Suffrage:Nonvoter ID:46ebd85c-5e96-f9bd-81e4-0a82d3b405c7 Address:172.17.0.6:8300}] 84 2018/12/03 07:27:43 [INFO] autopilot: Promoting Server (ID: "46ebd85c-5e96-f9bd-81e4-0a82d3b405c7" Address: "172.17.0.6:8300") to voter 85 2018/12/03 07:27:43 [INFO] raft: Updating configuration with AddStaging (46ebd85c-5e96-f9bd-81e4-0a82d3b405c7, 172.17.0.6:8300) to [{Suffrage:Voter ID:f217ca95-e83c-9a1f-9e87-3b5c1f5a82a3 Address:172.17.0.2:8300} {Suffrage:Voter ID:5b0b26fb-5e62-c390-0ced-b80e0f3293ef Address:172.17.0.3:8300} {Suffrage:Voter ID:3844affd-9b4e-ad3d-84f3-25fb77806e7c Address:172.17.0.4:8300} {Suffrage:Voter ID:45c059ba-bd5c-5a00-f7d6-e490324e7b52 Address:172.17.0.5:8300} {Suffrage:Voter ID:46ebd85c-5e96-f9bd-81e4-0a82d3b405c7 Address:172.17.0.6:8300}]
源码架构
先来看Consul内部是如何做服务注册与发现的流程,下图是consul客户端向agent注册以及发现目标服务的时序图。
通过上图,我们大概知道了在consul agent中,功能分为了consul server和consul agent(client)。
consul源码中,server和client都是在一套代码中,通过指定启动参数的形势来运行consul server。这里我们先来重点讲解一下consul client的内部架构。
Consul Client架构
上图简要描述了consul client中的各重要服务,以及它们之间的关系。
lan serf
主要职责是维护节点之间的关系,当有节点加入或者离开的时候,所有节点都会接收到对应的event,这里的lan serf就是指对这些event做处理的handler的go routine服务。state sync
在consul启动的时候,会启动该服务,它监听一个channel,当其他服务有向consul server同步配置的需求的时候,就会向channel中写入event信息;然后就会触发该服务向consul server同步配置信息。这里的同步又分为全同步和部分同步,主要是为了降低网路的负担。gRPC router
这是对连接到consul server的gRPC连接的维护和负载均衡机制。在该服务中心,一方面会基于lan serf对consul server节点的拓扑变更事件来维护server列表,另一方面也会对到存活server的connection做定期的ping来维护连接列表;除此之外,还能够对server连接做客户端负载均衡。local state
是一个本地的内存数据库,一般执行sync就是从server将数据同步过来保存到该db中;平时做一些配置更改也会对应更新该db。api
consul是提供了HTTP和CLI两种对外访问方式的,这里所谓的API并不是想说接口的细节,而指的是consul所提供对外API对应controller逻辑实现。比如下一节要讲到的服务注册的API,后面都做了什么业务逻辑,这是很重要的一部分,对于复杂的逻辑一般包括了:更新本地local state,启动对应的go routine来做事,使用gRPC向server更新数据,向sync channel发消息从而触发sync等操作。
服务注册流程
基于前面一节的介绍,我们大概能够猜测到服务注册大概都需要些什么样的流程,接下来我们就讲以下这块的逻辑。
上图是其服务注册API的controller中函数调用的一个简化流程。
- 首先
s.agent.AddService
函数要做的就是将接收到的服务信息做一通校验,然后整理成为local state的数据结构之后保存到本地;但是由于它是一个内存数据库,并不能够持久化,于是再将其保存到本地文件中做持久化。 - 干完这些操作之后,如果该服务没有指定healthcheck操作的话,接下来要做的就是将这个服务注册请求同步到consul server,让raft leader将数据真正持久化到server中,这部分我没有在图上体现出来,但是在代码中确实是这样实现的。
- 对于在注册的时候制定了healthcheck内容的服务,需要继续注册healthcheck。由于consul支持的healthcheck类型较多,这里对其所指定类型做了简单的校验,然后就开始干正事了。启动一个goroutine来专门为这个服务执行定期的健康检查操作,可见,如果该consul agent上注册的服务太多的话,势必消耗很多资源,这就要求我们部署方案要做好规划了。
- 当健康检查的结果与先前的结果不一致的时候,会触发对local state的更新,同时,需要局部同步该服务到consul server上的内容。为什么呢?因为服务的健康状态其实是保存到其check字段下的,而非是service的一个一级属性,这块大家可以下去查阅一下代码。另外,每次状态变更都会触发consul agent,通过gRPC调用server的
Catalog.Register
来注册服务,我的理解其实是覆盖先前注册关于该服务的信息。
操作实践
介绍consul agent的配置参数,以及各种使用场景下的命令。
consul agent参数
1 -advertise 通知展现地址用来改变我们给集群中的其他节点展现的地址,一般情况下-bind地址就是展现地址 2 -bootstrap 用来控制一个server是否在bootstrap模式,在一个datacenter中只能有一个server处于bootstrap模式,当一个server处于bootstrap模式时,可以自己选举为raft leader。 3 -bootstrap-expect 在一个datacenter中期望提供的server节点数目,当该值提供的时候,consul一直等到达到指定sever数目的时候才会引导整个集群,该标记不能和bootstrap公用 4 -bind 该地址用来在集群内部的通讯,集群内的所有节点到地址都必须是可达的,默认是0.0.0.0 5 -client consul绑定在哪个client地址上,这个地址提供HTTP、DNS、RPC等服务,默认是127.0.0.1 6 -config-file 明确的指定要加载哪个配置文件 7 -config-dir 配置文件目录,里面所有以.json结尾的文件都会被加载 8 -data-dir 提供一个目录用来存放agent的状态,所有的agent允许都需要该目录,该目录必须是稳定的,系统重启后都继续存在 9 -dc 该标记控制agent允许的datacenter的名称,默认是dc1 10 -encrypt 指定secret key,使consul在通讯时进行加密,key可以通过consul keygen生成,同一个集群中的节点必须使用相同的key 11 -join 加入一个已经启动的agent的ip地址,可以多次指定多个agent的地址。如果consul不能加入任何指定的地址中,则agent会启动失败,默认agent启动时不会加入任何节点。 12 -retry-join 和join类似,但是允许你在第一次失败后进行尝试。 13 -retry-interval 两次join之间的时间间隔,默认是30s 14 -retry-max 尝试重复join的次数,默认是0,也就是无限次尝试 15 -log-level consul agent启动后显示的日志信息级别。默认是info,可选:trace、debug、info、warn、err。 16 -node 节点在集群中的名称,在一个集群中必须是唯一的,默认是该节点的主机名 17 -protocol consul使用的协议版本 18 -rejoin 使consul忽略先前的离开,在再次启动后仍旧尝试加入集群中。 19 -server 定义agent运行在server模式,每个集群至少有一个server,建议每个集群的server不要超过5个 20 -syslog 开启系统日志功能,只在linux/osx上生效 21 -ui-dir 提供存放web ui资源的路径,该目录必须是可读的 22 -pid-file 提供一个路径来存放pid文件,可以使用该文件进行SIGINT/SIGHUP(关闭/更新)agent
转自:http://ljchen.net/2019/01/04/consul%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/