服务发现允许某个组件在想要与其它组件交互时,自动找到对方。由于这些应用本身是分布式的,服务发现机制也需要是分布式的。且服务发现作为分布式应用不同组件之间的"胶水",其本身还需要足够动态、可靠、适应性强,而且可以快速且一致的共享关于这些服务数据。

Consul

Consul 是使用Raft一致性算法来提供确定的写入机制的特殊数据存储器。Consul暴露了键值存储系统和服务分类系统,并提供高可用、高容错能力,保证强一致性。服务可以将自己注册到Consul,并以高可用且分布式的方式共享这些信息。

学习任务

  • 创建Consul服务的Docker镜像
  • 构建3台运行Docker的宿主机,并在每台上运行一个Consul。
  • 构建服务,并将其注册到Consul,然后从其它服务查询该数据。

Consul Image

1
2
3
$ mkdir consul && cd consul
$ vi Dockerfile
$ docker build -t acqua/consul .

Dockerfile

Consul默认端口

Port Function
53/udp DNS服务器
8300 服务器使用的RPC
8301+udp Serf服务器的LAN端口
8302+udp Serf服务器的WAN端口
8400 命令行RPC接入点
8500 HTTP API

Local Test Consul

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$ docker run -p 8500:8500 -p 53:53/udp \
-h node1 acqua/consul -server -bootstrap

==> WARNING: Bootstrap mode enabled! Do not enable unless necessary
==> Starting Consul agent...
==> Starting Consul agent RPC...
==> Consul agent running!
Node name: 'node1'
Datacenter: 'dc1'
Server: true (bootstrap: true)
Client Addr: 0.0.0.0 (HTTP: 8500, HTTPS: -1, DNS: 53, RPC: 8400)
Cluster Addr: 172.17.0.7 (LAN: 8301, WAN: 8302)
Gossip encrypt: false, RPC-TLS: false, TLS-Incoming: false
Atlas: <disabled>

==> Log data will now stream in as it occurs:

2018/01/26 07:31:17 [INFO] raft: Node at 172.17.0.7:8300 [Follower] entering Follower state
2018/01/26 07:31:17 [INFO] serf: EventMemberJoin: node1 172.17.0.7
2018/01/26 07:31:17 [INFO] consul: adding LAN server node1 (Addr: 172.17.0.7:8300) (DC: dc1)
2018/01/26 07:31:17 [INFO] serf: EventMemberJoin: node1.dc1 172.17.0.7
2018/01/26 07:31:17 [INFO] consul: adding WAN server node1.dc1 (Addr: 172.17.0.7:8300) (DC: dc1)
2018/01/26 07:31:17 [ERR] agent: failed to sync remote state: No cluster leader
2018/01/26 07:31:18 [WARN] raft: Heartbeat timeout reached, starting election
2018/01/26 07:31:18 [INFO] raft: Node at 172.17.0.7:8300 [Candidate] entering Candidate state
2018/01/26 07:31:18 [INFO] raft: Election won. Tally: 1
2018/01/26 07:31:18 [INFO] raft: Node at 172.17.0.7:8300 [Leader] entering Leader state
2018/01/26 07:31:18 [INFO] consul: cluster leadership acquired
2018/01/26 07:31:18 [INFO] raft: Disabling EnableSingleNode (bootstrap)
2018/01/26 07:31:18 [INFO] consul: New leader elected: node1
2018/01/26 07:31:18 [INFO] consul: member 'node1' joined, marking health alive
2018/01/26 07:31:20 [INFO] agent: Synced service 'consul'
==> Newer Consul version available: 1.0.3

-server参数告诉consul代理以服务器的模式运行。
-bootstrap参数告诉Consul本节点可以执行 Raft leader选举。

每个数据中心最多只有1台Consul服务器可以运行在bootstrap模式下。否则,如果有多个可以进行自选举的节点,整个集群无法保证一致性。

Consul Cluster

Conusl Public IP

1
2
3
4
5
host1$ PUBLIC_IP="$(ifconfig ens160 | awk -F ' *|:' '/inet /{print $3}')"
host1$ echo $PUBLIC_IP
10.0.77.22

假定`host1`运行在bootstrap模式下,故需要将host1的PUBLIC_IP(10.0.77.22)添加到host[2~3]。

User-defined Container DNS

Configure container DNS in user-defined networks

  • 本地Docker的IP地址,以便Consul来解析DNS
  • Google的DNS服务地址,解析其它请求
  • 为Consul查询指定搜索域

Start Consul

HOST1

1
2
3
4
5
6
7
host1$ ip a show docker0
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 02:42:bf:5e:6e:d3 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:bfff:fe5e:6ed3/64 scope link
valid_lft forever preferred_lft forever
1
2
3
4
5
6
7
8
9
host1$ docker run -d -h $HOSTNAME \
-p 8300:8300 -p 8301:8301 \
-p 8301:8301/udp -p 8302:8302 \
-p 8302:8302/udp -p 8400:8400 \
-p 8500:8500 -p 53:53/udp \
--dns=172.17.0.1 \
--dns=8.8.8.8 \
--dns-search=service.consul \
--name host1_agent acqua/consul -server -advertise=$PUBLIC_IP -bootstrap-expect=3

[ERR] agent: failed to sync remote state: No cluster leader
因为没有其它节点加入集群,没有触发选举行为。

HOST2

1
2
3
4
5
6
host2$ PUBLIC_IP="$(ifconfig ens160 | awk -F ' *|:' '/inet /{print $3}')"
host2$ echo $PUBLIC_IP
10.0.77.16

host2$ JOIN_IP=10.0.77.22
host2$ echo $JOIN_IP
1
2
3
4
5
6
7
8
9
host2$ docker run -d -h $HOSTNAME \
-p 8300:8300 -p 8301:8301 \
-p 8301:8301/udp -p 8302:8302 \
-p 8302:8302/udp -p 8400:8400 \
-p 8500:8500 -p 53:53/udp \
--dns=172.17.0.1 \
--dns=8.8.8.8 \
--dns-search=service.consul \
--name host2_agent acqua/consul -server -advertise=$PUBLIC_IP -join=$JOIN_IP

HOST3

1
2
3
4
5
6
host3$ PUBLIC_IP="$(ifconfig ens160 | awk -F ' *|:' '/inet /{print $3}')"
host3$ echo $PUBLIC_IP
10.0.77.17

host3$ JOIN_IP=10.0.77.22
host3$ echo $JOIN_IP
1
2
3
4
5
6
7
8
9
host3$ docker run -d -h $HOSTNAME \
-p 8300:8300 -p 8301:8301 \
-p 8301:8301/udp -p 8302:8302 \
-p 8302:8302/udp -p 8400:8400 \
-p 8500:8500 -p 53:53/udp \
--dns=172.17.0.1 \
--dns=8.8.8.8 \
--dns-search=service.consul \
--name host3_agent acqua/consul -server -advertise=$PUBLIC_IP -join=$JOIN_IP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
host1_agent log:
2018/01/27 02:55:04 [INFO] serf: EventMemberJoin: host3 10.0.77.17
2018/01/27 02:55:04 [INFO] consul: adding LAN server host3 (Addr: 10.0.77.17:8300) (DC: dc1)
2018/01/27 02:55:04 [INFO] consul: Attempting bootstrap with nodes: [10.0.77.22:8300 10.0.77.16:8300 10.0.77.17:8300]
2018/01/27 02:55:05 [WARN] raft: Heartbeat timeout reached, starting election
2018/01/27 02:55:05 [INFO] raft: Node at 10.0.77.22:8300 [Candidate] entering Candidate state
2018/01/27 02:55:05 [WARN] raft: Remote peer 10.0.77.17:8300 does not have local node 10.0.77.22:8300 as a peer
2018/01/27 02:55:05 [WARN] raft: Remote peer 10.0.77.16:8300 does not have local node 10.0.77.22:8300 as a peer
2018/01/27 02:55:05 [INFO] raft: Election won. Tally: 2
2018/01/27 02:55:05 [INFO] raft: Node at 10.0.77.22:8300 [Leader] entering Leader state
2018/01/27 02:55:05 [INFO] consul: cluster leadership acquired
2018/01/27 02:55:05 [INFO] consul: New leader elected: host1
2018/01/27 02:55:05 [INFO] raft: pipelining replication to peer 10.0.77.17:8300
2018/01/27 02:55:05 [INFO] raft: pipelining replication to peer 10.0.77.16:8300
2018/01/27 02:55:05 [INFO] consul: member 'host1' joined, marking health alive
2018/01/27 02:55:05 [INFO] consul: member 'host2' joined, marking health alive
2018/01/27 02:55:05 [INFO] consul: member 'host3' joined, marking health alive
2018/01/27 02:55:05 [INFO] agent: Synced service 'consul'

Dig Consul DNS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ dig @172.17.0.1 consul.service.consul

; <<>> DiG 9.9.4-RedHat-9.9.4-51.el7_4.1 <<>> @172.17.0.1 consul.service.consul
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19003
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;consul.service.consul. IN A

;; ANSWER SECTION:
consul.service.consul. 0 IN A 10.0.77.17
consul.service.consul. 0 IN A 10.0.77.16
consul.service.consul. 0 IN A 10.0.77.22

;; Query time: 1 msec
;; SERVER: 172.17.0.1#53(172.17.0.1)
;; WHEN: Sat Jan 27 11:04:05 CST 2018
;; MSG SIZE rcvd: 150

Register Consul

基于uWSGI框架创建一个分布式应用来演示注册服务。

  • 一个Web应用:distributed_app host[1~2]。它在启动时启动相关的worker,并将这些程序作为服务注册到Consul。
  • 一个应用客户端:distributed_client host3。它从Consul读取与distributed_app相关的信息,并报告当前应用程序的状态和配置。

Distributed_app Image

1
2
3
$ mkdir distributed_app && cd distributed_app
$ vi Dockerfile
$ docker build -t acqua/distributed_app .

Dockerfile

Distributed_client Image

1
2
3
$ mkdir distributed_client && cd distributed_client
$ vi Dockerfile
$ docker build -t acqua/distributed_client .

Dockerfile

Start App

1
2
3
4
5
host1$ docker run -h $HOSTNAME -d \
--dns=172.17.0.1 \
--dns=8.8.8.8 \
--dns-search=service.consul \
--name host1_distributed acqua/distributed_app
1
2
log:
[consul] service distributed_app registered successfully

1
2
3
4
5
host2$ docker run -h $HOSTNAME -d \
--dns=172.17.0.1 \
--dns=8.8.8.8 \
--dns-search=service.consul \
--name host2_distributed acqua/distributed_app

1
2
3
4
5
host3$ docker run -h $HOSTNAME -d \
--dns=172.17.0.1 \
--dns=8.8.8.8 \
--dns-search=service.consul \
--name host3_distributed acqua/distributed_client
1
2
3
4
5
log:
Application distributed_app with element server1 on port 2001 found on node host1 (10.0.77.22).
We can also resolve DNS - distributed_app resolves to 10.0.77.22 and 10.0.77.16.
Application distributed_app with element server2 on port 2002 found on node host1 (10.0.77.22).
We can also resolve DNS - distributed_app resolves to 10.0.77.22 and 10.0.77.16.

Reference
The Docker Book
Jimmy Song - Docker内置DNS