当前规划中,只启动一个HAProxy服务,主要用来做MySQL节点的负载均衡和代理,但是HAProxy可能会出现单点故障,后续需要启动多个HAProxy节点,然后结合Keepalived来进行 设置虚拟IP 做故障转移
节点名称 | 节点主机名 | IP地址 | 角色 | 运行服务 |
---|---|---|---|---|
cluster01 | cluster01 | 192.168.12.48 | Docker Swarm Manager & Node | HAProxy MySQL-Node01 |
cluster01 | cluster02 | 192.168.12.49 | Docker Swarm Manager & Node | MySQL-Node02 |
cluster01 | cluster03 | 192.168.12.50 | Docker Swarm Manager & Node | MySQL-Node03 |
下面部署文档中,需要执行的指令所在节点,将会说明在以上规划的某个节点执行,均已节点名称代替。
安装Docker和Docker Swarm。Docker是用于构建、发布和运行容器化应用程序的开源工具,而Docker Swarm是Docker的一种编排和集群管理方式,用于实现容器化应用程序的高可用性和负载均衡。
需要多节点,那么在多个节点上安装Docker和Docker Swarm
可以通过以下命令来安装Docker和Docker Swarm
cluster01、cluster02、cluster03
节点执行# 安装Docker
$ curl -sSL https://get.docker.com/ | sh
# 启动Docker
$ systemctl start docker
# 安装Docker Swarm
$ docker pull swarm:latest
创建Docker Swarm集群。Docker Swarm集群由一组Docker主机组成,其中有一台主机作为管理节点
,其它主机作为工作节点
。
如果配置多节点,需要在多个节点创建Docker Swarm集群
在
cluster01
节点执行
# 创建Docker Swarm管理节点
$ docker swarm init \
--advertise-addr=192.168.12.48
注意:三个节点我们都规划成了manager管理节点并且充当node节点,也会为了访问单点故障,所以下面执行的指令,我们将不通过已经指令执行,我们通过获取manager的加入指令来操作
在
cluster01
节点执行
$ docker swarm join-token manager
# 将指令的输出结果复制,将作为后续其他cluster02、cluster03的加入操作指令
复制上面通过docker swarm join-token manager
管理节点的初始化输出指令
在
cluster02、cluster03
节点执行
# 创建Docker Swarm其他管理节点
$ docker swarm join \
--token=TOKEN MANAGER_IP:2377
# 查询Docker Swarm集群的状态
$ docker node ls
共享存储卷,可以使用外部NFS做为共享卷来存储服务的持久化数据,也是可以Docker Swarm的本地volume卷来存储数据,这里两种方式都有具体操作,但是建议使用本地volume来存储。
下面所有步骤关于共享卷的使用,本文档选择本地Volume的方式,其他方式可以自行配置即可。
首先你需要配置一台NFS服务器,本地挂载一块大容量磁盘,并格式化分区作为NFS Server的对外共享卷,以备后续Docker Swarm来挂载作为集群共享卷使用,这里你也可以直接在Docker Swarm的启动一个节点,可以是管理节点或工作节点配置NFS Server,但是建议是额外单独的一台机器。
这里的device:/mysql-data 则为NFS Server 共享的目录,可以多个服务所使用的单独目录,用来存放不同应用的数据,这里的就以192.168.1.10位NFS服务器
cluster01
上执行共享卷挂载### 在cluster01节点执行 ##
# 配置mysql的共享卷
$ docker volume create \
--driver=rexray/nfs \
--opt type=nfs4 \
--opt o=nfsvers=4.1,addr=192.168.1.10,rw=true \
--opt device=:/mysql-data/192.168.12.48/cluster01 \
cluster01-mysql-data
cluster02
上执行共享卷挂载### 在cluster02节点执行 ##
# 配置mysql的共享卷
$ docker volume create \
--driver=rexray/nfs \
--opt type=nfs4 \
--opt o=nfsvers=4.1,addr=192.168.1.10,rw=true \
--opt device=:/mysql-data/192.168.12.49/cluster02 \
cluster02-mysql-data
cluster03
上执行共享卷挂载### 在cluster03节点执行 ##
# 配置mysql的共享卷
$ docker volume create \
--driver=rexray/nfs \
--opt type=nfs4 \
--opt o=nfsvers=4.1,addr=192.168.1.10,rw=true \
--opt device=:/mysql-data/192.168.12.50/cluster03 \
cluster03-mysql-data
Docker Swarm 的共享存储卷功能会自动在集群中的多个节点之间分布共享存储卷的数据,并保证数据的一致性和可用性。它内部使用了分布式文件系统来存储数据,并提供一个统一的接口,让容器可以访问存储卷中的数据。
总之,Docker Swarm 的共享存储卷功能可以方便地解决容器之间共享数据的问题,并保证数据的安全性和可用性。
在
cluster01、cluster02、cluster03
节点执行
# 初始化一个本地目录做数据共享
$ mkdir -p /data/mysql-data
# 执行挂载挂载映射指令
$ docker volume create --driver local \
--opt type=none \
--opt device=/data/mysql-data \
--opt o=bind \
cluster<01、02、03>-mysql-data
上面的命令将创建一个名为 cluster<01、02、03>-mysql-data 的共享存储卷,并将它挂载到本地主机的 /data/mysql-data 目录
这里的 mysql_network、rabbitmq_network、redis_network
为三个节点直接共享所使用的网络名称,分别为三个服务创建单独通信的网络,后续将会使用这个网络节点之间的网络通信,名称可以自定义,但后续在创建服务时需要查询使用
在
cluster01
节点执行
$ docker network create -d overlay --attachable mysql_network
在
cluster01、cluster02、cluster03
节点执行
$ docker pull percona/percona-xtradb-cluster:5.7.21
使用 docker run 命令来创建一个运行 Mysql 的服务,是否配置参数如下:
配置参数 | 配置值 | 解析 |
---|---|---|
-p | 3306:3306 | 指定服务映射端口,表示宿主机端口:内部容器服务端口,这里是宿主机3306映射到容器的3306端口 |
-e MYSQL_ROOT_PASSWORD | root123. | 指定MYSQL容器启动后的root管理员密码 |
-e CLUSTER_NAME | PXC | 指定PXC集群的名称,如果同属于一个集群,那么所有节点都需要保持一致 |
-e XTRABACKUP_PASSWORD | root123. | 指定备份工具的访问密码 |
-v | cluster01-mysql-data:/var/lib/mysql | 指定本地映射目录,将cluster01-mysql-data的本地volume卷映射到容器内部/var/lib/mysql目录作为数据存储目录使用【cluster01-mysql-data就是上面给mysql所创建的卷名称】 |
–name | mysql-node01 | 指定容器服务启动的名称 |
–network | mysql_network | 指定容器服务所使用的网络【mysql_network就是上面给mysql通信所创建的网络】 |
在
cluster01
节点执行
$ docker run -d -p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root123. \
-e CLUSTER_NAME=PXC \
-e XTRABACKUP_PASSWORD=root123. \
-v cluster01-mysql-data:/var/lib/mysql \
--name=mysql-node01 \
--network=mysql_network \
percona/percona-xtradb-cluster:5.7.21
通过指令查看mysql-node01的容器服务是否正常启动
# 查看mysql-node01服务的所有配置信息
$ docker inspect mysql-node01
$ docker exec -ti mysql-node01 bash
$ mysql -uroot -proot123. -e "show databases;"
执行以上指令来确认数据库服务正常启动,并可以连接使用。 如果有以下输出则表示数据库正常。
使用 docker run 命令来创建一个运行 Mysql 的服务,是否配置参数如下:
配置参数 | 配置值 | 解析 |
---|---|---|
-p | 3306:3306 | 指定服务映射端口,表示宿主机端口:内部容器服务端口,这里是宿主机3306映射到容器的3306端口 |
-e MYSQL_ROOT_PASSWORD | root123. | 指定MYSQL容器启动后的root管理员密码 |
-e CLUSTER_NAME | PXC | 指定PXC集群的名称,如果同属于一个集群,那么所有节点都需要保持一致 |
-e CLUSTER_JOIN | mysql-node01 | 指定加入主节点的集群 |
-e XTRABACKUP_PASSWORD | root123. | 指定备份工具的访问密码 |
-v | cluster02-mysql-data:/var/lib/mysql | 指定本地映射目录,将cluster02-mysql-data的本地volume卷映射到容器内部/var/lib/mysql目录作为数据存储目录使用【cluster02-mysql-data就是上面给mysql所创建的卷名称】 |
–name | mysql-node02 | 指定容器服务启动的名称 |
–network | mysql_network | 指定容器服务所使用的网络【mysql_network就是上面给mysql通信所创建的网络】 |
在
cluster02
节点执行
$ docker run -d -p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root123. \
-e CLUSTER_NAME=PXC \
-e CLUSTER_JOIN=mysql-node01 \
-e XTRABACKUP_PASSWORD=root123. \
-v cluster02-mysql-data:/var/lib/mysql \
--name=mysql-node02 \
--network=mysql_network \
percona/percona-xtradb-cluster:5.7.21
通过指令查看mysql-node02的容器服务是否正常启动
# 查看mysql-node02服务的所有配置信息
$ docker inspect mysql-node02
$ docker exec -ti mysql-node02 bash
$ mysql -uroot -proot123. -e "show databases;"
使用 docker run 命令来创建一个运行 Mysql 的服务,是否配置参数如下:
配置参数 | 配置值 | 解析 |
---|---|---|
-p | 3306:3306 | 指定服务映射端口,表示宿主机端口:内部容器服务端口,这里是宿主机3306映射到容器的3306端口 |
-e MYSQL_ROOT_PASSWORD | root123. | 指定MYSQL容器启动后的root管理员密码 |
-e CLUSTER_NAME | PXC | 指定PXC集群的名称,如果同属于一个集群,那么所有节点都需要保持一致 |
-e CLUSTER_JOIN | mysql-node01 | 指定加入主节点的集群 |
-e XTRABACKUP_PASSWORD | root123. | 指定备份工具的访问密码 |
-v | cluster03-mysql-data:/var/lib/mysql | 指定本地映射目录,将cluster03-mysql-data的本地volume卷映射到容器内部/var/lib/mysql目录作为数据存储目录使用【cluster03-mysql-data就是上面给mysql所创建的卷名称】 |
–name | mysql-node03 | 指定容器服务启动的名称 |
–network | mysql_network | 指定容器服务所使用的网络【mysql_network就是上面给mysql通信所创建的网络】 |
在
cluster02
节点执行
$ docker run -d -p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root123. \
-e CLUSTER_NAME=PXC \
-e CLUSTER_JOIN=mysql-node01 \
-e XTRABACKUP_PASSWORD=root123. \
-v cluster03-mysql-data:/var/lib/mysql \
--name=mysql-node03 \
--network=mysql_network \
percona/percona-xtradb-cluster:5.7.21
通过指令查看mysql-node03的容器服务是否正常启动
# 查看mysql-node03服务的所有配置信息
$ docker inspect mysql-node03
$ docker exec -ti mysql-node03 bash
$ mysql -uroot -proot123. -e "show databases;"
完成以上cluster01、cluster02、cluster03三个节点的PXC服务的启动,并且都可以登录访问数据库,这里再次确认整个数据库集群是否已经初始化完成,所有节点都已经同步至数据,切集群状态是正常状态;
在 cluster01 节点指令以下查询指令
$ docker exec -ti mysql-node01 bash
$ mysql -uroot -proot123. -e "show status like 'wsrep%';"
查询指令输出如下:
+----------------------------------+------------------------------------------------+
| Variable_name | Value |
+----------------------------------+------------------------------------------------+
| wsrep_local_state_uuid | 3b982143-7c5f-11ed-86d5-b77967adc009 |
| wsrep_protocol_version | 8 | |
| wsrep_local_state | 4 |
| wsrep_local_state_comment | Synced |
| wsrep_cert_index_size | 0 |
| wsrep_cert_bucket_count | 22 |
| wsrep_gcache_pool_size | 1456 |
| wsrep_causal_reads | 0 |
| wsrep_cert_interval | 0.000000 |
| wsrep_ist_receive_status | |
| wsrep_ist_receive_seqno_start | 0 |
| wsrep_ist_receive_seqno_current | 0 |
| wsrep_ist_receive_seqno_end | 0 |
| wsrep_incoming_addresses | 10.0.13.26:3306,10.0.13.23:3306,10.0.13.6:3306 |
| wsrep_desync_count | 0 |
| wsrep_evs_delayed | |
| wsrep_evs_evict_list | |
| wsrep_evs_repl_latency | 0/0/0/0/0 |
| wsrep_evs_state | OPERATIONAL |
| wsrep_gcomm_uuid | 4830fe4c-7c5f-11ed-9ca9-c23a8b01a1d4 |
| wsrep_cluster_conf_id | 11 |
| wsrep_cluster_size | 3 |
| wsrep_cluster_state_uuid | 3b982143-7c5f-11ed-86d5-b77967adc009 |
| wsrep_cluster_status | Primary |
| wsrep_connected | ON |
| wsrep_local_bf_aborts | 0 |
| wsrep_local_index | 1 |
| wsrep_provider_name | Galera |
| wsrep_provider_vendor | Codership Oy <[email protected]> |
| wsrep_provider_version | 3.26(rac090bc) |
| wsrep_ready | ON |
+----------------------------------+------------------------------------------------+
如果执行查询输出中,wsrep_incoming_addresses
值为三个节点的对应容器服务IP地址,则表述PXC的数据集群状态正常。也可以通过 wsrep_cluster_size
值为3,则表示目标集群中3个节点在线。
测试三个节点数据库是否在任意一个节点执行操作,其他数据库都可以同步,包含库、表等,达到底层的强一致性。
最终结果:在任意一个节点连接数据库执行操作,其他库都可以同步执行操作和数据。
三个节点的PXC MySQL服务集群正常之后,已经完成高可用配置,但是在使用过程中,不能让使用MySQL的程序连接三个地址,所以我们还需要一个代理服务器,来配置代理转发做到负载均衡,在使用中客户端只需要连接代理服务器的地址,由代理服务器根据策略来负责分发到后台的三个节点进行数据库的访问,而且可以持续监测后台三个PXC MySQL服务的状态,如果某一个节点挂掉之后,自动负载到其他可用节点。上线之后重新加入到集群,并持续监听,来实现负载均衡高可用。
做代理服务可以有多种选,例如:HAProxy、ProxySQL、Nginx都可以实现,这里我们选择HAProxy来实现代理服务,其他配置实现可以自定查阅相关资料来配置。
这里我们选择最新的HAProxy的镜像,版本为2.7.0 ,其他版本可以通过访问 https://haproxy.org 来获取
具体版本:HAProxy version 2.7.0-437fd28 2022/12/01
在
cluster01
节点执行
目前只在一个节点HAProxy服务,会存在一个单点故障的问题,由于所有客户端都会通过HAProxy来转发到后端服务,所以后续将结合Keepalived来实现高可用。
$ docker pull haproxy:latest
HAProxy镜像服务内部没有配置文件,需要在容器启动之前将haproxy.cfg配置文件按照需要配置完成,再启动服务时映射到容器内部使用
在
cluster01
节点执行
$ mkdir -p /data/haproxy
MySQL中创建一个没有权限的haproxy用户,密码为空。Haproxy使用这个账户对MySQL数据库心跳检测
在
cluster01
节点执行
# 登录到数据库容器
$ docker exec -it mysql-node01 bash
#登录mysql
$ mysql -u root -proot123.
#指定数据库
mysql> use mysql;
#创建用户
mysql> create user 'haproxy'@'%' identified by '';
# 刷新权限
mysql> flush privileges;
配置文件中,需要修改的内容主要是 listen proxy-mysql
段,这里需要将PXC的三个节点的MySQL地址进行配置,具体IP地址为容器的内部地址,可以在每个节点通过**docker inspect mysql-node<01/02/03>
**来获取,也可以通过前边查询数据库集群状态的输出的wsrep_incoming_addresses
值来获取,IP地址获取后配置到文件中即可,其他haproxy的配置参数,也可以根据具体环境来进行配置。
这里配置的负载模式为:roundrobin,轮训算法,可以根据实际环境自行配置其他模式即可
在
cluster01
节点执行
$ vim /data/haproxy/haproxy.cfg
# ---- 配置文件内容如下 ----
global
#日志文件,使用rsyslog服务中local5日志设备(/var/log/local5),等级info
log 127.0.0.1 local5 info
#守护进程运行
daemon
defaults
log global
mode http
#日志格式
option httplog
#日志中不记录负载均衡的心跳检测记录
option dontlognull
#连接超时(毫秒)
timeout connect 5000
#客户端超时(毫秒)
timeout client 50000
#服务器超时(毫秒)
timeout server 50000
#监控界面
listen admin_stats
#监控界面的访问的IP和端口
bind 0.0.0.0:8888
#访问协议
mode http
#URI相对地址
stats uri /dbs
#统计报告格式
stats realm Global\ statistics
#登陆帐户信息, 用于web浏览器的访问用户名密码
stats auth admin:root123.
#数据库负载均衡
listen proxy-mysql
#访问的IP和端口
bind 0.0.0.0:3306
#网络协议
mode tcp
#负载均衡算法(轮询算法)
#轮询算法:roundrobin
#权重算法:static-rr
#最少连接算法:leastconn
#请求源IP算法:source
balance roundrobin
#日志格式
option tcplog
#在MySQL中创建一个没有权限的haproxy用户,密码为空。Haproxy使用这个账户对MySQL数据库心跳检测
option mysql-check user haproxy
server MySQL_Node01 10.0.13.23:3306 check weight 1 maxconn 2000 inter 5000 rise 2 fall 2
server MySQL_Node02 10.0.13.26:3306 check weight 1 maxconn 2000 inter 5000 rise 2 fall 2
server MySQL_Node03 10.0.13.6:3306 check weight 1 maxconn 2000 inter 5000 rise 2 fall 2
#使用keepalive检测死链
option tcpka
使用 docker run 命令来创建一个运行 HAProxy 的服务,是否配置参数如下:
配置参数 | 配置值 | 解析 |
---|---|---|
-p | 4001:8888 | 指定服务映射端口,表示宿主机端口:内部容器服务端口,这里是宿主机4001映射到容器的8888端口,这个端口后续用来访问haproxy服务的浏览器可视化监控页面 |
-p | 4002:3306 | 指定服务映射端口,表示宿主机端口:内部容器服务端口,这里是宿主机4002映射到容器的3306端口,这个端口主要对外客户端访问外部端口,通过这个端口转发至容器内部来访问后端的3个MySQL集群。 |
-v | /data/haproxy:/usr/local/etc/haproxy | 指定本地映射目录,将/data/haproxy的本地目录映射到容器内部/usr/local/etc/haproxy目录作为haproxy的配置文件目录使用 |
–name | haproxy | 指定容器服务启动的名称 |
–network | mysql_network | 指定容器服务所使用的网络【mysql_network就是上面给mysql通信所创建的网络】 |
在
cluster01
节点执行
$ docker run -d \
-p 4001:8888 \
-p 4002:3306 \
-v /data/haproxy:/usr/local/etc/haproxy \
--name=haproxy \
--net=mysql_network \
--privileged \
haproxy:latest
HAProxy服务启动后,可以通过访问cluster01节点宿主机的http://:4001/dbs来访问HAProxy服务的浏览器可视化监控页面来查看后端MySQL服务的监测及数据转发负责情况,通过页面可以看到目前后端的节点数量以及状态是否正常等相关信息。
截止到当前,MySQL的PXC+HAProxy实现的高可用负载集群就配置完成,那么内部及外部其他应用客户端可以访问HAProxy服务的对外地址及端口来访问后端的MySQL数据库。
外部服务访问地址:192.168.12.48 4002 端口,即可访问到数据库,后面就由HAProxy来根据配置的轮训算法来转发到后端的三台MySQL数据库服务**(192.168.12.48 为cluster01节点宿主机的IP地址)**
说明:mysql-node01宕机后,集群正常运行,会选举出新的主节点。如果修改参数safe_to_bootstrap=1以最开始mysql-node01为主节点创建启动PXC的命令那么会分裂新的集群,毫无意义。
$ docker rm mysql-node01
$ docker run -d -p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root123. \
-e CLUSTER_NAME=PXC \
-e CLUSTER_JOIN=mysql-node02 \
-e XTRABACKUP_PASSWORD=root123. \
-v cluster01-mysql-data:/var/lib/mysql \
--name=mysql-node01 \
--network=mysql_network \
percona/percona-xtradb-cluster:5.7.21
# 重点:‐e CLUSTER_JOIN=mysql-node02
在
cluster01
节点执行
# 创建cluster01-mysql-data-node04卷
$ docker volume create cluster01-mysql-data-node04
# 创建服务
$ docker run -d -p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root123. \
-e CLUSTER_NAME=PXC \
-e CLUSTER_JOIN=mysql-node02 \
-e XTRABACKUP_PASSWORD=root123. \
-v cluster01-mysql-data-node04:/var/lib/mysql \
--name=mysql-node04 \
--network=mysql_network \
percona/percona-xtradb-cluster:5.7.21
以下根据创建的cluster集群做宕机分析及重启操作。
场景1:停掉mysql-node02(或者mysql-node03),stop成功后重启
在
cluster02
节点执行
$ docker stop mysql-node02
确认容器挂掉后重启,使用start 或者restart,结果是可以启动成功的,并且其他节点的数据会同步成功。
$ docker start mysql-node02
**结论:**mysql-node01正常运作的情况下 ,mysql-node02或者mysql-node03宕机都是可以通过start命令成功重启,并同步增量数据。
场景2:停掉mysql-node01,保持mysql-node02及mysql-node03正常运行。stop成功后重启
在
cluster01
节点执行
$ docker stop mysql-node01
确认容器挂掉后重启,使用start 或者restart,结果是无法重启成功的,启动后会闪退。
docker start mysql-node01
结论: mysql-node01作为启动时的主节点,如果优先其他宕机,无法通过start类命令启动成功 。这是什么原因?网上有一些成功启动的方法,修改cluster01-mysql-data卷下文件grastate.dat的参数safe_to_bootstrap=1后,可以重启成功, 但是在实际使用中毫无意义 。在实际项目中,因为在mysql-node01宕机后,mysql-node02及mysql-node03必然会继续工作写入数据等做操作。safe_to_bootstrap是pxc集群安全策略的参数:”是否是安全启动节点”。 如果强制修改文件的参数启动后,会发现mysql-node02及mysql-node03在mysql-node01宕机后产生的数据不会同步到mysql-node01。因为以这种强制方式启动mysql-node01会直接分裂成第二个集群,与之前搭建的集群不产生任何关系。正确操作方式参考 12.1.1. 主节点故障 章节内容
场景3:停掉mysql-node01后,再停掉mysql-node02,保持mysql-node03正常运行。stop成功后,重启mysql-node02重启
$ docker stop mysql-node01
$ docker stop mysql-node02
确认容器挂掉后重启,使用start 或者restart,结果是无法重启成功的,启动后会闪退。
docker start mysql-node02
为什么mysql-node02节点也启动不了?原因是创建mysql-node02容器的时候,mysql-node02加入的是以mysql-node01为主节点的集群,现在mysql-node01运行不正常,当然mysql-node02无法启动。
结论: 这个场景仍需要将mysql-node01、mysql-node02 按照 12.1.1. 主节点故障 章节内容,将两个节点进行重建,重建时指定-e CLUSTER_JOIN=mysql-node02 加入mysql-node03节点,则可以正常加入集群,并重新同步数据。
可以参考如下文章,使用6个案例来说明PXC集群故障的恢复方法。
用6个案例说明如何恢复PXC集群