前言 本文主要介绍如何使用 Docker 部署 Percona XtraDB Cluster 5.7 集群(单机三个节点),并基于 Haproxy + Keepalived 实现双机热备方案。
系列教程 官方文档 部署准备 整体部署架构 提示
PXC + Haproxy + Keepalived 双机热备架构的介绍可以阅读 这里 的内容。
整体部署规划 软件 版本 描述 PXC 镜像 5.7.43 Haproxy 镜像 2.8.3 Keepalived 服务 1.3.5
Docker 部署 PXC 集群(三个节点)+ Haproxy(两个节点 - 主备) 节点名称 容器名称 容器 IP 容器数据卷 容器数据卷目录 操作系统 PXC 节点一 pxc-node1 172.30.0.2 pxc-v1 /var/lib/docker/volumes/pxc-v1/_data/
Debian 11 PXC 节点二 pxc-node2 172.30.0.3 pxc-v2 /var/lib/docker/volumes/pxc-v2/_data/
Debian 11 PXC 节点三 pxc-node3 172.30.0.4 pxc-v3 /var/lib/docker/volumes/pxc-v3/_data/
Debian 11 Haproxy 节点一 haproxy-node1 172.30.0.5 haproxy-v1 /var/lib/docker/volumes/haproxy-v1/_data/
Debian 11 Haproxy 节点二 haproxy-node2 172.30.0.6 haproxy-v2 /var/lib/docker/volumes/haproxy-v2/_data/
Debian 11
服务器名称 服务器角色 虚拟 IP 说明 操作系统 Keepalived 服务器一 主服务器(MASTER) 172.30.0.7 安装在 Haproxy 节点一的容器内(haproxy-node1
) Debian 11 Keepalived 服务器二 备服务器(BACKUP) 172.30.0.7 安装在 Haproxy 节点二的容器内(haproxy-node2
) Debian 11 Keepalived 服务器三 192.168.1.160 安装在宿主机内,为了实现外网可以正常访问 Docker 容器内的虚拟 IP Centos 7
概念介绍 虚拟 IP 概念介绍 Linux 系统支持在一个网卡中定义多个 IP 地址,并将这些地址分配给多个应用程序,这些地址就是虚拟 IP,基于 Haproxy + Keepalived 的双机热备方案最关键的技术就是虚拟 IP。
Keepalived 利用了上述 Linux 系统的特性,让多台服务器去获取同一个虚拟 IP,获取到的服务器将虚拟 IP 绑定到自身的网卡,然后接受外部流量;没有抢占到虚拟 IP 的则作为备用服务器,并进行心跳检测,一旦检测到主服务器宕机,则立刻抢占虚拟 IP。
负载均衡中间件选型 PXC 集群有三个节点,如果每次都是第一个节点处理请求,那么就存在负载高、性能差、其他节点利用率不高等问题,所以更优的方案是对不同的节点都进行请求。这就需要有负载均衡中间件负责请求转发,主流的中间件有 Nginx、Haproxy 等,两者都支持 TCP/IP 协议,Nginx 额外支持插件,Haproxy 属于是老牌的中间件。在数据库集群的负载均衡领域,Haproxy 会使用的要多一些。不同中间件的对比如下图所示:
Нaproxy 双机热备方案介绍
Docker 创建两个 Haproxy 容器,每个容器中都单独安装 Keepalived 服务器 两个 Keepalived 服务器会争抢 Docker 容器内的虚拟 IP,一个抢到后,另一个没抢到就会等待,抢到的作为主服务器,没抢到的作为备用服务器 两个 Keepalived 服务器之间会进行心跳检测,如果备用服务器没有接收到主服务器的心跳响应,则说明主服务器发生故障,那么备用服务器就可以抢占虚拟 IP,继续工作 客户端向虚拟 IP 发送数据库请求,当其中一个 Haproxy 节点宕机后,还有另一个 Haproxy 节点可以接替工作 由于 Docker 容器内的虚拟 IP 不能被外网直接访问,所以需要借助宿主机里的 Keepalived 服务映射成外网可以访问的虚拟 IP PXC 集群搭建 在本节中,将通过 Docker 部署 PXC 5.7 集群,其中包含三个集群节点。
提示
Percona Xtradb Cluster (PXC) 的详细介绍请看 这里 的教程。
部署规划 节点名称 容器名称 容器 IP 容器数据卷 容器数据卷目录 操作系统 PXC 节点一 pxc-node1 172.30.0.2 pxc-v1 /var/lib/docker/volumes/pxc-v1/_data/
Debian 11 PXC 节点二 pxc-node2 172.30.0.3 pxc-v2 /var/lib/docker/volumes/pxc-v2/_data/
Debian 11 PXC 节点三 pxc-node3 172.30.0.4 pxc-v3 /var/lib/docker/volumes/pxc-v3/_data/
Debian 11
提示
Percona XtraDB Cluster 要求最小的集群大小是 3 个节点。 建议尽可能地控制 PXC 集群的规模,节点越多,数据同步速度越慢。 PXC 存在硬件配置短板限制,即整个集群的写吞吐量受最弱节点的限制。因此所有 PXC 节点的硬件配置要一致,否则如果一个节点变慢,整个集群会跟着变慢。 集群部署 1 2 3 4 5 sudo docker pull percona/percona-xtradb-cluster:5.7.43 sudo docker tag percona/percona-xtradb-cluster:5.7.43 pxc
创建数据卷,存储路径为 /var/lib/docker/volumes/
1 2 3 4 5 6 7 8 9 10 sudo docker volume create --name pxc-v1 sudo docker volume create --name pxc-v2 sudo docker volume create --name pxc-v3 sudo docker volume ls sudo docker volume inspect pxc-v1
1 2 3 4 5 sudo docker network create --subnet=172.30.0.0/24 pxc-network sudo docker network ls
创建容器,XTRABACKUP_PASSWORD
是 XtraBackup 工具备份数据库数据的密码 1 2 3 4 5 6 7 8 sudo docker create -p 13306:3306 -v pxc-v1:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_NAME=pxc --name=pxc-node1 --net=pxc-network --ip 172.30.0.2 pxc sudo docker create -p 13307:3306 -v pxc-v2:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_NAME=pxc -e CLUSTER_JOIN=pxc-node1 --name=pxc-node2 --net=pxc-network --ip 172.30.0.3 pxc sudo docker create -p 13308:3306 -v pxc-v3:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_NAME=pxc -e CLUSTER_JOIN=pxc-node1 --name=pxc-node3 --net=pxc-network --ip 172.30.0.4 pxc
启动节点一的容器(必须先启动节点一,等待 PXC 集群初始化操作执行完成后,再启动节点二和节点三的容器) 1 2 3 4 5 sudo docker start pxc-node1 sudo docker logs -f pxc-node1
启动节点二的容器(必须等待节点一启动成功后再启动) 1 2 3 4 5 sudo docker start pxc-node2 sudo docker logs -f pxc-node2
启动节点三的容器(必须等待节点一启动成功后再启动) 1 2 3 4 5 sudo docker start pxc-node3 sudo docker logs -f pxc-node3
集群验证 登录 MySQL 登录 MySQL 数据库,可以是 PXC 集群中的任意一个节点 1 sudo docker exec -it pxc-node1 /usr/bin/mysql -uroot -p123456
查看集群状态 在 PXC 集群的任意一个节点上,执行以下 SQL 语句来查看集群状态 1 show status like 'wsrep_cluster%' ;
创建数据库表 在 PXC 集群的任意一个节点上,执行以下 SQL 语句,然后观察其他节点是否同步创建了数据库和表。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 CREATE DATABASE `percona` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;USE `percona`; CREATE TABLE `acl_user` ( `id` char (19 ) NOT NULL COMMENT '会员id' , `username` varchar (20 ) NOT NULL DEFAULT '' COMMENT '微信openid' , `password` varchar (32 ) NOT NULL DEFAULT '' COMMENT '密码' , `nick_name` varchar (50 ) DEFAULT NULL COMMENT '昵称' , `salt` varchar (255 ) DEFAULT NULL COMMENT '用户头像' , `token` varchar (100 ) DEFAULT NULL COMMENT '用户签名' , `is_deleted` tinyint(1 ) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除' , `gmt_create` datetime NOT NULL COMMENT '创建时间' , `gmt_modified` datetime NOT NULL COMMENT '更新时间' , PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE= InnoDB DEFAULT CHARSET= utf8mb4 COMMENT= '用户表' ; INSERT INTO `acl_user` VALUES ('1' ,'admin' ,'96e79218965eb72c92a549dd5a330112' ,'admin' ,'' ,NULL ,0 ,'2018-05-01 10:39:47' ,'2018-05-01 10:39:47' );
Haproxy 负载均衡 在本节中,将介绍如何使用 Haproxy 作为负载均衡服务器,将请求转发给 PXC 集群中的各个节点。特别注意,在执行以下操作之前,请先启动 PXC 集群,并确保集群可以正常运行。
部署规划 节点名称 容器名称 容器 IP 容器数据卷 容器数据卷目录 操作系统 Haproxy 节点一 haproxy-node1 172.30.0.5 haproxy-v1 /var/lib/docker/volumes/haproxy-v1/_data/
Debian 11
整体架构 当使用 PXC 集群的单个节点处理所有请求时,存在负载高、性能差、其他节点利用率不高等问题。
使用 Haproxy 做负载均衡,将请求均匀地分配给 PXC 集群中的每一个节点,单节点负载低、性能高,且所有节点都能利用起来。
创建用户 在本节中,将创建 MySQL 用户,Haproxy 后续会使用这个用户对 PXC 集群节点进行心跳检测。
登录 MySQL 数据库,可以是 PXC 集群中的任意一个节点 1 sudo docker exec -it pxc-node1 /usr/bin/mysql -uroot -p123456
创建数据库用户 haproxy
,不指定密码和权限,只允许远程访问 1 2 3 4 5 6 7 8 CREATE USER 'haproxy' @'%' ; FLUSH PRIVILEGES; SELECT user, host FROM mysql.user;
应用部署 1 2 3 4 5 sudo docker pull haproxy:2.8.3 sudo docker tag haproxy:2.8.3 haproxy
创建数据卷,存储路径为 /var/lib/docker/volumes/
1 2 3 4 5 6 7 8 sudo docker volume create --name haproxy-v1 sudo docker volume ls sudo docker volume inspect haproxy-v1
1 2 3 4 5 sudo touch /var/lib/docker/volumes/haproxy-v1/_data/haproxy.cfg sudo vi /var/lib/docker/volumes/haproxy-v1/_data/haproxy.cfg
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 global # 工作目录 # chroot /usr/local/etc/haproxy # 日志文件,使用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 # 登陆账户信息 stats auth admin:admin # 数据库负载均衡 listen proxy-pxc # 访问的IP和端口 bind 0.0.0.0:3306 # 网络协议 mode tcp # 负载均衡算法(轮询算法) # 轮询算法:roundrobin # 权重算法:static-rr # 最少连接算法:leastconn # 请求源IP算法:source balance roundrobin # 日志格式 option tcplog # Haproxy使用MySQL的haproxy账户对数据库进行心跳检测 option mysql-check user haproxy server PXC_Node_1 172.30.0.2:3306 check weight 1 maxconn 2000 server PXC_Node_2 172.30.0.3:3306 check weight 1 maxconn 2000 server PXC_Node_3 172.30.0.4:3306 check weight 1 maxconn 2000 # 使用Keepalived检测死链 option tcpka
特别注意
在上述 Haproxy 的配置文件中,不能启用 chroot /usr/local/etc/haproxy
,否则 Haproxy 容器会启动失败(日志信息如下),暂时不清楚其原因。
1 2 3 4 5 6 7 8 9 10 [NOTICE] (1) : New worker (8) forked [NOTICE] (1) : Loading success. [NOTICE] (8) : haproxy version is 2.8.3-86e043a [NOTICE] (8) : path to executable is /usr/local/sbin/haproxy [ALERT] (8) : [haproxy.main()] Cannot chroot(/usr/local/etc/haproxy). [NOTICE] (1) : haproxy version is 2.8.3-86e043a [NOTICE] (1) : path to executable is /usr/local/sbin/haproxy [WARNING] (1) : Current worker (8) exited with code 1 (Exit) [ALERT] (1) : exit-on-failure: killing every processes with SIGTERM [WARNING] (1) : All workers exited. Exiting... (1)
创建并运行 Haproxy 容器,其中的 8888
是 Haproxy 监听的 HTTP 端口(用于提供监控界面的 Web 服务),3306
是 Haproxy 监听的 MySQL 端口(用于转发请求给 PXC 集群节点) 1 2 3 4 5 sudo docker run -it -p 4001:8888 -p 4002:3306 -v /var/lib/docker/volumes/haproxy-v1/_data/:/usr/local /etc/haproxy --privileged --name=haproxy-node1 --net=pxc-network --ip 172.30.0.5 -d haproxy sudo docker logs -f haproxy-node1
部署验证 访问监控界面 在宿主机内使用浏览器访问 http://192.168.1.221:4001/dbs
,打开 Haproxy 的监控界面(如下图所示),登录用户名是 admin
,登录密码是 admin
。如果可以正常访问 Haproxy 的监控界面,则说明 Haproxy 成功部署。
提示
值得一提的是,上述的 192.168.1.221
是宿主机的 IP 地址,4001
是 Haproxy 容器映射的 HTTP 端口。
连接 PXC 集群 通过 Haproxy 容器映射的数据库代理端口 4402
登录 PXC 集群的 MySQL 节点。如果可以正常登录,则说明 Haproxy 成功将请求转发给 PXC 集群。
1 mysql -h 192.168.1.221 -u root -P 4002 -p123456
提示
值得一提的是,上述的 192.168.1.221
是宿主机的 IP 地址,4002
是 Haproxy 容器映射的 MySQL 端口。
集群节点心跳检测 Haproxy 每隔一段时间会对 PXC 集群的节点进行心跳检测,当某个集群节点下线后,在 Haproxy 的监控界面可以观察到(如下图所示)。
提示
当 Haproxy 检测到有 PXC 集群节点处于不可用状态时,它会将该节点从负载均衡的服务器列表中剔除掉,直到该节点重新恢复到可用状态后。因此 Haproxy 可以做到一定程度的高可用,它的负载均衡跟 Nginx 有比较大的区别,后者默认不会剔除不用的服务器节点,而是会直接转发请求给故障节点。
1 2 sudo docker stop pxc-node2
Haproxy + Keepalived 双机热备部署 在本节中,将会创建多一个 Haproxy 容器(最终 Haproxy 会有两个容器),并在宿主机和每个 Haproxy 容器内单独安装 Keepalived 服务器,以此实现 Haproxy + Keepalived 双机热备方案。特别注意,在执行以下操作之前,请先启动 PXC 集群,并确保集群可以正常运行。
双机热备部署规划 Docker 部署 Haproxy(两个节点 - 主备) 节点名称 容器名称 容器 IP 容器数据卷 容器数据卷目录 操作系统 Haproxy 节点一 haproxy-node1 172.30.0.5 haproxy-v1 /var/lib/docker/volumes/haproxy-v1/_data/
Debian 11 Haproxy 节点二 haproxy-node2 172.30.0.6 haproxy-v2 /var/lib/docker/volumes/haproxy-v2/_data/
Debian 11
服务器名称 服务器角色 虚拟 IP 说明 操作系统 Keepalived 服务器一 主服务器(MASTER) 172.30.0.7 安装在 Haproxy 节点一的容器内(haproxy-node1
) Debian 11 Keepalived 服务器二 备服务器(BACKUP) 172.30.0.7 安装在 Haproxy 节点二的容器内(haproxy-node2
) Debian 11 Keepalived 服务器三 192.168.1.160 安装在宿主机内,为了实现外网可以正常访问 Docker 容器内的虚拟 IP Centos 7
双机热备整体架构 单节点的 Haproxy 不具备真正的高可用性,必须要有冗余设计,否则 Haproxy 宕机后,会造成整个集群不可用,如下图所示:
对 Haproxy 进行集群部署(两个节点),并使用 Keepalived 实现双机热备架构,当其中一个 Haproxy 节点宕机后,另一个 Haproxy 节点可以顶上,保证整个集群的可用性,如下图所示:
Docker 创建两个 Haproxy 容器,每个容器中都单独安装 Keepalived 服务器 两个 Keepalived 服务器会争抢 Docker 容器内的虚拟 IP,一个抢到后,另一个没抢到就会等待,抢到的作为主服务器,没抢到的作为备用服务器 两个 Keepalived 服务器之间会进行心跳检测,如果备用服务器没有接收到主服务器的心跳响应,则说明主服务器发生故障,那么备用服务器就可以抢占虚拟 IP,继续工作 客户端向虚拟 IP 发送数据库请求,当其中一个 Haproxy 节点宕机后,还有另一个 Haproxy 节点可以接替工作 由于 Docker 容器内的虚拟 IP 不能被外网直接访问,所以需要借助宿主机里的 Keepalived 服务映射成外网可以访问的虚拟 IP 安装第一个 Keepalive 服务 在本节中,将在上面创建第一个的 Haproxy 容器内,安装 Keepalived 服务器。
使用 root 权限连接第一个 Haproxy 容器 1 2 docker exec -u 0 -it haproxy-node1 /bin/bash
在第一个 Haproxy 容器内安装 Keepalived 服务器 1 2 3 4 5 apt-get update apt-get install -y vim keepalived
在第一个 Haproxy 容器内配置 Keepalived 1 2 3 4 5 touch /etc/keepalived/keepalived.conf vim /etc/keepalived/keepalived.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 vrrp_instance VI_1 { state MASTER # 必填,Keepalived 的身份(MASTER 是主服务器,BACKUP 是备服务器) interface eth0 # 必填,Docker 的网卡设备,虚拟 IP 所在 virtual_router_id 51 # 必填,虚拟路由标识,取值在0-255之间,用来区分多个Instance的VRRP组播,同一网段内ID不能重复,主备机器的该值必须为一样 priority 100 # 必填,用来选举Master的,MASTER 权重要高于 BACKUP,数字越大优先级越高,该项取值范围是1-255(在此范围之外会被识别成默认值100) advert_int 1 # 必填,MASTER 和 BACKUP 节点同步检查的时间间隔(单位为秒),主备之间必须一致,可以认为是健康检查的时间间隔 authentication { # 必填,主备服务器的验证方式,主备之间必须使用相同的密码才能正常通信 auth_type PASS # 必填,主备服务器的认证方式,其中有两种方式PASS和HA(IPSEC),推荐使用PASS(密码只识别前8位),主备之间必须使用相同的密码才能正常通信 auth_pass 123456 } virtual_ipaddress { # 必填,虚拟 IP,可以设置多个虚拟 IP 地址,每行一个 172.30.0.7 } }
在第一个 Haproxy 容器内启动 Keepalived 1 service keepalived start
在宿主机内查看第一个 Haproxy 容器内的进程列表,验证 Keepalived 服务是否正常运行 1 docker top haproxy-node1
在宿主机内 Ping 虚拟 IP,验证虚拟 IP 是否可用 安装第二个 Keepalived 服务 在本节中,将另外多部署一个 Haproxy 容器,并在容器内安装 Keepalived 服务器。
部署第二个 Haproxy 容器 创建数据卷,存储路径为 /var/lib/docker/volumes/
1 2 3 4 5 6 7 8 sudo docker volume create --name haproxy-v2 sudo docker volume ls sudo docker volume inspect haproxy-v2
1 2 3 4 5 sudo touch /var/lib/docker/volumes/haproxy-v2/_data/haproxy.cfg sudo vi /var/lib/docker/volumes/haproxy-v2/_data/haproxy.cfg
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 global # 工作目录 # chroot /usr/local/etc/haproxy # 日志文件,使用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 # 登陆账户信息 stats auth admin:admin # 数据库负载均衡 listen proxy-pxc # 访问的IP和端口 bind 0.0.0.0:3306 # 网络协议 mode tcp # 负载均衡算法(轮询算法) # 轮询算法:roundrobin # 权重算法:static-rr # 最少连接算法:leastconn # 请求源IP算法:source balance roundrobin # 日志格式 option tcplog # Haproxy使用MySQL的haproxy账户对数据库进行心跳检测 option mysql-check user haproxy server PXC_Node_1 172.30.0.2:3306 check weight 1 maxconn 2000 server PXC_Node_2 172.30.0.3:3306 check weight 1 maxconn 2000 server PXC_Node_3 172.30.0.4:3306 check weight 1 maxconn 2000 # 使用Keepalived检测死链 option tcpka
特别注意
在上述 Haproxy 的配置文件中,不能启用 chroot /usr/local/etc/haproxy
,否则 Haproxy 容器会启动失败(日志信息如下),暂时不清楚其原因。
1 2 3 4 5 6 7 8 9 10 [NOTICE] (1) : New worker (8) forked [NOTICE] (1) : Loading success. [NOTICE] (8) : haproxy version is 2.8.3-86e043a [NOTICE] (8) : path to executable is /usr/local/sbin/haproxy [ALERT] (8) : [haproxy.main()] Cannot chroot(/usr/local/etc/haproxy). [NOTICE] (1) : haproxy version is 2.8.3-86e043a [NOTICE] (1) : path to executable is /usr/local/sbin/haproxy [WARNING] (1) : Current worker (8) exited with code 1 (Exit) [ALERT] (1) : exit-on-failure: killing every processes with SIGTERM [WARNING] (1) : All workers exited. Exiting... (1)
创建并运行 Haproxy 容器,其中的 8888
是 Haproxy 监听的 HTTP 端口(用于提供监控界面的 Web 服务),3306
是 Haproxy 监听的 MySQL 端口(用于转发请求给 PXC 集群节点) 1 2 3 4 5 sudo docker run -it -p 4003:8888 -p 4004:3306 -v /var/lib/docker/volumes/haproxy-v2/_data/:/usr/local /etc/haproxy --privileged --name=haproxy-node2 --net=pxc-network --ip 172.30.0.6 -d haproxy sudo docker logs -f haproxy-node2
在宿主机内使用浏览器访问 http://192.168.1.221:4003/dbs
,打开 Haproxy 的监控界面(如下图所示),登录用户名是 admin
,登录密码是 admin
。如果可以正常访问 Haproxy 的监控界面,则说明 Haproxy 成功部署。 提示
值得一提的是,上述的 192.168.1.221
是宿主机的 IP 地址,4003
是 Haproxy 容器映射的 HTTP 端口。
安装第二个 Keepalive 服务 使用 root 权限连接第二个 Haproxy 容器 1 2 docker exec -u 0 -it haproxy-node2 /bin/bash
在第二个 Haproxy 容器内安装 Keepalived 服务器 1 2 3 4 5 apt-get update apt-get install -y vim keepalived
在第二个 Haproxy 容器内配置 Keepalived 1 2 3 4 5 touch /etc/keepalived/keepalived.conf vim /etc/keepalived/keepalived.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 vrrp_instance VI_1 { state BACKUP # 必填,Keepalived 的身份(MASTER 是主服务器,BACKUP 是备服务器) interface eth0 # 必填,Docker 的网卡设备,虚拟 IP 所在 virtual_router_id 51 # 必填,虚拟路由标识,取值在0-255之间,用来区分多个Instance的VRRP组播,同一网段内ID不能重复,主备机器的该值必须为一样 priority 90 # 必填,用来选举Master的,MASTER 权重要高于 BACKUP,数字越大优先级越高,该项取值范围是1-255(在此范围之外会被识别成默认值100) advert_int 1 # 必填,MASTER 和 BACKUP 节点同步检查的时间间隔(单位为秒),主备之间必须一致,可以认为是健康检查的时间间隔 authentication { # 必填,主备服务器的验证方式,主备之间必须使用相同的密码才能正常通信 auth_type PASS # 必填,主备服务器的认证方式,其中有两种方式PASS和HA(IPSEC),推荐使用PASS(密码只识别前8位),主备之间必须使用相同的密码才能正常通信 auth_pass 123456 } virtual_ipaddress { # 必填,虚拟 IP,可以设置多个虚拟 IP 地址,每行一个 172.30.0.7 } }
在第二个 Haproxy 容器内启动 Keepalived 1 service keepalived start
在宿主机内查看第二个 Haproxy 容器内的进程列表,验证 Keepalived 服务是否正常运行 1 docker top haproxy-node2
安装第三个 Keepalive 服务 在本节中,将在宿主机内安装第三个 Keepalive 服务器,目的是为了实现外网可以正常访问 Docker 内的虚拟 IP。特别注意,Docker 内的虚拟 IP 默认是不能被外网访问的,所以需要借助宿主机的 Keepalived 映射成外网可以正常访问的虚拟 IP。
配置宿主机的防火墙,对外开放 8888
和 3306
端口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 sudo systemctl status firewalld sudo systemctl start firewalld sudo firewall-cmd --zone=public --permanent --add-port=8888/tcp sudo firewall-cmd --zone=public --permanent --add-port=3306/tcp sudo firewall-cmd --reload sudo firewall-cmd --list-ports
1 2 3 4 5 sudo yum install -y nmap nmap -sP 192.168.1.0/24
1 2 3 4 5 sudo yum install -y keepalived sudo systemctl enable keepalived
1 2 3 4 5 sudo cp /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.bak sudo vim /etc/keepalived/keepalived.conf
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 34 35 36 37 38 39 40 41 42 vrrp_instance VI_1 { state MASTER # 这里指定是宿主机的网卡,可以通过 "ip a" 命令查看当前系统上使用的网卡是哪个 interface enp0s3 virtual_router_id 100 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { # 这里是指定宿主机上的一个虚拟IP,一定要和宿主机网卡在同一个网段,例如宿主机网卡的IP是192.168.1.221,那么指定的虚拟IP可以是192.168.1.160 192.168.1.160 } } # 接收HTTP数据,用于访问Haproxy的Web监控页面 virtual_server 192.168.1.160 8888 { delay_loop 3 lb_algo rr lb_kind NAT persistence_timeout 50 protocol TCP # 将宿主机接收到的数据,转发给Haproxy容器内的虚拟IP和HTTP端口 real_server 172.30.0.7 8888 { weight 1 } } # 接收数据库请求,用于访问Haproxy代理的MySQL端口 virtual_server 192.168.1.160 3306 { delay_loop 3 lb_algo rr lb_kind NAT persistence_timeout 50 protocol TCP # 将宿主机接收到的数据,转发给Haproxy容器内的虚拟IP和MySQL端口 real_server 172.30.0.7 3306 { weight 1 } }
1 2 3 4 5 sudo systemctl start keepalived sudo systemctl status keepalived
双机热备方案部署验证 在其他电脑上,如果可以通过宿主机的虚拟 IP 192.168.1.160
正常访问宿主机 Haproxy 容器中的虚拟 IP 172.30.0.7
及相应端口,则说明 PXC + Haproxy + Keepalived 的高可用集群搭建成功。 验证内容 宿主机的虚拟 IP 宿主机的端口 验证命令 Haproxy 的监控页面 192.168.1.160 8888 curl -basic -u admin:admin -I http://192.168.1.160:8888/dbs
Haproxy 的 MySQL 负载均衡 192.168.1.160 3306 mysql -h 192.168.1.160 -u root -P 3306 -p123456
检查两个 Keepalived 节点之间是否可以正常进行心跳通信,如果不能进行心跳通信,则会发生脑裂现象(即两个 Keepalived 节点会同时抢占到虚拟 IP) 1 2 3 4 5 docker exec -u 0 -it haproxy-node1 /bin/bash ip addr
1 2 3 4 5 docker exec -u 0 -it haproxy-node2 /bin/bash ip addr
在宿主机上面关闭 Haproxy 节点一的容器,然后在其他电脑上,打开浏览器访问 http://192.168.1.160:8888/dbs
,如果可以正常访问 Haproxy 监控页面,则说明 Haproxy + Keepalived 的双机热备方案生效了。 1 2 sudo docker stop haproxy-node1
在宿主机上面关闭 Haproxy 节点一的容器之后,连接进 Haproxy 节点二的容器内部,使用 ip addr
命令查看 IP 地址,可以看见已经抢占到的虚拟 IP 1 2 3 4 5 docker exec -u 0 -it haproxy-node2 /bin/bash ip addr
Haproxy + Keepalived 双机热备完善 在本节中,将介绍上述操作完成之后,Haproxy + Keepalived 双机热备方案仍需要改进的地方。
Keepalived 服务自启动 目前集群里有两个 Keepalived 服务分别安装在不同的 Haproxy 容器内,但它们默认都没有配置自启动,也就是说 Keepalived 没有随 Haproxy 容器启动而启动。为了日后方便维护集群,建议将 Haproxy 容器内的 Keepalived 服务统一配置成自启动。可以尝试通过 Dockerfile 自主构建包含有 Haproxy + Keepalived 的 Docker 镜像。由于篇幅有限,这里不再累述。
Haproxy 保证事务持久性 Haproxy 代理 MySQL 的时候,事务持久性的问题必须解决。这个事务持久性不是 ACID 的 D(持久性,Durability),而是 Transaction Persistent,这里简单描述一下此处的事务持久性。
1 2 3 4 5 start transaction update1... update2... insert3... commit
当客户端显式开启一个事务,然后执行上述几个数据库操作,然后提交或回滚。如果使用代理软件(如 Haproxy)对 MySQL 进行代理,必须要保证这 5 个语句全都路由到同一个 MySQL 节点上,即使后端的 MySQL 采用的是多主模型(MGR、Galera 都提供多主模型),否则事务中各语句分散,轻则返回失败,重则数据不一致、提交混乱。这就是 Transaction Persistent 的概念,即让同一个事务路由到同一个后端节点。Haproxy 如何保证事务持久性呢?对于非 MySQL 协议感知的代理(LVS、Nginx、Haproxy 等),要保证事务持久性,只能通过间接的方法实现,比较通用的方法是在代理软件上监听不同的端口(实现读写分离)。具体的思路如下:
1)在 Haproxy 上监听不同端口,例如 3307
端口的请求作为写端口,3306
端口的请求作为读端口。 2)从后端 MySQL 节点中选一个节点 (只能是一个) 作为逻辑写节点,Haproxy 将 3307
端口的请求全都路由给这个节点。 3)可以在 Haproxy 上配置多个备用写节点 (Backup),但 3307
端口在某一时刻,路由到的必须只能有一个写节点。 这样能保证事务的持久性,也能解决一些乐观锁问题。但是,如果后端是多主模型的 MGR(组复制)或 Galera,这样的代理方式将强制变为单主模型,虽然是逻辑上的强制。当然,这并非什么问题,至少到目前为止的开源技术,都建议采用单主模型。Haproxy 保证事务持久性的配置示例如下:
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 listen haproxy_3306_read_multi bind *:3306 mode tcp timeout client 10800s timeout server 10800s balance leastconn option httpchk option allbackups default-server port 9200 inter 2s downinter 5s rise 3 fall 2 slowstart 60s maxconn 64 maxqueue 128 weight 100 server galera1 192.168.55.111:3306 check server galera2 192.168.55.112:3306 check server galera3 192.168.55.113:3306 check listen haproxy_3307_write_single bind *:3307 mode tcp timeout client 10800s timeout server 10800s balance leastconn option httpchk option allbackups default-server port 9200 inter 2s downinter 5s rise 3 fall 2 slowstart 60s maxconn 64 maxqueue 128 weight 100 server galera1 192.168.55.111:3306 check server galera2 192.168.55.112:3306 check backup server galera3 192.168.55.113:3306 check backup
上面的配置通过 3306
端口和 3307
端口进行读写分离,并且在负责写的 3307
端口中只有一个节点可写,其余两个节点作为 Backup 节点。对于 MySQL 的负载来说,更建议采用 MySQL 协议感知的程序来实现,例如 MySQL Router、ProxySql,MaxScale、MyCat 等数据库中间件。
Keepalived 监控 Haproxy 运行状态 上述两个 Haproxy 容器内的 Keepalived 服务,彼此仅仅是基于心跳检测来实现双机热备(故障切换)。如果第一个 Haproxy 容器内的 Keepalived 服务(Master)正常运行,而 Haproxy 自身运行异常,那么将会出现 Haproxy 负载均衡服务失效,无法切换到备用的 Haproxy 负载均衡器上,最终导致后端的 Web 服务无法收到响应。所以,应该是要基于 Shell 脚本每隔一段时间检测 Haproxy 服务是否正常运行,而不是仅仅依靠 Keepalived 主备节点之间的心跳检测。 比如,当检测到 Haproxy 服务不是正常运行,首先尝试启动 Haproxy 服务;若 Haproxy 服务重启失败,就应该关闭掉该节点上的 Keepalived 服务,并发送报警邮件,这样才能自动切换到 Keepalived 服务的 Backup 节点上。详细的解决方案建议参考 这里 的教程。
PXC 集群数据备份 数据库备份方案 冷备份
冷备份是关闭数据库时候的备份方式,通常做法是拷贝数据文件。 是简单安全的一种备份方式,但不能在数据库运行时进行备份。 大型网站无法做到关闭业务备份数据,所以冷备份不是最佳选择。 热备份
热备份是在数据库运行状态下备份数据,MySQL 常见的热备份有 LVM
和 XtraBackup
两种方案。 LVM
热备份方案 Linux 的分区备份命令,可以备份任何数据库。 会对数据库加锁,备份期间只能读取数据库,而且命令复杂。 XtraBackup
热备份方案基于 InnoDB 的在线热备工具,开源免费 备份过程中不锁表,快速可靠 备份过程中不会打断正在执行地事务 备份数据经过压缩,占用磁盘空间小 能够非常快速地备份与恢复 MySQL 数据库 全量备份与增量备份
全量备份
:备份全部数据。备份过程时间长,占用空间大。第一次备份要使用全量备份。增量备份
: 只备份变化的那部分数据。备份的时间短,占用空间小。第二次以后可以使用增量备份PXC 全量备份(暂未验证) 备份操作要在某个 PXC 集群节点的 Docker 容器内执行,但应该把备份数据保存到宿主机内,因此先创建用于存储备份数据的数据卷 1 2 3 4 5 sudo docker volume create backup sudo docker volume ls
挑选第二个 PXC 集群节点 pxc-node2
,将其容器关闭并删除掉,然后重新创建一个挂载了 backup
数据卷的 pxc-node2
容器 1 2 3 4 5 6 7 8 sudo docker stop pxc-node2 sudo docker rm pxc-node2 sudo docker create -p 13307:3306 -v pxc-v2:/var/lib/mysql -v backup:/data -e MYSQL_ROOT_PASSWORD=123456 -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_NAME=pxc -e CLUSTER_JOIN=pxc-node1 --name=pxc-node2 --net=pxc-network --ip 172.30.0.3 pxc
在新创建的 pxc-node2
容器中安装 xtrabackup
工具 1 2 3 4 5 sudo docker start pxc-node2 sudo docker exec -u 0 -it pxc-node2 /bin/bash
1 2 3 4 5 6 7 8 yum install -y https://repo.percona.com/yum/percona-release-latest.noarch.rpm percona-release enable-only tools release yum install -y percona-xtrabackup-80
1 2 3 4 5 mkdir -p /data/backup/full xtrabackup --backup -uroot -p123456 --target-dir=/data/backup/full
PXC 全量还原(暂未验证) 数据库可以热备份,但是不能热还原,否则会造成业务数据和还原数据的冲突。针对 PXC 集群,为了避免在还原过程中可能出现各节点数据同步冲突的问题,需要先解散原来的集群(删除所有集群节点),然后重新创建空白数据库节点,再执行数据库冷还原操作,最后再创建其他集群节点。还原前还要将热备份保存的未提交的事务回滚,还原之后重启 MySQL 服务器。
1 2 3 4 5 6 7 8 sudo docker stop pxc-node1 pxc-node2 pxc-node3 sudo docker rm pxc-node1 pxc-node2 pxc-node3 sudo docker volume rm pxc-v1 pxc-v2 pxc-v3
按照之前的步骤重新创建第一个节点的容器 pxc-node1
,并进入容器内,执行冷还原操作 1 2 3 4 5 6 7 8 9 10 11 docker volume create pxc-v1 sudo docker create -p 13306:3306 -v pxc-v1:/var/lib/mysql -v backup:/data -e MYSQL_ROOT_PASSWORD=123456 -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_NAME=pxc --name=pxc-node1 --net=pxc-network --ip 172.30.0.2 pxc sudo docker start pxc-node1 docker exec -it -uroot pxc-node1 /bin/bash
1 2 3 4 5 6 7 8 9 10 11 xtrabackup --prepare --target-dir=/data/backup/full/ xtrabackup --copy-back --target-dir=/data/backup/full/ chown -R mysql:mysql /var/lib/mysql exit
1 2 3 4 5 docker stop pxc-node1 docker start pxc-node1
1 2 3 4 5 sudo docker volume create --name pxc-v2 sudo docker volume create --name pxc-v3
1 2 3 4 5 sudo docker create -p 13307:3306 -v pxc-v2:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_NAME=pxc -e CLUSTER_JOIN=pxc-node1 --name=pxc-node2 --net=pxc-network --ip 172.30.0.3 pxc sudo docker create -p 13308:3306 -v pxc-v3:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_NAME=pxc -e CLUSTER_JOIN=pxc-node1 --name=pxc-node3 --net=pxc-network --ip 172.30.0.4 pxc
1 2 3 4 5 sudo docker start pxc-node2 sudo docker start pxc-node3
PXC 集群日常维护 特别注意
对于 PXC 集群节点的启动、关闭等操作,区分第一个节点(负责集群初始化)和其他节点,两者的操作步骤是不同的。
正确关闭集群 如果第一个节点(负责集群初始化)不是最后一个离开集群的,那么它在一般情况下就不能再以第一个节点的形式启动了。这是因为从这个节点引导集群启动可能是不安全的,即这个节点可能不包含所有更新的数据。综上所述,PXC 集群节点的正确关闭顺序,应该与它们的启动顺序相反(类似栈结构 - 先进后出),即最先启动的节点应该最后关闭。
1 2 3 4 5 6 7 8 sudo docker stop pxc-node3 sudo docker stop pxc-node2 sudo docker stop pxc-node1
1 2 3 4 5 6 7 8 sudo docker start pxc-node1 sudo docker start pxc-node2 sudo docker start pxc-node3
正确关闭与启动节点 如果是希望 PXC 集群关闭某个节点(非第一个节点),正确的步骤如下:
1 2 3 4 5 sudo docker stop pxc-node2 sudo docker ps -a
某个节点(非第一个节点)关闭之后,其他维护工作也完成了,若希望将该节点重新加入 PXC 集群,可以执行以下命令:
1 2 sudo docker start pxc-node2
第一个节点不是最后一个离开集群 如果第一个节点(负责集群初始化)不是最后一个离开集群的,不能再以第一个节点的形式启动了。
第一个节点(负责集群初始化)关闭后,希望重新加入 PXC 集群,此时运行 sudo docker start pxc-node1
命令,会发现第一个节点的容器没有正常启动 启动失败的原因往往是:此时从第一个节点引导集群启动可能是不安全。由于该节点不是最后一个离开集群的节点(最后停掉的节点),可能没有包含所有更新的数据 如果此时所有集群节点都处于关闭状态首先逐一排查每个节点的数据卷目录,查看数据卷目录下是否存在 grastate.dat
文件,并找到 safe_to_bootstrap=1
的节点如果找到 safe_to_bootstrap=1
的节点,且它不是第一个节点(负责集群初始化)此时为了方便操作,建议先将所有节点容器关闭并删除掉,在删除所有节点容器之前,必须确保数据卷目录的数据不被误删,否则会丢失所有数据库数据 然后再按照 上面的步骤 ,重新创建并启动每一个 PXC 集群节点容器 最后再检查集群的数据是否可以正常同步 如果找到 safe_to_bootstrap=1
的节点,且它是第一个节点(负责集群初始化),或者根本找不到 safe_to_bootstrap=1
的节点这时,需要通过 sudo docker volume inspect pxc-v1
得到第一个节点的数据卷目录路径,找到数据卷目录下的 grastate.dat
文件 打开第一个节点下的 grastate.dat
文件,将 safe_to_bootstrap
设置为 1
然后直接使用 sudo docker start pxc-node1
命令强制从第一个节点启动 等第一个节点正常启动后,再接着启动其他节点 最后再检查集群的数据是否可以正常同步 如果此时集群里还至少有一个节点存活(例如节点三存活着)首先删除第一个节点的容器,sudo docker stop pxc-node1
、sudo docker rm pxc-node1
重新创建一个新节点的容器,sudo docker create -p 13306:3306 -v pxc-v1:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_NAME=pxc -e CLUSTER_JOIN=pxc-node3 --name=pxc-node1 --net=pxc-network --ip 172.30.0.2 pxc
在创建新节点的容器时,是以普通节点形式(非第一个节点的形式)加入集群的,注意创建新节点容器的命令行参数是包含 -e CLUSTER_JOIN=pxc-node3
,这里的 pxc-node3
是集群中存活着的节点三 然后启动新节点的容器,sudo docker start pxc-node1
最后再检查集群的数据是否可以正常同步 系统重启之后,PXC 集群启动失败 Docker 所在的 Windows/Linux 操作系统重启后,导致所有 PXC 集群节点都意外关闭了,此时选择 PXC 集群中的第一个节点容器进行重启,出现以下的错误信息:
1 [ERROR] WSREP: It may not be safe to bootstrap the cluster from this node. It was not the last one to leave the cluster and may not contain all the updates. To force cluster bootstrap with this node, edit the grastate.dat file manually and set safe_to_bootstrap to 1 .
意思是从这个节点引导集群启动可能是不安全。由于该节点不是最后一个离开集群的节点(最后停掉的节点),可能没有包含所有更新的数据,强制从该节点启动,需要手工编辑该节点的 grastate.dat
文件,设置 safe_to_bootstrap=1
。 当然了,一般情况下不需要强制从该节点启动,可以逐一排查每个节点下的 grastate.dat
文件,找到 safe_to_bootstrap=1
的节点。 如果 safe_to_bootstrap=1
的节点是第一个节点,那么可以直接在该节点上引导 PXC 集群启动,然后再启动其他节点。 如果 safe_to_bootstrap=1
的节点不是第一个节点,此时为了方便操作,建议先将所有节点容器关闭并删除掉,然后再按照 上面的步骤 ,重新创建并启动每一个 PXC 集群节点容器。 如果所有节点的 safe_to_bootstrap
都为 0
,那么只能任意选择一个节点,更改该节点下的 grastate.dat
文件,将 safe_to_bootstrap
设置为 1
,然后在该节点上引导 PXC 集群启动,最后再启动其他节点。 无论是上述哪种情况,都必须等待第一个节点启动成功,也就是 PXC 集群初始化完成之后,才能接着启动其他节点,最后再检查集群的数据是否可以正常同步。 提示
grastate.dat
文件的路径是 /var/lib/docker/volumes/xxxx/_data/grastate.dat
,其中的 xxx
是容器数据卷的目录名称。最坏情况,所有节点容器重新创建 假设由于各种原因导致整个 PXC 集群无法正常启动,此时可以将所有节点容器关闭并删除掉(必须确保数据卷目录的数据不被误删,否则会丢失所有数据库数据 ),然后再按照 上面的步骤 ,重新创建并启动每一个 PXC 集群节点容器。
参考资料