MySQL InnoDB Cluster + Router + Keepalived 高可用部署完整文档
环境 :Rocky Linux 9.8 × 3 台
节点 :192.168.10.11(mysql-node1)、192.168.10.12(mysql-node2)、192.168.10.13(mysql-node3)
网卡 :ens33
VIP :192.168.10.10
目标:三节点 InnoDB Cluster + 双 Router + Keepalived VIP,实现数据库层与接入层双重高可用,应用无感知
📌 本文中所有 IP 地址、密码、认证口令均为示例值,请按实际生产环境替换后再执行。
目录
- 一、架构总览
- 二、环境准备(三台分别执行)
- 三、统一 root 密码与远程授权(三台分别执行)
- 四、配置 MySQL 实例(仅在 141 执行)
- 五、创建集群(仅在 141 执行)
- 六、添加其余节点(仅在 141 执行)
- 七、部署 MySQL Router(141 与 142 分别执行)
- 八、部署 Keepalived VIP(141 与 142 分别执行)
- 九、验证高可用
- 十、原主节点恢复与 rejoin
- 十一、应用连接地址
- 十二、变量对照表
- 十三、常见问题排查
一、架构总览
| 角色 | 主机 | IP | 说明 |
|---|---|---|---|
| MySQL 节点 + Router + Keepalived(MASTER) | mysql-node1 | 192.168.10.11 | 主库首选,VIP 首选持有者(priority 100) |
| MySQL 节点 + Router + Keepalived(BACKUP) | mysql-node2 | 192.168.10.12 | 备库,VIP 备选(priority 90) |
| MySQL 节点(仅数据节点) | mysql-node3 | 192.168.10.13 | 第三个数据节点,保证多数派 |
| 浮动 VIP | --- | 192.168.10.10 | 应用唯一接入地址 |
流量路径:
text
应用 → VIP 192.168.10.10:6446
│
┌──────┴──────┐
Router@141 Router@142 (Keepalived VRRP 主备)
│ │
└──────┬───────┘
当前主库 3306(141/142/143 中任一)
│
InnoDB Cluster (MGR)
高可用两层:
- 数据层:MGR 三节点,主库宕机自动选主,容忍一台故障。
- 接入层:双 Router + Keepalived VIP,Router 所在机器宕机 VIP 自动漂移。
二、环境准备(三台分别执行)
在 141、142、143 上各自完成 2.1~2.8。
2.1 配置主机名与 hosts 解析
141 上:
bash
hostnamectl set-hostname mysql-node1
142 上:
bash
hostnamectl set-hostname mysql-node2
143 上:
bash
hostnamectl set-hostname mysql-node3
三台都执行:
bash
cat >> /etc/hosts <<'EOF'
192.168.10.11 mysql-node1
192.168.10.12 mysql-node2
192.168.10.13 mysql-node3
EOF
验证:
bash
ping -c 1 mysql-node1
ping -c 1 mysql-node2
ping -c 1 mysql-node3
关键 :Shell 会以 mysql-nodeX:3306 作为集群成员通信地址,解析不通会导致 addInstance 失败。
2.2 关闭防火墙与 SELinux(简化部署)
bash
sudo systemctl stop firewalld && sudo systemctl disable firewalld
sudo setenforce 0
sudo sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
生产环境应改为放行 3306、33060、33061、6446、6447、112(VRRP)端口,并保留 SELinux。
2.3 修复 locale 警告
bash
sudo dnf install -y glibc-langpack-en
2.4 安装 MySQL 官方 yum 仓库
bash
sudo dnf install -y https://dev.mysql.com/get/mysql80-community-release-el9-1.noarch.rpm
说明:
el9对应 Rocky / AlmaLinux 9;其他系统版本换成对应包(如 el8);- 安装后生成
/etc/yum.repos.d/mysql-community.repo等仓库文件,后续dnf install才有包可装; - 验证:
ls /etc/yum.repos.d/mysql*.repo。
若此步安装 repo rpm 本身提示
Public key for mysql80-community-release...rpm is not installed,说明新机器缺少 MySQL 官方公钥,先执行sudo rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2023再重试。
2.5 安装 MySQL 软件包(首次安装可能触发 GPG 密钥报错)
bash
sudo dnf clean all
sudo dnf makecache
sudo dnf install -y mysql-community-server mysql-shell mysqlrouter keepalived
可能遇到的报错(常见):
text
Public key for mysql-community-server-8.0.46-1.el9.x86_64.rpm is not installed.
Failing package is: mysql-community-server-8.0.46-1.el9.x86_64.rpm
...
The GPG keys listed for the "MySQL 8.0 Community Server" repository are already installed but they are not correct for this package.
原因:MySQL 8.0.46 起软件包改用 2023 版公钥 (RPM-GPG-KEY-mysql-2023)签名,而仓库文件中的 gpgkey 仍指向旧密钥,校验失败。遇到此报错请执行 2.6 修复后,重新执行本条安装命令。
2.6 修复 MySQL GPG 密钥问题(遇报错时执行)
清理旧密钥:
bash
rpm -qa gpg-pubkey* | grep -i mysql
若有输出(如 gpg-pubkey-3a79bd29-xxxx),逐个删除:
bash
sudo rpm -e <实际包名>
导入 2023 版密钥:
bash
sudo rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2023
替换仓库 gpgkey 与 gpgcheck:
bash
sudo sed -i 's|^gpgkey=.*|gpgkey=https://repo.mysql.com/RPM-GPG-KEY-mysql-2023|g' /etc/yum.repos.d/mysql*.repo
sudo sed -i 's|^gpgcheck=.*|gpgcheck=1|g' /etc/yum.repos.d/mysql*.repo
确认:
bash
grep -E '^\[|^gpgkey=|^gpgcheck=' /etc/yum.repos.d/mysql*.repo
修复完成后,重新执行 2.5 的安装命令即可。
2.7 验证安装
bash
mysqld --version
mysqlsh --version
mysqlrouter --version
keepalived --version
三台都装 mysqlrouter 和 keepalived,便于后续灵活调整;实际只在 141/142 启用。
2.8 启动 MySQL 并设置开机自启
bash
sudo systemctl enable --now mysqld
sudo systemctl status mysqld --no-pager
确认 active (running)。
三、统一 root 密码与远程授权(三台分别执行)
三台都要完成 3.1~3.3。
3.1 获取临时 root 密码
bash
sudo grep 'temporary password' /var/log/mysqld.log
输出中 root@localhost: 后面的字符串即临时密码。
3.2 修改 root 密码
bash
mysql -u root -p
在 MySQL 提示符下执行(三台统一同一密码,示例 YourRootPass_2026):
sql
ALTER USER 'root'@'localhost' IDENTIFIED BY 'YourRootPass_2026';
FLUSH PRIVILEGES;
EXIT;
3.3 授权 root 从集群内网远程访问
MySQL 8.0 语法变更:
IDENTIFIED BY不能写在GRANT语句里,必须拆分为CREATE USER+GRANT。
bash
mysql -u root -p
执行:
sql
CREATE USER 'root'@'192.168.10.%' IDENTIFIED BY 'YourRootPass_2026';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'192.168.10.%' WITH GRANT OPTION;
FLUSH PRIVILEGES;
EXIT;
不要使用 'root'@'%'。用 192.168.10.% 限定内网网段。
三台都执行同样的授权,否则 configureInstance 和 addInstance 会因 Error 1130 失败。
四、配置 MySQL 实例(仅在 141 执行)
dba.configureInstance() 会检查并修复 MGR 参数(gtid_mode、enforce_gtid_consistency、server_id、binlog_transaction_dependency_tracking 等),并创建 clusteradmin。
⚠️ 不要加
interactive: false。该参数会禁用所有交互式提示(包括密码输入),导致Access denied ... (using password: NO)的 1045 错误。
4.1 进入 MySQL Shell
bash
mysqlsh
4.2 配置 141
在 MySQL JS > 提示符下执行:
javascript
dba.configureInstance('root@192.168.10.11:3306', {clusterAdmin: 'clusteradmin', clusterAdminPassword: 'YourClusterAdminPass_2026'})
提示密码时输入 141 的 root 密码。两个交互确认都输入 y:
text
Do you want to perform the required configuration changes? [y/n]: y
Do you want to restart the instance after configuring it? [y/n]: y
成功标志:
text
The instance 'mysql-node1:3306' was configured to be used in an InnoDB cluster.
4.3 配置 142
javascript
dba.configureInstance('root@192.168.10.12:3306', {clusterAdmin: 'clusteradmin', clusterAdminPassword: 'YourClusterAdminPass_2026'})
4.4 配置 143
javascript
dba.configureInstance('root@192.168.10.13:3306', {clusterAdmin: 'clusteradmin', clusterAdminPassword: 'YourClusterAdminPass_2026'})
4.5 验证配置结果
bash
mysql -u root -p -e "SELECT @@server_id, @@gtid_mode, @@enforce_gtid_consistency;"
期望 server_id 不为 1,gtid_mode 与 enforce_gtid_consistency 均为 ON。
五、创建集群(仅在 141 执行)
5.1 使用集群管理账户连接 141
在 MySQL Shell 中:
javascript
\connect clusteradmin@192.168.10.11:3306
输入 clusteradmin 密码 YourClusterAdminPass_2026。
5.2 创建集群
javascript
var cluster = dba.createCluster('myCluster')
预期输出含 Cluster successfully created.。
5.3 查看初始状态
javascript
cluster.status()
单节点状态:status 为 OK_NO_TOLERANCE,primary 为 192.168.10.11:3306。
六、添加其余节点(仅在 141 执行)
保持 cluster 变量可用。
6.1 添加 142
javascript
cluster.addInstance('clusteradmin@192.168.10.12:3306')
提示数据恢复方式时,直接回车接受默认 Clone。
6.2 添加 143
javascript
cluster.addInstance('clusteradmin@192.168.10.13:3306')
同样接受默认 Clone。
6.3 验证集群健康状态
javascript
cluster.status()
期望:
json
"status": "OK"
"statusText": "Cluster is ONLINE and can tolerate up to ONE failure."
一个节点 R/W(PRIMARY),两个节点 R/O(SECONDARY)。
6.4 设置备库优先权重(可选,建议执行)
若希望 MySQL 主库故障时优先由 142 接管:
javascript
cluster.setInstanceOption('clusteradmin@192.168.10.12:3306', 'memberWeight', 80)
cluster.setInstanceOption('clusteradmin@192.168.10.13:3306', 'memberWeight', 20)
验证:
javascript
cluster.status({extended: 1})
在输出中各节点的 memberWeight 字段可见。
七、部署 MySQL Router(141 与 142 分别执行)
Router 部署在两台:141 和 142。143 不部署 Router。
7.1 在 141 上 Bootstrap Router
先退出 MySQL Shell(\exit),在系统 shell 中执行:
bash
sudo mkdir -p /etc/mysqlrouter
sudo mysqlrouter --bootstrap clusteradmin@192.168.10.11:3306 --directory /etc/mysqlrouter --user=mysqlrouter
输入 clusteradmin 密码。
7.2 在 141 上启动 Router
bash
sudo systemctl enable --now mysqlrouter
sudo systemctl status mysqlrouter --no-pager
ss -tlnp | grep -E '6446|6447'
应看到 6446(读写)和 6447(只读)在 LISTEN。
7.3 在 142 上 Bootstrap Router
bash
sudo mkdir -p /etc/mysqlrouter
sudo mysqlrouter --bootstrap clusteradmin@192.168.10.11:3306 --directory /etc/mysqlrouter --user=mysqlrouter
bootstrap 连 141(当前主库)即可。Router 会从元数据拿到完整拓扑。
7.4 在 142 上启动 Router
bash
sudo systemctl enable --now mysqlrouter
sudo systemctl status mysqlrouter --no-pager
ss -tlnp | grep -E '6446|6447'
7.5 分别验证两台 Router 连通性
141 上:
bash
mysql -h 192.168.10.11 -P 6446 -u clusteradmin -p -e "SELECT @@hostname, @@port;"
142 上:
bash
mysql -h 192.168.10.12 -P 6446 -u clusteradmin -p -e "SELECT @@hostname, @@port;"
两者都应返回当前主节点主机名(例如 mysql-node1 和 3306)。这证明两台 Router 都能正确路由。
八、部署 Keepalived VIP(141 与 142 分别执行)
Keepalived 只部署在 141 和 142。143 不部署。
VIP 用 192.168.10.10,网卡用 ens33(不是 eth0)。
8.1 141 上配置 Keepalived
编辑 /etc/keepalived/keepalived.conf:
bash
sudo vi /etc/keepalived/keepalived.conf
141 配置(MASTER,priority 100):
ini
vrrp_instance VI_ROUTER {
state MASTER
interface ens33
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass YourVrrpAuthPass
}
virtual_ipaddress {
192.168.10.10/24
}
}
⚠️ 不要包含任何
virtual_server块。之前 141 启动失败正是因为残留了 LVS/IPVS 相关配置。当前场景只需要 VRRP,不需要 LVS。
8.2 142 上配置 Keepalived
142 配置(BACKUP,priority 90):
ini
vrrp_instance VI_ROUTER {
state BACKUP
interface ens33
virtual_router_id 51
priority 90
advert_int 1
authentication {
auth_type PASS
auth_pass YourVrrpAuthPass
}
virtual_ipaddress {
192.168.10.10/24
}
}
8.3 两台关键参数必须一致
| 参数 | 141 | 142 | 必须一致 |
|---|---|---|---|
| interface | ens33 | ens33 | ✅ |
| virtual_router_id | 51 | 51 | ✅ |
| auth_pass | YourVrrpAuthPass | YourVrrpAuthPass | ✅ |
| virtual_ipaddress | 192.168.10.10/24 | 192.168.10.10/24 | ✅ |
| state / priority | MASTER / 100 | BACKUP / 90 | 141 优先 |
8.4 加载 ip_vs 内核模块(保险措施)
在两台上执行:
bash
sudo modprobe ip_vs
sudo modprobe ip_vs_rr
echo "modprobe ip_vs" | sudo tee /etc/modules-load.d/ipvs.conf
echo "modprobe ip_vs_rr" | sudo tee -a /etc/modules-load.d/ipvs.conf
8.5 启动两台 Keepalived
141 和 142 上分别:
bash
sudo systemctl enable --now keepalived
sudo systemctl status keepalived --no-pager
检查两台 systemctl is-active keepalived mysqlrouter 都应输出两行 active。
8.6 确认 VIP 归属
141 上:
bash
ip addr show ens33 | grep 192.168.10.10
应有输出,说明 141 作为 MASTER 持有 VIP。
142 上:
bash
ip addr show ens33 | grep 192.168.10.10
应无输出(BACKUP 不持有 VIP,正常)。
8.7 通过 VIP 验证接入层
从任意机器执行:
bash
mysql -h 192.168.10.10 -P 6446 -u clusteradmin -p -e "SELECT @@hostname, @@port;"
应返回当前 MySQL 主节点主机名,证明:VIP → 141 Router → 当前主库 链路通。
九、验证高可用
高可用分两层验证:接入层 VIP 漂移 和 数据库层主库切换。
9.1 验证接入层:VIP 漂移
第一步:在 141 上停掉 Keepalived
bash
sudo systemctl stop keepalived
第二步:在 142 上确认 VIP 已漂移
等待 1~3 秒后:
bash
ip addr show ens33 | grep 192.168.10.10
应出现 192.168.10.10/24。
第三步:通过 VIP 再次访问数据库
bash
mysql -h 192.168.10.10 -P 6446 -u clusteradmin -p -e "SELECT @@hostname, @@port;"
仍应成功返回当前主节点主机名。这证明 VIP 漂到了 142,142 的 Router 正常接管。
第四步:恢复 141 的 Keepalived
bash
sudo systemctl start keepalived
141 priority 更高,会抢回 VIP,142 回到 BACKUP。这是正常行为。
9.2 验证数据层:主库自动切换
第一步:模拟主库(假设当前主库为 141)宕机
在 141 上执行:
bash
sudo systemctl stop mysqld
第二步:在 142 或 143 上查看集群状态
bash
mysqlsh --js -e "shell.connect('clusteradmin@192.168.10.12:3306'); var cluster = dba.getCluster('myCluster'); print(JSON.stringify(cluster.status(), null, 2));"
输入 clusteradmin 密码。几秒内 primary 变为 142 或 143,status 变为 OK_NO_TOLERANCE(剩两节点仍构成多数派,但不能再容忍第二次故障)。
第三步:通过 VIP 验证读写切换
bash
mysql -h 192.168.10.10 -P 6446 -u clusteradmin -p -e "SELECT @@hostname, @@port;"
返回的主机名不再是 mysql-node1,而是新选举出的主节点。证明 Router 已自动把写流量切换到新主库。
9.3 验证整机宕机(综合场景)
第一步:141 整机关机或 systemctl isolate emergency(慎用)
等效模拟方式:在 141 上同时停掉服务:
bash
sudo systemctl stop mysqld
sudo systemctl stop mysqlrouter
sudo systemctl stop keepalived
第二步:在 142 上检查 VIP 与 Router
bash
ip addr show ens33 | grep 192.168.10.10
ss -tlnp | grep 6446
systemctl is-active mysqlrouter keepalived
期望:VIP 已漂到 142、6446 在监听、两服务 active。
第三步:通过 VIP 访问数据库
bash
mysql -h 192.168.10.10 -P 6446 -u clusteradmin -p -e "SELECT @@hostname, @@port;"
应成功返回 142 或 143(新主库)。至此数据层与接入层的双重高可用完整闭环验证通过。
十、原主节点恢复与 rejoin
当 141 恢复后(重新开机或启动服务):
10.1 141 上启动 MySQL 与 Router
bash
sudo systemctl start mysqld
sudo systemctl start mysqlrouter
sudo systemctl start keepalived
10.2 将 141 重新加入集群
在任意健康节点(如 142)上执行:
bash
mysqlsh --js -e "shell.connect('clusteradmin@192.168.10.12:3306'); var cluster = dba.getCluster('myCluster'); cluster.rejoinInstance('clusteradmin@192.168.10.11:3306');"
如果 141 未自动 rejoin,此命令将其作为 SECONDARY 加回集群。
10.3 验证集群恢复健康
bash
mysqlsh --js -e "shell.connect('clusteradmin@192.168.10.12:3306'); var cluster = dba.getCluster('myCluster'); print(JSON.stringify(cluster.status(), null, 2));"
期望三节点全部 ONLINE,status 回到 OK。
注意:MGR 不会自动把主库迁回 141。此时 142(或 143)仍是 PRIMARY,141 是 SECONDARY,这是正常的。若希望主库回到 141,手动执行:
javascript
cluster.setPrimaryInstance('clusteradmin@192.168.10.11:3306')
10.4 确认 VIP 已回到 141
由于 141 priority 更高,Keepalived 恢复后会自动抢回 VIP:
bash
# 141 上
ip addr show ens33 | grep 192.168.10.10
十一、应用连接地址
应用的 DBHost 统一写:
text
192.168.10.10:6446
不再指向任何一台具体物理机。
高可用效果
| 故障场景 | 数据层行为 | 接入层行为 | 应用影响 |
|---|---|---|---|
| MySQL 主库进程崩溃 | MGR 自动选新主 | Router 自动跟 | 秒级中断,自动恢复 |
| 141 整机宕机 | 若 141 为主库,MGR 选主;若 141 为从库,无影响 | VIP 漂到 142 | 接入不中断 |
| 142 整机宕机 | 若 142 为主库,MGR 选主;若 142 为从库,无影响 | VIP 漂回 141(若在线) | 接入不中断 |
| 143 宕机 | 集群剩 2 节点,仍可用但不再容忍第二次故障 | 无影响 | 无影响 |
十二、变量对照表
| 变量 | 本文值 | 说明 |
|---|---|---|
| MySQL root 密码 | YourRootPass_2026 | 三台统一 |
| 集群管理账户 | clusteradmin | 由 configureInstance 自动创建 |
| 集群管理密码 | YourClusterAdminPass_2026 | 建议替换为强密码 |
| 集群名称 | myCluster | 后续 getCluster 需一致 |
| 节点 IP | 192.168.10.11/12/13 | 三台数据节点 |
| 网卡名 | ens33 | Keepalived 配置必须匹配 |
| VIP | 192.168.10.10/24 | 应用唯一接入地址 |
| VRRP 组 ID | 51 | 141/142 必须一致 |
| VRRP 认证密码 | YourVrrpAuthPass | 141/142 必须一致 |
| 141 priority | 100 | MASTER 优先持有 VIP |
| 142 priority | 90 | BACKUP |
| Router 读写端口 | 6446 | 应用连接此端口 |
| Router 只读端口 | 6447 | 读请求分发到从节点 |
| 备库权重(可选) | 142=80,143=20 | 主库故障时优先 142 接管 |
十三、常见问题排查
13.1 Error 1130 Host 'xxx' is not allowed to connect
- 原因:目标 MySQL 未授权 root 从集群内网远程访问。
- 解决 :在目标 MySQL 本地登录,执行第三章 3.3 的
CREATE USER+GRANT。
13.2 Error 1045 Access denied ... (using password: NO)
- 原因 :
dba.configureInstance命令中带了interactive: false,禁用了密码提示。 - 解决 :去掉
interactive: false,保持交互模式让 Shell 提示输入密码。
13.3 ERROR 1064 ... near 'IDENTIFIED BY ... WITH GRANT OPTION'
- 原因 :MySQL 8.0 语法变更,
IDENTIFIED BY不能写在GRANT语句里。 - 解决 :拆分为
CREATE USER+GRANT两条语句分别执行。
13.4 GPG 检查失败(公钥没有安装)
- 原因 :MySQL 8.0.46 使用 2023 版密钥签名,旧仓库配置仍指向 2022 版密钥;首次
dnf install报Public key for ... is not installed也是同一原因。 - 解决:执行第二章 2.6 的密钥清理、导入、sed 替换,再重新执行 2.5 的安装命令。
13.5 Cannot set LC_ALL to locale en_US.UTF-8
- 原因:系统缺少 en_US.UTF-8 locale。
- 解决 :
sudo dnf install -y glibc-langpack-en。
13.6 addInstance 卡住或失败
检查三台 /etc/hosts 是否都能互相解析 mysql-node1/2/3,以及防火墙是否放行 3306、33060、33061。
13.7 interactive option is deprecated 警告
MySQL Shell 8.0 较新版本的前瞻性提醒,当前版本仍完全支持,可忽略。
13.8 Keepalived 启动失败,日志有 Initializing ipvs / Gained quorum ... VS ...
- 原因 :配置文件中残留
virtual_server块(LVS/IPVS 配置),当前场景只需要 VRRP。 - 解决:
bash
# 编辑 /etc/keepalived/keepalived.conf,删除所有 virtual_server { ... } 块
sudo modprobe ip_vs && sudo modprobe ip_vs_rr
sudo systemctl restart keepalived
grep -c virtual_server /etc/keepalived/keepalived.conf # 输出应为 0
13.9 VIP 不漂移 / 两台各自持有 VIP(脑裂)
- 原因 :141 和 142 的
virtual_router_id或auth_pass不一致,未形成同一 VRRP 组。 - 解决 :核对两台
grep -E 'virtual_router_id|auth_pass' /etc/keepalived/keepalived.conf输出必须完全相同。同时确认interface ens33、virtual_ipaddress 192.168.10.10/24两台一致。
13.10 VIP 在 142 上但应用连不上 6446
- 原因:142 上 Router 未启动或未 bootstrap,6446 未监听。
- 解决:
bash
ss -tlnp | grep 6446
systemctl status mysqlrouter --no-pager
若 6446 无监听,在 142 上重新执行:
bash
sudo mysqlrouter --bootstrap clusteradmin@192.168.10.11:3306 --directory /etc/mysqlrouter --user=mysqlrouter
sudo systemctl restart mysqlrouter
13.11 setPrimaryInstance 报 bash 语法错误
- 原因 :把 JavaScript 命令敲到了 Linux bash 里(提示符是
#而不是JS >)。 - 解决 :先
mysqlsh进入 Shell,连接集群,再在JS >提示符下执行cluster.setPrimaryInstance(...)。
按上述顺序逐条执行,即可完成三节点 InnoDB Cluster + 双 Router + Keepalived VIP 的完整高可用部署,并通过两层故障转移验证。