阅读本文前需要先了解consul的基础架构。
注意,dockerhub-consul 提到:在正常的生产环境中,会在每个主机上运行一个Consul容器。consul在docker应该始终以--network=host
模式运行,因为Consul 的共识和 Gossip
协议对延迟和数据包丢失很敏感,所以其他网络类型涉及的额外层通常是不可取的,也是不必要的。
使用host模式,Consul容器就和主机(服务器)共享同一个Network Namespace
(网络命名空间)了,这可以减少额外的开销,而且不需要再配置那么多端口映射(很容易遗漏)。但是我没有这么多服务器,所以接下来并不会使用host模式(用和不用的区别并不大)。
目的:在一个服务器上,用docker搭建consul集群。
方法分析:利用docker的network-bridge网桥,同一个network-bridge
上的容器位于同一网段,拥有独立的ip和端口范围。以此实现在一台机器上搭建集群。
docker-network
进行内网通信。8500
端口。8500
映射到主机的8500
(因为应用程序与client连接,而不是与server连接,不改变默认端口看起来会更简单),而把server的8500
映射到其它端口,如8501
、8502
等。docker-network
进行内网通信。8301
、8300
端口进行内部通信,但因为它们都处于同一docker-network
下,可以自由通信,因此并不需要我们特别关心这些端口。docker-entrypoint.sh
以下是一个简单的consul容器运行命令,容器的默认启动命令是docker-entrypoint.sh agent -dev -client=0.0.0.0
docker run -d -p 8500:8500 consul
consul容器始终使用docker-entrypoint.sh
脚本执行启动命令,无论是默认命令的还是我们指定的命令。这是通过dockerfile的ENTRYPOINT
指令实现的。
docker-entrypoint.sh
脚本进行了一些初始封装,此脚本在容器内的路径:/usr/local/bin/docker-entrypoint.sh
,可以查看其详细内容。
自定义启动命令部分可以直接写agent
,等同于consul agent
。
docker run -d consul agent ...
-data-dir
被设置为/consul/data
, -config-dir
被设置为/consul/config
,因此这两个选项不再需要我们手动配置,只需要按需进行挂载卷即可。(dockerhub-consul也提到了这两个可直接挂载的目录)
# 创建network
docker network create --subnet 172.10.0.0/16 --gateway 172.10.0.1 consul-network
# 这里指定了16位子网掩码,因此子网ip范围是 172.10.0.0 - 172.10.255.255
docker network 有两个bug(issuse):
- 不用
--subnet
创建的网络,启动容器时无法使用--ip
指定静态ip,但这篇文档写的可以。- 使用
--subnet
创建的网络,不会自动设置网关(但这篇文档写的会自动设置),需要用--gateway
指定。
之所以说"不会自动设置",根据是:用docker inspect
命令查看network的信息看不到gateway
。不过,尽管如此,但如果把容器加入到网络,容器内执行route -n
命令又能看到网关地址,默认是subnet
的.1
,如172.10.0.1
。
启动命令。创建server-1
、server-2
、server-3
,一个高可用集群至少要有3个server节点。
# (不使用docker时的)基础格式是。
consul agent -server -node=<node_name> -bind=<listen_ip> [-advertise=<public_ip>] -data-dir=<data_dir> [-config-dir=<config_dir>] [-bootstrap-expect=<number_of_server_agents>] -client=0.0.0.0 -ui
# 刚刚讲过,`docker-entrypoint.sh`封装了`-data-dir`和`-config-dir`,因此直接按需挂载即可。
# 启动server-1。
docker run -d --network consul-network --ip 172.10.1.1 -p 8501:8500 -v consul_conf:/consul/config --name consul-server-1 \
consul agent -server -node=server-1 -bootstrap-expect=3 -bind=172.10.1.1 -client=0.0.0.0 -ui
# 启动server-2,并加入到server-1。
docker run -d --network consul-network --ip 172.10.1.2 -p 8502:8500 -v consul_conf:/consul/config --name consul-server-2 \
consul agent -server -node=server-2 -bootstrap-expect=3 -bind=172.10.1.2 -retry-join=172.10.1.1 -client=0.0.0.0 -ui
# 启动server-3,并加入到server-1。
docker run -d --network consul-network --ip 172.10.1.3 -p 8503:8500 -v consul_conf:/consul/config --name consul-server-3 \
consul agent -server -node=server-3 -bootstrap-expect=3 -bind=172.10.1.3 -retry-join=172.10.1.1 -client=0.0.0.0 -ui
主要选项说明 (包含命令中没用到的一些选项)
-server
:表示以server模式启动。
-node
:节点名称,在集群(一个数据中心)中必须是唯一的,不能包含空格或引号。默认值是主机名。
-ui
:启用web管控台功能。(dev模式默认启用,生产模式需要指定此选项启用)
-config-dir
:consul读取配置文件的目录。
-data-dir
:consul运行时产生的数据的存储目录(必须提前创建好)。
-client
:允许访问的客户端ip,0.0.0.0
表示不限制。
配置通信地址选项
-bind
:当前节点监听的本机ip,集群内部通信的地址。会被发送到集群中的其它节点,用作与本节点通信,因此需要能被其它节点访问到。
就是tcp/http的侦听地址,必须是本机的ip(即本机某个网卡的ip),否则会报错
listen tcp xxx.xx.xxx.xx:8300: bind: can't assign requested address
。这不是consul专有的报错,而是tcp的报错,可以尝试自己动手写一个tcp/http,如果监听地址不写本机ip,就会报这个错误。
-advertise
:默认情况下,-bind
选项的地址会被发送到集群中的其它节点,用作与本节点通信。但某些情况下,可能存在无法-bind
的可路由地址,此时就可以使用-advertise
选项,consul会把此选项指定的地址(而不再是-bind
的地址)发送到集群中的其它节点,用作与本节点通信。
解释"无法
-bind
的可路由地址":例如一些云服务器的公网ip。有些云直接向服务器提供公网ip;而有些云则没有,并通过NAT转换提供公网ip的访问。对于后者,尽管可以通过公网ip访问到服务器,但无法将其设置为-bind
的值,因为这种公网ip没有被直接分配到云服务器(即服务器上没有对应公网ip的本地网卡)。
"引导和加入集群"选项。(详见关于"引导和加入集群")
-bootstrap-expect
:提供集群中预期的server节点数量。为了防止不一致和脑裂的情况,集群中所有server节点的此选项的值必须一致 或者 不指定任何的值,只有指定了值的server才会尝试引导集群。-server
。-retry-join
:指定加入到集群(集群中的任意一个agent)的地址,重试直至加入成功。-serf-lan-port
:Serf的LAN Gossip
通信的端口,默认值8301。
可以通过-serf-lan-bind
指定对应的ip,默认使用-bind
选项的ip。
-serf-wan-port
:Serf的WAN Gossip
通信的端口,默认值8302。跨数据中心使用。
可以通过-serf-wan-bind
指定对应的ip,默认使用-bind
选项的ip。
-datacenter
:控制Agent运行所属的数据中心,默认值"dc1"。
访问UI观察集群状态:ip:port/ui/
# (不使用docker时的)基础格式是。
consul agent -node=<node_name> -bind=<listen_ip> -retry-join=<server-ip> -data-dir=<data_dir> [-config-dir=<config_dir>] -client=0.0.0.0 -ui
# 启动client,并加入到server-1。
# 因为正常生产环境中会有很多client,都在集群中显示,所以我们给它的节点名编号为client-1。
docker run -d --network consul-network --ip 172.10.2.1 -p 8500:8500 -v consul_conf:/consul/config --name consul-client-1 \
consul agent -node=client-1 -bind=172.10.2.1 -retry-join=172.10.1.1 -client=0.0.0.0 -ui
访问UI观察只因群状态:ip:port/ui/
(是集群状态啦~)
这就完成了,很简单吧~
接下来,用你的应用程序连接client节点,开始使用consul集群吧~
参考文档 - Bootstrap a Datacenter
刚才我们使用-retry-join
做了一件事:加入到server-1
。如果不这么做,那么每个节点成功运行后,都还只是相互独立的,因此需要把它们关联起来,建立集群关系。
建立集群关系又可细分为两个部分:引导集群、加入集群(连接集群)。
① 引导集群
在 Consul 集群可以开始为请求提供服务之前,必须选举server节点的leader。引导,是将这些"初始server节点"建立为可用群集的过程。
引导集群有两种不同的选项:(两个选项都需要-server
,因为"引导集群"仅是server节点的功能)
选项一:-bootstrap-expect
:提供集群中预期的server节点数量。为了防止不一致和脑裂的情况,集群中所有server节点的此选项的值必须一致 或者 不指定任何的值,只有指定了值的server才会尝试引导集群。
Consul会等到有指定数量的server可用后,引导集群,自动选举初始leader,在此之前会不断地打印错误日志:No cluster leader
。
通常值设置为3或5,这是在可用性和效率之间的权衡。节点越多,数据在节点间的同步过程越长,效率越慢;节点越少,则越不能保证高可用。
选项二:-bootstrap
:此选项控制server是否处于"引导"模式。处于引导模式的server可以自选为leader。
注意:只能有一个server节点处于此模式;否则,由于多个节点进行自选,会出现脑裂,无法保证一致性。
这两个选项通常单独使用,强烈不建议混用。
推荐使用-bootstrap-expect
,因为-bootstrap
更容易出错(由于配置时的不小心)。
② 加入集群(连接集群)
加入/连接集群:无论是加入到尚未引导的集群还是已经引导的集群,方式都一样。
通过指定集群中任意一个或多个agent的ip地址加入集群,具体也有两种不同的方式:
方式一:新的节点启动时,使用-retry-join
选项加入集群,会不断重试直至加入成功。v1.15
之前可以使用-join
选项(v1.15
已弃用)。
这种方式被称为自动加入。
consul agent -retry-join=172.10.1.1 [-retry-join=<ip> ...]
方式二:新的节点启动后,使用 consul join
命令加入集群。
这种方式被称为手动加入。
consul join 172.10.1.1 [...]
"加入集群"的设计原则是:
join
指定的ip。可以指定多个要加入的节点ip,Agent会按照顺序尝试加入,直到出现第一个成功。join
另一个集群中的Agent,这会导致两个集群合并为单个集群。join
时指定的ip不必须是相同的,只要新的Agent能成功join
到集群中的任何一个Agent,就能加入到集群,此后它们仅通过Gossip维持集群状态。集群操作命令
consul members
:查询集群成员列表及其状态。
consul leave
:离开集群。
集群的使用
使用时,集群和dev模式没有太大的区别。
docker compose 的优点就不必多说了,直接上配置!
编写docker-compose.yml
文件:
(为了保证稳定性,可以指定镜像的版本。不然说不定哪次版本更新之后这个配置就不能用了呢~)
version: '1.0'
name: consul-cluster
services:
consul-server-1:
image: consul:1.15
ports:
- 8501:8500
volumes:
- consul_conf:/consul/config
networks:
consul-network:
ipv4_address: 172.10.1.1
container_name: compose-consul-server-1
command: agent -server -node=server-1 -bootstrap-expect=3 -bind=172.10.1.1 -client=0.0.0.0 -ui
consul-server-2:
image: consul:1.15
ports:
- 8502:8500
volumes:
- consul_conf:/consul/config
networks:
consul-network:
ipv4_address: 172.10.1.2
container_name: compose-consul-server-2
command: agent -server -node=server-2 -bootstrap-expect=3 -bind=172.10.1.2 -retry-join=172.10.1.1 -client=0.0.0.0 -ui
consul-server-3:
image: consul:1.15
ports:
- 8503:8500
volumes:
- consul_conf:/consul/config
networks:
consul-network:
ipv4_address: 172.10.1.3
container_name: compose-consul-server-3
command: agent -server -node=server-3 -bootstrap-expect=3 -bind=172.10.1.3 -retry-join=172.10.1.1 -client=0.0.0.0 -ui
consul-client-1:
image: consul:1.15
ports:
- 8500:8500
volumes:
- consul_conf:/consul/config
networks:
consul-network:
ipv4_address: 172.10.2.1
container_name: compose-consul-client-1
command: agent -node=client-1 -bind=172.10.2.1 -retry-join=172.10.1.1 -client=0.0.0.0 -ui
volumes:
consul_conf:
networks:
consul-network:
ipam:
config:
- subnet: 172.10.0.0/16
gateway: 172.10.0.1
启动
在docker-compose.yml
文件所在目录指定启动命令:
docker compose up -d
ro~ 的一下就启动了