微服务注册发现集群搭建

在互联网应用领域,服务的动态性需求十分常见,这就对服务的自动发现和可动态扩展提出了很高的要求。

微服务系统动辄上万个服务,而且还要动态伸缩。以人工写好的IP、Port 硬编码脚本的方式无法做到大规模自动化,稍微多点服务运维就傻了。微服务必然要做到ip和port自动分配,减少人工干预。我们需要让每个服务能动态的创建地址,同时调用方要能感知地址变化。

这就需要有一个服务注册与发现的机制,这篇文件就是讨论如何实现这个机制。

1. 服务注册发现的流程

我们做这个事情要达到的目的是:

注册发现模式传统模式
服务启动后自动被发现手动注册
动态变更负载均衡人工写入静态配置
自动伸缩规模运维较长时间的手动调整

1.1 服务 “自注册” 与 “第三方注册”。

按注册源分

1.自注册:服务内部启动客户端,连接注册中心,写入服务信息。

好处:

  • 没有引入第三方,进程数量少,少依赖。

问题:

  • 服务代码对注册中心进行了硬编码,若更换了注册中心,服务代码也必须跟着调整;
  • 注册中心必须与每个服务都保持通信,来做心跳检测。如果服务很多时,对注册中心也是一种额外的开销;

2.第三方注册(本文采用方式):采用协同进程的方式,监听服务进程的变化,将服务信息写入注册中心。

  • 好处:做到了服务与注册中心的解耦,对服务而言,完成了服务的自动化注册;
  • 问题:协同进程本身也要考虑高可用,否则将成为单点故障的风险点;

1.2 自注册的实现

自注册不是我们本篇要讨论的,可以自己写代码实现,我们讨论第三方注册的实现。

1.3 第三方注册的实现

Docker 的出现,以及微服务架构的兴起,让众多开源项目开始关注在松耦合的架构前提下,如何基于 Docker 实现一套真正可动态扩展的服务架构。

这里我们使用 Registrator + Consul + Consul-template + Nginx 这几个开源组件来实现可动态扩展的服务注册与发现机制,当然,毫无疑问他们都跑在docker上。

首先看看流程:

这里写图片描述

服务注册中心:作为整个架构中的核心,要支持分布式、持久化存储,注册信息变动实时通知消费者。

服务提供者:服务以 docker 容器化方式部署(实现服务端口的动态生成),并以 docker-compose 的方式来管理,通过 Registrator 可以检测到docker进程信息以完成服务的自动注册。

服务消费者:要使用服务提供者提供的服务,和服务提供者往往是动态相互转位置的。

  1. 服务注册:服务提供者到注册中心注册;
  2. 服务订阅:服务消费者到注册中心订阅服务信息,对其进行监听;
  3. 缓存:本地缓存服务列表,减少与注册中心的网络通信;
  4. 服务调用:先查找本地缓存,找不到再去注册中心拉取服务地址,然后发送服务请求;
  5. 变更通知:服务节点变动时(新增、删除等),注册中心将通知监听节点,更新服务信息。

2. 工具介绍

2.1 Registrator

Registrator:一个由Go语言编写的,针对docker使用的,通过检查本机容器进程在线或者停止运行状态,去注册服务的工具。所以我们要做的实验,所有的工具都是在docker上运行的,就是因为registrator是通过检查docker容器的状态来判断服务状态的,这样就和我们的代码实现完全解耦了,对上层透明化,无感知。它有如下特点

  • 通过docker socket直接监听容器event,根据容器启动/停止等event来注册/注销服务
  • 每个容器的每个exposed端口对应不同的服务
  • 支持可插拔的registry backend,默认支持Consul, etcd and SkyDNS
  • 自身也是docker化的,可以容器方式启动
  • 用户可自定义配置,如服务TTL(time-to-live)、服务名称、服务tag等

2.1 consul

我们上图所说的服务注册中心,就是这玩意。Consul 是一个分布式高可用的服务发现和配置共享的软件。由 HashiCorp 公司用 Go 语言开发。

Consul在这里用来做 docker 实例的注册与配置共享。

特点:

  • 一致性协议采用 Raft 算法,比Paxos算法好用. 使用 GOSSIP 协议管理成员和广播消息, 并且支持 ACL 访问控制.
  • 支持多数据中心以避免单点故障,内外网的服务采用不同的端口进行监听。而其部署则需要考虑网络延迟, 分片等情况等.zookeeper 和 etcd 均不提供多数据中心功能的支持.
  • 健康检查. etcd 没有的.
  • 支持 http 和 dns 协议接口. zookeeper 的集成较为复杂, etcd 只支持 http 协议.
  • 还有一个web管理界面。

2.3 consul-template

一开始构建服务发现,大多采用的是zookeeper/etcd+confd。但是复杂难用。consul-template,大概取代了confd的位置,以后可以这样etcd+confd或者consul+consul-template。

consul template的使用场景:consul template可以查询consul中的服务目录、key、key-values等。这种强大的抽象功能和查询语言模板可以使consul template特别适合动态的创建配置文件。例如:创建apache/nginx proxy balancers、haproxy backends、varnish servers、application configurations。

consul-template提供了一个便捷的方式从consul中获取存储的值,consul-template守护进程会查询consul服务,来更新系统上指定的任何模板,当更新完成后,模板可以选择运行一些任意的命令,比如我们这里用它来更新nginx.conf这个配置文件,然后执行nginx -s reload命令,以更新路由,达到动态调节负载均衡的目的。

consul-template和nginx必须装到一台机器,因为consul-template需要动态修改nginx配置文件

2.4 nginx

这个耳熟能详的名字,不用过多介绍了,它在这里就是做负载均衡,转发请求用的。当然最擅长负载均衡的是直接用硬件,软件做性能比不上。但软件成本低、维护方便。

3. 单机实验

首先看一个简单的传统负载均衡web服务

load balance web servers

这个很好理解吧,client访问nginx,然后被转发到后端某一个web server上,传统的负载均衡。如果后端有添加/删除web server,运维手动改下nginx.conf,然后重新载入配置,就可以调整负载均衡了。

再看看我们基于微服务自动注册和发现模式下的负载均衡:

Servies register and find

负载均衡的方式没有变,只是多了一些外围的组件,当然这些组件对client是不可见的,client依然只能看到nginx入口,访问方式也没变化。

其中,我们用registrator来监控每个web server的状态。当有新的web server启动的时候,registrator会把它注册到consul这个注册中心上。由于consul_template已经订阅了该注册中心上的服务消息,此时consul注册中心会将新的web server信息推送给consul_template,consul_template则会去修改nginx.conf的配置文件,然后让nginx重新载入配置以达到自动修改负载均衡的目的。同样当一个web server挂了,registrator也能感知到,进而通知consul做出响应。

整个过程不需要运维人工的干预,自动完成。接下来我们找一台机器上实践下这个方案

3.1 环境

headerheader
操作系统ubuntu:16.04 x86_64,内核:4.8.0-58-generic
主机ip10.111.152.136
dockerDocker version 1.12.6, build 78d1802
docker-composedocker-compose version 1.8.0, build unknown

首先安装 docker 和 docker-compose

$ apt-get install docker docker-compose -y
  • 1

随便找个目录,创建模板文件 docker-compose.yml

 
  1. #backend web application, scale this with docker-compose scale web=3

  2. web:

  3. image: liberalman/helloworld:latest

  4. environment:

  5. SERVICE_80_NAME: my-web-server

  6. SERVICE_TAGS: backend-1

  7. MY_HOST: host-1

  8. ports:

  9. - "80"

  10.  
  11. #load balancer will automatically update the config using consul-template

  12. lb:

  13. image: liberalman/nginx-consul-template:latest

  14. hostname: lb

  15. links:

  16. - consulserver:consul

  17. ports:

  18. - "80:80"

  19.  
  20. consulserver:

  21. image: progrium/consul:latest

  22. environment:

  23. SERVICE_TAGS: consul servers

  24. hostname: consulserver

  25. ports:

  26. - "8300"

  27. - "8400"

  28. - "8500:8500"

  29. - "53"

  30. command: -server -ui-dir /ui -data-dir /tmp/consul -bootstrap-expect 1

  31.  
  32. # listen on local docker sock to register the container with public ports to the consul service

  33. registrator:

  34. image: gliderlabs/registrator:master

  35. hostname: registrator

  36. links:

  37. - consulserver:consul

  38. volumes:

  39. - "/var/run/docker.sock:/tmp/docker.sock"

  40. command: -internal consul://consul:8500

注意: liberalman/helloworld和liberalman/nginx-consul-template这两个镜像我已经实现了,可以pull下来,大家可以直接使用。想要看他们怎么写的,访问https://github.com/liberalman

3.2 启动

进入模板所在目录,执行

$ docker-compose up
  • 1

没问题的话就启动成功了,其中的镜像自动被下。访问 http://localhost 可以看到一个 web 页面:

Hello World! I’m host-1 addr:172.17.0.2. I saw that you are 172.17.0.6:35612.

这个内容实际是后端web服务器helloworld所反馈的页面,它告诉我们它自己的地址是172.17.0.2(docker的内网地址),它所看到的前端访问过来的ip是172.17.0.6,实际上这个前端是我们的nginx的负载均衡的代理转发的,所以它看到的实际是nginx的地址。

这里的host-1是我自己设置的物理机的名称,注释不是操作系统那hostname,纯粹是为了在页面上好显示,以及后期多个物理机实验的时候好区分不同物理机器,所以自定义了一个临时名称。它对应docker-compose.yml中的MY_HOST环境变量,会通过docker容器传递到helloworld的运行环境中。

要停止服务Ctrl + C就行了,如果有些没有停止,则

$ docker-compose down

 

如果要在后台运行

$ docker-compose up -d

3.3 负载均衡

回到正题,在浏览器上多次刷新,可以看到后端地址没有变化,这是因为只有一个 web 后端服务器。

如果要测试一下nginx负载均衡的效果,则调整后端为 3 个服务器。先停掉服务,然后

 
  1. $ docker-compose scale web=3

  2. $ docker-compose up

再次访问 http://localhost ,多次刷新,可以看到页面的实际目标地址发生了变化,有3个ip轮换。新启动的 web 后端服务器被自动注册,并且 nginx 也把新的路由添加上了:

Hello World! I’m host-1 addr:172.17.0.2. I saw that you are 172.17.0.6:36710. 
Hello World! I’m host-1 addr:172.17.0.3. I saw that you are 172.17.0.6:35210. 
Hello World! I’m host-1 addr:172.17.0.4. I saw that you are 172.17.0.6:58678.

3.4 查看服务状态

要查看节点注册状况,到 http://localhost:8500 可以看到 consul web ui 的管理端

consul ui

点击SERVICES这个按钮,列出所有被注册的服务。

  • consul server,看到有多个是因为监听多个端口,还有udp端口的。
  • my-web-server就是后端web服务,这个名称是要在docker-compose模板中设置SERVICE_80_NAME这个变量的,针对80端口,详情见registrator 用户指导手册 
    https://gliderlabs.com/registrator/latest/user/services/
  • nginx-consul-template就是nginx和consul-template的合体服务。

点击my-web-server,可以看到它右侧的服务节点数,这里只有一个,有多个的话会依次列出

host-1 my-web-server

4. 两台物理机

以上都是在单台物理机上完成的,下面我们要测试下多台物理机情况下,真正分布式的效果。

host namereal ipservices
host-110.111.152.136registrator、helloworld、consul-server、consul-template、nginx
host-210.111.152.135registrator、helloworld

第一台物理机host-1的docker-compse.yml

 
  1. #backend web application, scale this with docker-compose scale web=3

  2. web:

  3. image: liberalman/helloworld:latest

  4. environment:

  5. SERVICE_80_NAME: my-web-server

  6. SERVICE_TAGS: backend-1

  7. MY_HOST: host-1

  8. ports:

  9. - "80"

  10.  
  11. #load balancer will automatically update the config using consul-template

  12. lb:

  13. image: liberalman/nginx-consul-template:latest

  14. hostname: lb

  15. links:

  16. - consulserver:consul

  17. ports:

  18. - "80:80"

  19.  
  20. consulserver:

  21. image: progrium/consul:latest

  22. environment:

  23. SERVICE_TAGS: consul servers

  24. hostname: consulserver-node1

  25. ports:

  26. - "8300"

  27. - "8400"

  28. - "8500:8500"

  29. - "53"

  30. command: -server -ui-dir /ui -data-dir /tmp/consul -bootstrap-expect 1

  31.  
  32. # listen on local docker sock to register the container with public ports to the consul service

  33. registrator:

  34. image: gliderlabs/registrator:master

  35. hostname: registrator-1

  36. volumes:

  37. - "/var/run/docker.sock:/tmp/docker.sock"

  38. command: -ip=10.111.152.136 consul://10.111.152.136:8500

我们第二台机器host-2的yml文件:

 
  1. #backend web application, scale this with docker-compose scale web=3

  2. web:

  3. image: liberalman/helloworld:latest

  4. environment:

  5. SERVICE_80_NAME: my-web-server

  6. SERVICE_TAGS: backend-2

  7. MY_HOST: host-2

  8. ports:

  9. - "80"

  10.  
  11. # listen on local docker sock to register the container with public ports to the consul service

  12. registrator:

  13. image: gliderlabs/registrator:master

  14. hostname: registrator-2

  15. volumes:

  16. - "/var/run/docker.sock:/tmp/docker.sock"

  17. command: -ip 10.111.152.135 consul://10.111.152.136:8500

这是我们将MY_HOST改为host-2了,以便在页面查看的时候可以直观看到。另外的重要改变就是registrator的启动参数,我们去掉了上报docker内部ip的-internal,转而使用了外部ip,将自己本机的ip 10.111.152.135上报了。同时要访问的consul服务器参数配置成host-1的ip地址 10.111.152.136。还有registrator的hostname要和第一台机器的区别开,我改成registrator-2了,这样在注册到consul中的时候,不会覆盖掉。hostname一样consul无法区分是哪个机器的,这样两个机器的registrator会相互覆盖。

host-1启动方式不变,我们现在到host-2上启动,看看效果,是否新节点被加上了。

Hello World! I’m host-1 addr:172.17.0.2. I saw that you are 172.17.0.5:41464.

Hello World! I’m host-2 addr:172.17.0.2. I saw that you are 10.111.152.136:41578.

刷新两次,发现一会儿是host-1,一会儿是host-2,说明我们host-2物理机上的服务被添加进来了,并且被nginx路由到了。

同时consul ui,看到新的节点果然被添加上了

host-2 my-web-server

不过发现个问题,如果在host-2上先将registrator关闭,再关闭host-2上的后端web,我们的consul服务器可以感知到,但是那个consul ui界面没更新,依然显示两个节点。

5. Consul Cluster

以上我们的实验其实是个单点的consul服务,点击consul ui页面的NODES按钮可以看到

single node

只有一个consul server节点,也就是在我们host-1上跑的节点consulserver,另外一个物理机上没有运行consul节点。一旦它挂了整个服务注册功能就歇菜了。既然是分布式,一定要发挥集群的优势以解决单点问题。所以,我们要建立Consul Cluster。

在Consul方案中,每个提供服务的节点上都要部署和运行一个agent,所有运行Consul agent节点的集合构成Consul Cluster。

Consul agent有两种运行模式:Server和Client。这里的Server和Client只是Consul集群层面的区分,与搭建在Cluster之上的应用服务无关。

以Server模式运行的Consul agent节点用于维护Consul集群的状态,官方建议每个Consul Cluster至少有3个或以上的运行在Server mode的Agent,Client节点不限。

这里写图片描述

每个数据中心的Consul Cluster都会在运行于server模式下的agent节点中选出一个Leader节点,这个选举过程通过Consul实现的raft协议保证,多个 server节点上的Consul数据信息是强一致的。处于client mode的Consul agent节点比较简单,无状态,仅仅负责将请求转发给Server agent节点。

我们这次的架构有些调整,绘制一个服务器的逻辑上的部署图来说明下

Services register adn find, consul cluster

这是一张逻辑上服务部署的图,我们找3台机器来实验。每台机器上部署几个web server,一个registrator和一个consul client,这是基本需求。另外再建立一个consul cluster集群,用来当我们的注册中心。当web server启动后,被registrator感知,进而将注册信息发送给consul client,consul client则访问注册中心的leader节点,上报新加入的服务信息。consul cluster会将新的服务信息推送给已经到它这里订阅了服务消息的consul-template,consul-template再去修改和自己同一台机器上的nginx,以达到动态调整负载均衡的目的。

注意:由于资源有限,我们没有单独使用机器去搭建consul集群,所以图中的consul client和consul server节点其实是同一个节点,因为server模式同时可以提供client的功能嘛。那个consul cluster集群其实是分布到3个host中建立起来的,我们就在3个host中分别启动一个consul进程,每个都同时担任server和client的功能。

5.1 配置

host namereal ipservicesnote
host-110.111.152.136registrator、helloworld*n、consul-server、consul-template、nginx放置consol web ui和nginx负载均衡
host-210.111.152.135registrator、helloworld*n、consul-server 
host-310.111.152.168registrator、helloworld*n、consul-server 

host-1作为运行负载均衡的机器,部署consul-template和nginx。每个机器上都部署了consul-server节点,也就是我们有3个节点,接下来就研究这3个节点是如何选举leader的。

host-1的docker-compose.yml文件

 
  1. #backend web application, scale this with docker-compose scale web=3

  2. web:

  3. image: liberalman/helloworld:latest

  4. environment:

  5. SERVICE_80_NAME: my-web-server

  6. SERVICE_TAGS: backend-1

  7. MY_HOST: host-1

  8. ports:

  9. - "80"

  10.  
  11. #load balancer will automatically update the config using consul-template

  12. lb:

  13. image: liberalman/nginx-consul-template:latest

  14. hostname: lb

  15. links:

  16. - consulserver:consul

  17. ports:

  18. - "80:80"

  19.  
  20. consulserver:

  21. image: progrium/consul:latest

  22. environment:

  23. SERVICE_TAGS: consul servers

  24. hostname: consulserver-node1

  25. ports:

  26. - "8300:8300"

  27. - "8301:8301"

  28. - "8301:8301/udp"

  29. - "8302:8302"

  30. - "8302:8302/udp"

  31. - "8400:8400"

  32. - "8500:8500"

  33. - "53:53/udp"

  34. command: -server -ui-dir /ui -advertise 10.111.152.136 -bootstrap-expect 3

  35.  
  36. # listen on local docker sock to register the container with public ports to the consul service

  37. registrator:

  38. image: gliderlabs/registrator:master

  39. hostname: registrator-1

  40. volumes:

  41. - "/var/run/docker.sock:/tmp/docker.sock"

  42. command: -ip 10.111.152.136 consul://10.111.152.136:8500

参数解释下

  • hostname,将来consul节点都靠这个来标识了,所以每个物理机上的节点名称都要区别开,以免冲突。
  • -bootstrap-expect 3,这个参数的作用是,当consulserver-node1节点启动之后,等待另外两个节点的加入,3个节点聚齐后,之后才开始选举leader。
  • -advertise 10.111.152.136,如果要让节点在WAN网络中被发现,就要配置这个参数,暴露出外网ip。如果只在LAN中被发现,就不用配置这个了,默认绑定内网ip。
  • -ui-dir /ui,这个配置是指定当前节点支持consul ui的web页面。

host-2的docker-compose.yml文件

 
  1. #backend web application, scale this with docker-compose scale web=3

  2. web:

  3. image: liberalman/helloworld:latest

  4. environment:

  5. SERVICE_80_NAME: my-web-server

  6. SERVICE_TAGS: backend-2

  7. MY_HOST: host-2

  8. ports:

  9. - "80"

  10.  
  11. consulserver:

  12. image: progrium/consul:latest

  13. environment:

  14. SERVICE_TAGS: consul servers

  15. hostname: consulserver-node2

  16. ports:

  17. - "8300:8300"

  18. - "8301:8301"

  19. - "8301:8301/udp"

  20. - "8302:8302"

  21. - "8302:8302/udp"

  22. - "8400:8400"

  23. - "8500:8500"

  24. - "53:53/udp"

  25. command: -server -advertise 10.111.152.135 -join 10.111.152.136

  26.  
  27. # listen on local docker sock to register the container with public ports to the consul service

  28. registrator:

  29. image: gliderlabs/registrator:master

  30. hostname: registrator-2

  31. volumes:

  32. - "/var/run/docker.sock:/tmp/docker.sock"

  33. command: -ip 10.111.152.135 consul://10.111.152.136:8500

与host-1不同的是,host-2使用了参数 
-join 10.111.152.136 意思是把本节点加入到10.111.152.136这个ip的节点中,这是consulserver-node1的地址。我们上一个host的配置中表明,consulserver-node1这个节点启动后,会等待另外两个节点的加入,我们这里就是加入它。

host-3的docker-compose.yml文件

 
  1. #backend web application, scale this with docker-compose scale web=3

  2. web:

  3. image: liberalman/helloworld:latest

  4. environment:

  5. SERVICE_80_NAME: my-web-server

  6. SERVICE_TAGS: backend-3

  7. MY_HOST: host-3

  8. ports:

  9. - "80"

  10.  
  11. consulserver:

  12. image: progrium/consul:latest

  13. environment:

  14. SERVICE_TAGS: consul servers

  15. hostname: consulserver-node3

  16. ports:

  17. - "8300:8300"

  18. - "8301:8301"

  19. - "8301:8301/udp"

  20. - "8302:8302"

  21. - "8302:8302/udp"

  22. - "8400:8400"

  23. - "8500:8500"

  24. - "53:53/udp"

  25. command: -server -advertise 10.111.152.168 -join 10.111.152.136

  26.  
  27. # listen on local docker sock to register the container with public ports to the consul service

  28. registrator:

  29. image: gliderlabs/registrator:master

  30. hostname: registrator-3

  31. volumes:

  32. - "/var/run/docker.sock:/tmp/docker.sock"

  33. command: -ip 10.111.152.168 consul://10.111.152.136:8500

注意:到这里你可能有疑问,上文的3个节点都是server节点,那client节点哪里去了,没有client节点怎么访问集群啊?我们和集群交互可是访问client,client再转发到server节点的。

我们前篇也提到过,其实每个server节点,本身就具有client的功能,只是多了一些把所有的信息持久化的本地以及选举leader的功能呢,这样遇到故障,信息是可以被保留的。

所以,这里我们每个主机上部署registrator的时候,配置的访问consul服务的地址也是就近访问本机上的consul节点,把它当成一个consul client访问就可以了。当然也可以单独部署一个client节点,只是我们至少要保证有3个server节点,才能完成leader选举,如果再多一台机器我会考虑专门加一个client节点。

5.2 启动

依次在host-1、host-2和host-3上启动3个节点。注意执行docker-compose up之后,不要关闭终端,让它一直打印,后续我们还要在这里看日志,别的操作都转到新开终端上执行。访问 http://10.111.152.136:8500/ui/#/dc1/nodes 看到节点都被添加上了

这里写图片描述

除了查看ui界面外,也可以使用命令行看看有哪些服务注册了,在新终端下执行

 
  1. ~# curl 10.111.152.136:8500/v1/catalog/services|jq .

  2. % Total % Received % Xferd Average Speed Time Time Time Current

  3. Dload Upload Total Spent Left Speed

  4. 100 308 100 308 0 0 54892 0 --:--:-- --:--:-- --:--:-- 61600

  5. {

  6. "consul": [],

  7. "consul-53": [

  8. "consul servers",

  9. "udp"

  10. ],

  11. "consul-8300": [

  12. "consul servers"

  13. ],

  14. "consul-8301": [

  15. "consul servers",

  16. "udp"

  17. ],

  18. "consul-8302": [

  19. "consul servers",

  20. "udp"

  21. ],

  22. "consul-8400": [

  23. "consul servers"

  24. ],

  25. "consul-8500": [

  26. "consul servers"

  27. ],

  28. "my-web-server": [

  29. "backend-1",

  30. "backend-2",

  31. "backend-3"

  32. ],

  33. "nginx-consul-template": []

  34. }

访问 http://10.111.152.136 查看nginx负载均衡的效果,依次刷新,得到

Hello World! I’m host-1 addr:172.17.0.2. I saw that you are 172.17.0.1:49728. 
Hello World! I’m host-2 addr:172.17.0.3. I saw that you are 10.111.152.136:54640. 
Hello World! I’m host-3 addr:172.17.0.3. I saw that you are 10.111.152.136:58660.

OK,看起来一切正常。那我们现在分析下到底哪个节点是leader,有节点退出会怎样?

现在新开一个终端,在host-1上,执行

docker ps -f name=consul
  •  

查到consul节点的容器id是4364cd41f2ba。登录这个容器

docker exec -it 4364cd41f2ba /bin/sh
  •  

然后就进入容器的操作系统环境了,在该环境下执行

 
  1. / # consul members

  2. Node Address Status Type Build Protocol DC

  3. consulserver-node3 10.111.152.168:8301 alive server 0.5.2 2 dc1

  4. consulserver-node1 10.111.152.136:8301 alive server 0.5.2 2 dc1

  5. consulserver-node2 10.111.152.135:8301 alive server 0.5.2 2 dc1

一目了然的看到了我们的3个consul节点。查看当前节点信息

 
  1. / # consul info

  2. ......

  3. consul:

  4. bootstrap = false

  5. known_datacenters = 1

  6. leader = false

  7. server = true

  8. raft:

  9. applied_index = 192

  10. commit_index = 192

  11. fsm_pending = 0

  12. last_contact = 15.960533ms

  13. last_log_index = 192

  14. last_log_term = 2

  15. last_snapshot_index = 0

  16. last_snapshot_term = 0

  17. num_peers = 2

  18. state = Follower

  19. term = 2

  20. ......

输出信息很多,省略掉,只给出重要的。server = true确实是server节点。看到leader=false,说明这个节点不是leader。state = Follower,看来确实是个Follower节点哦。last_contact = 15.960533ms心跳剩余时间,term = 2说是第二个term,已经选过2回了。

执行上述命令的同时,由于之前在host-1上执行docker-compose up命令的时候,日志是直接输出到屏幕上的,我们此时可以节点1输出的日志

 
  1. consulserver_1 | 2017/07/26 05:08:20 [INFO] agent.rpc: Accepted client: 127.0.0.1:47084

  2. consulserver_1 | 2017/07/26 05:08:24 [INFO] agent.rpc: Accepted client: 127.0.0.1:47086

  3. ......

我们刚才执行的命令都是客户端发到当前consul server上的。

同样的方式,在节点在conserver-node3上

 
  1. consul:

  2. bootstrap = false

  3. known_datacenters = 1

  4. leader = true

  5. server = true

原来leader是节点3.

5.3 去掉节点

让一个节点挂掉,看看会发生什么。

5.3.1 关闭一个节点

在host-1上新开终端执行

 docker stop 4364cd41f2ba
  •  

看到host-1的日志滚动

 
  1. gocode_consulserver_1 exited with code 1

  2. lb_1 | 2017/07/26 06:02:51.211894 [WARN] (view) health.service(my-web-server|passing): Get http://consul:8500/v1/health/service/my-web-server?index=40&passing=1&stale=&wait=60000ms: dial tcp 172.17.0.4:8500: i/o timeout (retry attempt 1 after "250ms")

  3. ex=40&passing=1&stale=&wait=60000ms: dial tcp 172.17.0.4:8500: i/o timeout (retry attempt 1 after "250ms")

  4. lb_1 | 2017/07/26 06:03:10.099572 [WARN] (view) health.service(my-web-server|passing): Get http://consul:8500/v1/health/service/my-web-server?index=40&passing=1&stale=&wait=60000ms: dial tcp 172.17.0.4:8500: getsockopt: no route to host (retry attempt 2 after "500ms")

  5. ......

lb_1会不断的打印重试到http://consul:8500的健康检查。

不过此时访问 http://10.111.152.136 发现nginx并没有被破坏,还是可以正常路由到后端三个节点的,后端的web server也正常可用。没有受到一个consul server节点挂掉的影响。

只是consul web ui无法访问了,http://10.111.152.136:8500/ui/#/dc1/services 因为刚好把这个节点停掉了。

另外两个节点的日志情况

host-2机器上,consulserver-node2节点,也是一个Follower状态的节点上

 
  1. consulserver_1 | 2017/07/26 06:02:24 [INFO] memberlist: Suspect consulserver-node1 has failed, no acks received

  2. consulserver_1 | 2017/07/26 06:02:27 [INFO] memberlist: Suspect consulserver-node1 has failed, no acks received

  3. consulserver_1 | 2017/07/26 06:02:27 [INFO] memberlist: Marking consulserver-node1 as failed, suspect timeout reached

  4. consulserver_1 | 2017/07/26 06:02:27 [INFO] serf: EventMemberFailed: consulserver-node1 10.111.152.136

  5. consulserver_1 | 2017/07/26 06:02:27 [INFO] consul: removing server consulserver-node1 (Addr: 10.111.152.136:8300) (DC: dc1)

  6. consulserver_1 | 2017/07/26 06:03:19 [INFO] serf: attempting reconnect to consulserver-node1 10.111.152.136:8301

  7. consulserver_1 | 2017/07/26 06:03:49 [INFO] serf: attempting reconnect to consulserver-node1 10.111.152.136:8301

  8. consulserver_1 | 2017/07/26 06:05:19 [INFO] serf: attempting reconnect to consulserver-node1 10.111.152.136:8301

  9. ......

每隔30s尝试重连node1.

host-3机器上,consulserver-node3节点,我们的leader

 
  1. consulserver_1 | 2017/07/26 06:02:21 [INFO] raft: aborting pipeline replication to peer 10.111.152.136:8300

  2. consulserver_1 | 2017/07/26 06:02:21 [ERR] raft: Failed to AppendEntries to 10.111.152.136:8300: EOF

  3. consulserver_1 | 2017/07/26 06:02:21 [ERR] raft: Failed to heartbeat to 10.111.152.136:8300: dial tcp 10.111.152.136:8300: connection refused

  4. consulserver_1 | 2017/07/26 06:02:21 [ERR] raft: Failed to AppendEntries to 10.111.152.136:8300: dial tcp 10.111.152.136:8300: connection refused

  5. consulserver_1 | 2017/07/26 06:02:21 [ERR] raft: Failed to heartbeat to 10.111.152.136:8300: dial tcp 10.111.152.136:8300: connection refused

  6. consulserver_1 | 2017/07/26 06:02:21 [ERR] raft: Failed to AppendEntries to 10.111.152.136:8300: dial

  7. ......

也在尝试重连,而且它间隔2s就尝试一次,频率上更快。由于一直连不上,后来干脆去掉node1节点了。

 
  1. consulserver_1 | 2017/07/26 06:02:27 [INFO] memberlist: Suspect consulserver-node1 has failed, no acks received

  2. consulserver_1 | 2017/07/26 06:02:27 [INFO] memberlist: Marking consulserver-node1 as failed, suspect timeout reached

  3. consulserver_1 | 2017/07/26 06:02:27 [INFO] serf: EventMemberFailed: consulserver-node1 10.111.152.136

  4. consulserver_1 | 2017/07/26 06:02:27 [INFO] consul: removing server consulserver-node1 (Addr: 10.111.152.136:8300) (DC: dc1)

不过虽然去掉了node1,但是其他节点依然没有放弃尝试重连node1。重连的操作一直都在继续中。

5.3.2 恢复节点

把刚才在host-1上关闭的容器重新启动

docker start 4364cd41f2ba

看看3个机器都会输出什么。

host-1上

 
  1. consulserver_1 | ==> Starting raft data migration...

  2. consulserver_1 | ==> Starting Consul agent...

  3. consulserver_1 | ==> Starting Consul agent RPC...

  4. consulserver_1 | ==> Consul agent running!

  5. consulserver_1 | Node name: 'consulserver-node1'

  6. consulserver_1 | Datacenter: 'dc1'

  7. consulserver_1 | Server: true (bootstrap: false)

  8. consulserver_1 | Client Addr: 0.0.0.0 (HTTP: 8500, HTTPS: -1, DNS: 53, RPC: 8400)

  9. consulserver_1 | Cluster Addr: 10.111.152.136 (LAN: 8301, WAN: 8302)

  10. consulserver_1 | Gossip encrypt: false, RPC-TLS: false, TLS-Incoming: false

  11. consulserver_1 | Atlas: <disabled>

  12.  
  13. ......

  14.  
  15. consulserver_1 | 2017/07/26 06:23:11 [INFO] consul: New leader elected: consulserver-node2

  16. ......

consulserver-node1节点又重新启动了,并且整个集群选举了新的leader上来:consulserver-node2

host-2上

 
  1. consulserver_1 | 2017/07/26 06:23:05 [INFO] consul: adding server consulserver-node1 (Addr: 10.111.152.136:8300) (DC: dc1)

  2.  
  3. ......

  4.  
  5. consulserver_1 | 2017/07/26 06:23:11 [INFO] consul: New leader elected: consulserver-node2

  6. ......

感知到了consulserver-node1的复活,并且也参与了选举,选出新leader,是自己,哈哈。

host-3上

 
  1. consulserver_1 | 2017/07/26 06:23:05 [INFO] consul: adding server consulserver-node1 (Addr: 10.111.152.136:8300) (DC: dc1)

  2.  
  3. ......

  4.  
  5. consulserver_1 | 2017/07/26 06:23:11 [INFO] consul: New leader elected: consulserver-node2

  6. ......

同样感知到了consulserver-node1的复活,并且也参与了选举,选出新leader。

此时ngxin依然没受影响,web服务正常。而且consul web ui也可以正常访问了。一切都恢复如初。具体这3个节点是如何选举leader和处理节点的退出和重入的,参考我另外一篇文章 consul实现的raft算法选举过程解析


创建于 2017-07-23 北京,更新于 2017-07-27 北京

该文章在以下平台同步 
- LIBERALMAN: 
- CSDN:http://blog.csdn.net/socho/article/details/75434733 
- 简书:

附录:

文中架构图都是用graphviz绘制的,附上图源码

 
  1. digraph G {

  2. size="6,6";

  3. label="services register"

  4. node [colorscheme=paired12, color=1, style=filled];

  5. register_center [label="注册中心", color=5, shape="record"]

  6. consumer [label="服务消费者", color=4, shape="record"]

  7. service [label="服务提供者", color=2, shape="record"]

  8.  
  9. consumer -> register_center [label="2.订阅"]

  10. register_center -> consumer [label="5.通知" style=dashed]

  11.  
  12. consumer -> service [label="4.调用"]

  13. consumer -> consumer [label="3.缓存" style=dashed]

  14. service -> register_center [label="1.注册"]

  15. }

  16.  
  17.  
  18. digraph G {

  19. size="6,6";

  20. label="load balance web servers"

  21. node [colorscheme=paired12, color=1, style=filled];

  22. nginx [label="nginx", color=3, shape="record"]

  23. my_web_server_1 [label="my_web_server_1", color=4, shape="record"]

  24. my_web_server_2 [label="my_web_server_2", color=4, shape="record"]

  25. my_web_server_3 [label="my_web_server_3", color=4, shape="record"]

  26.  
  27. {Client1 Client2 Client3} -> nginx [label="访问"]

  28.  
  29. nginx -> {my_web_server_1 my_web_server_2 my_web_server_3} [label="转发"]

  30. }

  31.  
  32. digraph G {

  33. size="6,6";

  34. label="Services register and find"

  35. node [colorscheme=paired12, color=1, style=filled];

  36. consul [label="consul", color=1]

  37. consul_template [label="consul_template", color=2]

  38. nginx [label="nginx", color=3, shape="record"]

  39. registrator [label="registrator", color=5]

  40. my_web_server_1 [label="my_web_server_1", color=4, shape="record"]

  41. my_web_server_2 [label="my_web_server_2", color=4, shape="record"]

  42. my_web_server_3 [label="my_web_server_3", color=4, shape="record"]

  43.  
  44. {Client1 Client2 Client3} -> nginx [label="访问"]

  45. nginx -> {my_web_server_1 my_web_server_2 my_web_server_3} [label="转发"]

  46. {my_web_server_1 my_web_server_2 my_web_server_3} -> registrator [color="red",style="dashed",label="监控"]

  47. registrator -> consul [color="red",style="dashed",label="注册"]

  48. consul -> consul_template [dir=both color=red style="dashed" label="订阅服务"]

  49.  
  50. consul_template -> nginx [color=red,style="dashed",label="配置更新"]

  51. }

  52.  
  53.  
  54. digraph G {

  55. size="6,6";

  56. label="Services register and find, consul cluster"

  57. node [colorscheme=paired12, color=1, style=filled];

  58. consul_node1 [label="consul_node1(leader)", color=7]

  59. consul_node2 [label="consul_node2", color=7]

  60. consul_node3 [label="consul_ndoe3", color=7]

  61. consul_client1 [label="consul_client1", color=7]

  62. consul_client2 [label="consul_client2", color=7]

  63. consul_client3 [label="consul_client3", color=7]

  64. consul_template [label="consul_template", color=2]

  65. nginx [label="nginx", color=3, shape="record"]

  66. registrator_1 [label="registrator_1", color=5]

  67. registrator_2 [label="registrator_2", color=5]

  68. registrator_3 [label="registrator_3", color=5]

  69. my_web_server_1 [label="my_web_server_1", color=4, shape="record"]

  70. my_web_server_2 [label="my_web_server_2", color=4, shape="record"]

  71. my_web_server_3 [label="my_web_server_3", color=4, shape="record"]

  72. my_web_server_4 [label="my_web_server_4", color=4, shape="record"]

  73. my_web_server_5 [label="my_web_server_5", color=4, shape="record"]

  74. my_web_server_6 [label="my_web_server_6", color=4, shape="record"]

  75.  
  76. {Client1 Client2 Client3} -> nginx [label="访问"]

  77. nginx -> {my_web_server_1 my_web_server_2 my_web_server_3 my_web_server_4 my_web_server_5 my_web_server_6} [label="转发"]

  78. {my_web_server_1 my_web_server_2} -> registrator_1 [color="red",style="dashed",label="监控"]

  79. {my_web_server_3 my_web_server_4} -> registrator_2 [color="red",style="dashed",label="监控"]

  80. {my_web_server_5 my_web_server_6} -> registrator_3 [color="red",style="dashed",label="监控"]

  81. registrator_1 -> consul_client1 [color="red",style="dashed",label="注册"]

  82. registrator_2 -> consul_client2 [color="red",style="dashed",label="注册"]

  83. registrator_3 -> consul_client3 [color="red",style="dashed",label="注册"]

  84. {consul_client1 consul_client2 consul_client3} -> consul_node1 [color="red",style="dashed",label="注册"]

  85. consul_node1 -> consul_node2 -> consul_node3 [dir=both style=dashed color=blue]

  86. consul_node1 -> consul_template [dir=both color=red style="dashed" label="订阅服务"]

  87.  
  88. consul_template -> nginx [color=red,style="dashed",label="配置更新"]

  89.  
  90. subgraph cluster_host_1 {

  91. label="host_1"

  92. my_web_server_1

  93. my_web_server_2

  94. registrator_1

  95. consul_client1

  96. }

  97. subgraph cluster_host_2 {

  98. label="host_2"

  99. my_web_server_3

  100. my_web_server_4

  101. registrator_2

  102. consul_client2

  103. }

  104. subgraph cluster_host_3 {

  105. label="host_3"

  106. my_web_server_5

  107. my_web_server_6

  108. registrator_3

  109. consul_client3

  110. }

  111. subgraph cluster_clu {

  112. label="consul cluster"

  113. consul_node1

  114. consul_node2

  115. consul_node3

  116. }

  117. }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值