docker-compose部署consul及使用

前言

在云计算和容器化技术发展火热的当下,对于微服务架构,服务注册与发现组件是必不可少的。在传统的服务架构中,服务的规模处于运维人员的可控范围内。当部署服务的多个节点时,一般使用静态配置的方式实现服务信息的设定。在微服务应用中,服务实例的数量和网络地址都是动态变化的,这对系统运维提出了巨大的挑战。因此,动态的服务注册与发现就显得尤为重要。

服务发现的选择

主流服务发现软件的对比:

Software Consul zookeeper etcd
服务健康检查 服务状态,内存,硬盘等 (弱)长连接,keepalive 连接心跳
多数据中心 支持 - -
kv 存储服务 支持 支持 支持
一致性 raft paxos raft
cap cp cp cp
使用接口(多语言能力) 支持 http 和 dns 客户端 http/grpc
watch 支持 全量/支持long polling 支持 支持long polling
自身监控 metrics - metrics
安全 acl /https acl https支持(弱)
spring cloud 集成 已支持 已支持 已支持
优点 1.简单易用,不需要集成sdk
2.自带健康检查
3.支持多数据中心
4.提供web管理界面
1.功能强大,不仅仅只是服务发现
2.提供watcher机制能实时获取服务提供者的状态
3.dubbo等框架支持
1.简单易用,不需要集成sdk
2.可配置性强
缺点 1.不能实时获取服务信息的变化通知 1.没有健康检查
2.需在服务中集成sdk,复杂度高
3.不支持多数据中心
1.没有健康检查
2.需配合第三方工具一起完成服务发现
3.不支持多数据中心

总的来看,目前Consul 自身功能,和 spring cloud 对其集成的支持都相对较为完善,而且运维的复杂度较为简单,所以我们选择consul作为服务发现工具。

consul介绍

Consul是基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册服务软件,采用Raft算法保证服务的一致性,且支持健康检查。
Consul采用主从模式的设计,使得集群的数量可以大规模扩展,集群间通过RPC的方式调用(HTTP和DNS)。它的结构图如下所示:
docker-compose部署consul及使用_第1张图片

  • Client:作为一个代理(非微服务实例),它将转发所有的RPC请求到Server中。作为相对无状态的服务,它不持有任何注册信息。
  • Server:作为一个具备扩展功能的代理,它将响应RPC查询、参与Raft选举、维护集群状态和转发查询给Leader等。
  • Leader-Server:一个数据中心的所有Server都作为Raft节点集合的一部分。其中Leader将负责所有的查询和事务(如服务注册),同时这些事务也会被复制到所有其他的节点。
  • Data Center:数据中心作为一个私有的,低延迟和高带宽的一个网络环境。每个数据中心会存在Consul集群,一般建议Server是3-5台(考虑到Raft算法在可用性和性能上取舍),而Leader只能唯一,Client的数量没有限制,可以轻松扩展。

Raft算法

Raft是一种基于Paxos的一致性算法。相比于Paxos,Raft设计采用了较少的状态,并且是一种更简单、更易于理解的算法。
Raft节点总会是Follower、candidate、Leader三个状态之一。Leader处理所有的查询和事务,并向Follower同步事务。Follower会将所有的RPC查询和事务转发给Leader处理,它仅从Leader接受事务的同步。数据的一致性以Leader中的数据为准实现。
在节点初始启动时,节点的Raft状态机将处于Follower状态等待来来自Leader节点的心跳。如果在一定时间周期内没有收到Leader节点的心跳,节点将发起选举。
Follower节点选举时会将自己的状态切换为Candidate,然后向集群中其它Follower节点发送请求,询问其是否选举自己成为Leader。当收到来自集群中过半数节点的接受投票后,节点即成为Leader,开始接收Client的事务处理和查询并向其它的Follower节点同步事务。Leader节点会定时向Follower发送心跳来保持其地位。
一致性模式
为了支持开发人员可能需要的各种权衡,Consul支持3种不同的一致性模式

  • Default
    Raft用到了leader约期的概念,意思是,在一个时间窗口中,leader认为自己的角色是稳定的。但是,如果leader节点与别的节点被分隔,即发生所谓“脑裂”现象,那么会有一个新的leader节点被选举出来。旧的leader节点将不能提交任何新的log entry, 但是如果它提供了对数据的读取,那么客户端读到的这些值可能是过期的。时间窗口也是有界的,时间到了,leader也会下台。
    默认模式是基于leader约期的,因此客户端可能读到过期的值。但是这种模式是对读取速度和一致性的一种取舍,牺牲了某些情况下的强一致性,以换取更高的读取速度。
  • consistent
    这种模式是强一致性模式。这种模式要求leader节点每次做读和写操作时都要与法定个数的节点去确认自己仍然是leader。 牺牲读的速度,保障了强一致性。
  • stale
    这种模式允许任何Consul server向外部提供读取操作,无论它是不是leader节点。
    这种模式特别快,但是读到过期数据的可能性非常大。这种模式允许在没有leader节点的情况下对读请求做出相应,尽管实际上这时候Raft集群已经是处于不可用状态了

Gossip协议

Gossip协议是为了解决分布式环境下监控和事件通知的瓶颈。Gossip协议中的每个Agent会利用Gossip协议互相检查在线状态,分担了服务器节点的心跳压力,通过Gossip广播的方式发送消息。
所有的Agent都运行着Gossip协议。服务器节点和普通Agent都会加入这个Gossip集群,收发Gossip消息。每隔一段时间,每个节点都会随机选择几个节点发送Gossip消息,其他节点会再次随机选择其他几个节点接力发送消息。这样一段时间过后,整个集群都能收到这条消息。
基于Raft算法,Consul提供强一致性的注册中心服务,但是由于Leader节点承担了所有的处理工作,势必加大了注册和发现的代价,降低了服务的可用性。通过Gossip协议,Consul可以很好地监控Consul集群的运行,同时可以方便通知各类事件,如Leader选择发生、Server地址变更等。
Consul用了两种不同的Gossip池。我们把这两种池分别叫做LAN池和WAN池。
LAN池
Consul中的每个数据中心有一个LAN池,它包含了这个数据中心的所有成员,包括clients和servers。LAN池用于以下几个目的:
成员关系信息允许client自动发现server, 减少了所需要的配置量。
分布式失败检测机制使得由整个集群来做失败检测这件事, 而不是集中到几台机器上。
gossip池使得类似Leader选举这样的事件变得可靠而且迅速。
WAN池
WAN池是全局唯一的,因为所有的server都应该加入到WAN池中,无论它位于哪个数据中心。由WAN池提供的成员关系信息允许server做一些跨数据中心的请求。一体化的失败检测机制允许Consul优雅地去处理:整个数据中心失去连接, 或者仅仅是别的数据中心的某一台失去了连接。

使用docker-compose部署consul集群

建立compose文件consul.yml
docker-compose文件如下:

version: '3.6'

services:
  consul1: 
    image: consul:latest
    container_name: consul1
    restart: always
    network_mode: mynet
    command: agent -server -client=0.0.0.0 -bootstrap-expect=3 -node=consul1 -datacenter=dc1
  consul2:
    image: consul:latest
    container_name: consul2
    network_mode: mynet
    restart: always
    command: agent -server -client=0.0.0.0 -retry-join=consul1 -node=consul2 -datacenter=dc1
  consul3:
    image: consul:latest
    container_name: consul3
    network_mode: mynet
    restart: always
    command: agent -server -client=0.0.0.0 -retry-join=consul1 -node=consul3 -datacenter=dc1
  consul4:
    image: consul:latest
    container_name: consul4
    network_mode: mynet
    restart: always
    ports:
      - 8500:8500
    command: agent -client=0.0.0.0 -retry-join=consul1 -ui -node=client1 -datacenter=dc1
  consul5:
    image: consul:latest
    container_name: consul5
    network_mode: mynet
    restart: always
    command: agent -server -client=0.0.0.0 -bootstrap-expect=3 -node=consul5 -datacenter=dc2
  consul6:
    image: consul:latest
    container_name: consul6
    network_mode: mynet
    restart: always
    command: agent -server -client=0.0.0.0 -retry-join=consul5 -node=consul6 -datacenter=dc2
  consul7:
    image: consul:latest
    container_name: consul7
    network_mode: mynet
    restart: always
    command: agent -server -client=0.0.0.0 -retry-join=consul5 -node=consul7 -datacenter=dc2
  consul8:
    image: consul:latest
    container_name: consul8
    network_mode: mynet
    restart: always
    ports:
      - 8501:8500
    command: agent -client=0.0.0.0 -retry-join=consul5 -ui -node=client2 -datacenter=dc2
    

可以看到network_mode是我自己建立的一个网段,也可以使用已有的网段,建立网段的命令如下:

docker network create --driver bridge --subnet 10.10.0.0/24  mynet

安装docker-compose,

curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

上面安装的是稳定版本1.23.2的 docker-compose,可以到github上找最新版 https://github.com/docker/compose/releases

然后使用命令启动:

$ docker-compose -f compose文件名称 up -d

我们这里部署了两个数据中心,使用命令查看各自数据中心的成员:

//以consul1为例
$ docker exec consul1 consul members

打印如下:

 Node     Address          Status  Type    Build  Protocol  DC   Segment
consul1  172.19.0.6:8301  alive   server  1.4.0  2         dc1  <all>
consul2  172.19.0.8:8301  alive   server  1.4.0  2         dc1  <all>
consul3  172.19.0.5:8301  alive   server  1.4.0  2         dc1  <all>
client   172.19.0.2:8301  alive   client  1.4.0  2         dc1  <default>

可以看到显示了dc1数据中心的所有角色。
而数据中心的WAN池只显示server的成员,想要两个数据中心之间建立通信,只需要加入同一个WAN池就可以,命令如下:

//以dc1为例,加入dc2
$ docker exec consul1 consul join -wan consul5

查看WAN池的成员:

$ docker exec consul1 consul members -wan

打印如下:

Node         Address          Status  Type    Build  Protocol  DC   Segment
consul1.dc1  172.19.0.6:8302  alive   server  1.4.0  2         dc1  <all>
consul2.dc1  172.19.0.8:8302  alive   server  1.4.0  2         dc1  <all>
consul3.dc1  172.19.0.5:8302  alive   server  1.4.0  2         dc1  <all>
consul5.dc2  172.19.0.4:8302  alive   server  1.4.0  2         dc2  <all>
consul6.dc2  172.19.0.7:8302  alive   server  1.4.0  2         dc2  <all>
consul7.dc2  172.19.0.3:8302  alive   server  1.4.0  2         dc2  <all>

这样应用程序连接dc1也可以调用dc2的服务实例,但是数据的一致性还是只存在于各自的集群之中,也是说如果dc2的server全部挂掉,dc1中是没有dc2的服务实例的,这个时候也就无法调用dc2的服务。

ACL

ACL是Consul用来控制访问API与data的。
首先,我们创建一个uuid,也可以使用consul自带的生成key的功能

$ docker exec consul1 consul genkey

使用这个token,我们创建一个acl.json和acl_client.json文件,分别给与server节点和client节点使用
acl.json:

{
    "acl_datacenter": "dc1",
    "acl_master_token": "16c5413b-276c-45ab-9b0e-9664126f1161",
    "acl_default_policy": "deny"
}

acl_client.json

{
  "acl_datacenter": "dc1",
  "acl_token": "16c5413b-276c-45ab-9b0e-9664126f1161"
}

我这里比较懒,继续使用之前的docker-compose文件,所以要先关闭之前的容器

$ docker-compose -f compose文件 down

然我们修改一下compose文件,这里为了方便就不加第二个数据中心了

version: '3.6'

services:
  consul1:
    image: consul:latest
    container_name: consul1
    restart: always
    network_mode: mynet
    command: agent -server -client=0.0.0.0 -bootstrap-expect=3 -node=consul1 -config-dir=/home/acl.json
    volumes:
      - /opt/consul/acl.json:/home/acl.json
  consul2:
    image: consul:latest
    container_name: consul2
    network_mode: mynet
    restart: always
    command: agent -server -client=0.0.0.0 -retry-join=consul1 -node=consul2 -config-dir=/home/acl.json
    volumes:
      - /opt/consul/acl.json:/home/acl.json
  consul3:
    image: consul:latest
    container_name: consul3
    network_mode: mynet
    restart: always
    command: agent -server -client=0.0.0.0 -retry-join=consul1 -node=consul3 -config-dir=/home/acl.json
    volumes:
      - /opt/consul/acl.json:/home/acl.json
  consul4:
    image: consul:latest
    container_name: consul4
    network_mode: mynet
    restart: always
    ports:
      - 8500:8500
    command: agent -client=0.0.0.0 -retry-join=consul1 -ui -node=client1 -config-dir=/home/acl_client.json
    volumes:
      - /opt/consul/acl_client.json:/home/acl_client.json

然后启动,这个时候无论是服务注册还是rpc调用都需要有token才能调用,打开ui界面:
http://IP:8500
使用我们刚才的token进入,可以在acl里面创建相应权限的角色,这个在官方网站有详细的说明,然后给这个角色创建一个token,我们的服务注册的时候就可以在配置中加入这个acl_token,然后调用接口或注册服务。

这里说明一下,我这是演示用法,因为是一个虚拟机部署了全部的服务并且一次性全部,所以用的全是同一个token,在实际的生产过程中,流程应该是先启动一台server,然后进入ui界面给每一台server生成一个token并配置相应的权限,然后再启动加入集群,服务实例也是一样,应该给每个服务创建token并配置权限,这样才比较安全

负载均衡策略

由于同一服务的多个实例常常在微服务体系架构中同时运行,因此我们需要一种策略,以便在处理健康状态的更改、实例数量的更改和集群状态的更改时,均衡地平衡服务的所有健康实例的流量。这是负载均衡层的工作。下面说一下Consul负载均衡的一些常见策略
Consul Directly
使用Consul的内置负载均衡功能。Consul集成健康检查与服务发现。这意味着不健康的主机永远不会从查询返回到服务发现层。在这种模式下,每当应用程序和服务希望在数据中心中找到其他服务时,它们都直接与Consul进行通信。
每当应用程序或内核解析DNS条目时,它将收到一组IP地址的随机round-robin响应,这些地址对应于集群中的健康服务。DNS接口基本上提供了与任何应用程序的零接触服务发现集成。
优点

  • 不依赖外部工具或进程
  • 不需要监视或维护其他服务
  • 默认高度可用
  • 尽可能接近实时
  • DNS很容易使用,工作量很小
  • 健康检查是分布式的,集群负载最小

缺点

  • 单点故障——即使Consul在默认情况下高度可用,如果Consul不可用或不可访问,这种模式不会提供故障转移
  • 要求直接在应用程序中使用HTTP API或进行DNS查询,并假设端口或进行两个DNS查询以查找地址和端口
  • 应用程序和Consul之间的紧耦合

Fabio
Fabio是一个开源工具,它为Consul管理的服务提供了快速、现代、零配置负载均衡HTTP(S)和TCP路由器。用户注册服务和健康检查到Consul,fabio将自动路由流量到他们,不需要额外的配置。

优点

  • 与Consul的丰富整合
  • 比DNS方法更能控制负载均衡
  • 访问日志和许多其他很棒的特性
  • 集成HashiCorp Vault
  • 用于路由可视化的可选Web UI
  • 非常详细的开源文档

缺点

  • 需要额外的服务来运行和监视
  • 与Consul和Consul标签紧耦合

Nginx/HAProxy与Consul Template
Consul负载均衡的另一种方法是使用第三方工具(如Nginx或HAProxy)来平衡流量,以及使用开源工具(如 Consul Template)来管理配置。在这种模式下, Consul Template动态地管理nginx.conf或haproxy.conf配置文件,它定义负载均衡器和服务器列表。这个列表是模板化的,Consul Template作为一个服务运行,以保持模板是最新的。Consul Template与Consul集群有持续的连接,当发生对服务池的更改时, Consul Template写入一个新的配置文件,并向Nginx或HAProxy进程发送信号,以重新加载其配置。

优点

  • 处理在非默认端口上运行的应用程序,而不需要额外的API请求
  • Nginx和HAProxy都是经过实战检验的工具
  • 组织可能已经拥有使用这些工具的专业知识或现有基础结构
  • 如果Consul离线,仍然有记录的最后已知的良好状态的服务
  • Consul Template还集成了Vault,如果配置文件具有TLS私钥或共享密码等机密数据,那么这是一个理想的解决方案

缺点

  • 需要两个额外的服务来管理和监控- Nginx/HAProxy和Consul Template
  • 由于阻塞查询,低效的模板会给Consul集群带来巨大的压力
  • 实践“每个容器一个服务”范式具有挑战性
  • 一个“flappy”集群(一个服务在健康和不健康之间切换的集群,或者一个持续快速变化的集群)可能会导致Nginx/HAproxy配置的不稳定性
ps:上面这些是为了做演讲写的,所以理论多些,很多也是简单带过,并不能作为一个操作教程,如果遇到问题可以留言讨论,或直接联系我;QQ:345753283

你可能感兴趣的:(开发部署)