麒麟v10-Orchestrator高可用组件完整部署与使用(从入门到精通)

环境:MySQL 8.0.35 GTID 一主两从(141 主 + 142/143 从)+ Orchestrator 3.2.6 raft 三节点(141/142/143)+ 元数据库(143:3307 独立实例)+ VIP(192.168.195.200)+ 最小化 SMTP 邮件服务(143:25)
本文覆盖 Orchestrator.pdf 全部内容:架构原理、安装、配置文件逐项讲解、运行、监控、企业级场景模拟、常见故障模拟与解决、邮件与 VIP、知识点补充。
所有命令均注明执行节点,可直接跟随复现。


一、架构总览(生产级规划)

被监控 MySQL 集群(GTID 一主两从):
  192.168.195.141 (Master, rw) ──> 192.168.195.142 (Slave, ro)
                              └──> 192.168.195.143 (Slave, ro)

Orchestrator raft 集群(组件自身高可用,三节点互相发现,leader 处理写操作):
  141:3000 + 142:3000 + 143:3000  (raft 通信端口 10008)

元数据库(Orchestrator 专属后端,PDF 建议"专属后端放远程"):
  192.168.195.143:3307 独立 MySQL 实例 / orchestrator 库

VIP: 192.168.195.200/24 绑定当前 Master(ens32:0),故障切换由 Hook 脚本漂移
邮件: 143:25 最小化 SMTP(smtp_server.py),告警存 /var/mailbox/*.eml

为什么这样规划(对应 PDF"架构"章节):

  • raft 多点非共享架构:Orchestrator 自身高可用,leader 宕机 follower 秒级接管;
  • 元数据库独立实例:不与业务库混用,从库 read_only 会导致 Orchestrator 写元数据失败(本次实际踩坑验证);
  • VIP+邮件通过 Hook 脚本实现:对应 PDF"通过 Hook 实现故障切换"章节。

二、环境清理(三节点执行)

在 141/142/143 上清理旧 MySQL、MHA、VIP:

# 执行节点: 141/142/143 分别执行
ip addr del 192.168.195.200/24 dev ens32 2>/dev/null      # 清理残留VIP
systemctl stop mysqld 2>/dev/null; systemctl disable mysqld 2>/dev/null
rm -rf /usr/local/mysql* /data/mysql /etc/my.cnf /etc/sysconfig/mysql \
       /etc/systemd/system/mysqld.service /etc/masterha /var/log/masterha
rpm -e mha4mysql-manager 2>/dev/null; userdel -r mysql 2>/dev/null
systemctl daemon-reload

三、GTID 最小化主从搭建(依据《MySQL_8.0.35_GTID主从复制搭建笔记.md》)

3.1 二进制包安装(141 执行,内网分发)

# 执行节点: 141
cd /usr/local
wget -q https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.35-linux-glibc2.17-x86_64.tar.xz
tar xf mysql-8.0.35-linux-glibc2.17-x86_64.tar.xz && ln -sf mysql-8.0.35-linux-glibc2.17-x86_64 mysql
scp mysql-8.0.35-linux-glibc2.17-x86_64.tar.xz root@192.168.195.142:/usr/local/
scp mysql-8.0.35-linux-glibc2.17-x86_64.tar.xz root@192.168.195.143:/usr/local/

# 执行节点: 142/143 分别执行
cd /usr/local && tar xf mysql-8.0.35-linux-glibc2.17-x86_64.tar.xz && ln -sf mysql-8.0.35-linux-glibc2.17-x86_64 mysql

3.2 用户与目录(三节点)

# 执行节点: 141/142/143
groupadd -f mysql && useradd -g mysql -s /sbin/nologin mysql
mkdir -p /data/mysql/3306/data && chown -R mysql:mysql /data/mysql /usr/local/mysql*

3.3 配置文件(GTID 最小化 + report_host 供发现)

# 执行节点: 141 ── 主库
cat > /etc/my.cnf << 'EOF'
[client]
socket = /data/mysql/3306/data/mysql.sock
[mysqld]
basedir = /usr/local/mysql
datadir = /data/mysql/3306/data
user    = mysql
port    = 3306
socket  = /data/mysql/3306/data/mysql.sock
log_error = /data/mysql/3306/data/mysqld.err
log_timestamps = system
log-bin = mysql-bin          # 主库必须开binlog
server-id = 1                # 三节点唯一
gtid_mode = ON               # GTID核心参数(最小化两条)
enforce_gtid_consistency = ON
report_host = 192.168.195.141  # 供Orchestrator通过show slave hosts自动发现(关键!)
EOF

# 执行节点: 142 / 143 ── 从库(server-id 分别为 2/3,report_host 对应修改)
# 从库额外加只读保护:
read_only = ON
super_read_only = ON

⚠️ 重要教训(本次踩坑):从库 my.cnf 里写死 read_only=ON 后,主从切换角色变化时,新提升的库重启会自动变回只读。生产做法:所有节点 my.cnf 都不写 read_only,由 Orchestrator 在提升时动态设置。切换演练后需手工同步 my.cnf 与运行角色。

3.4 systemd 服务(三节点)

# 执行节点: 141/142/143
cat > /etc/systemd/system/mysqld.service << 'EOF'
[Unit]
Description=MySQL Server
After=network.target syslog.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
Type=forking
PIDFile=/data/mysql/3306/data/mysqld.pid
TimeoutSec=0
ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf --pid-file=/data/mysql/3306/data/mysqld.pid --daemonize $MYSQLD_OPTS
EnvironmentFile=-/etc/sysconfig/mysql
LimitNOFILE=65535
Restart=on-failure
RestartPreventExitStatus=1
PrivateTmp=false
EOF
echo 'MYSQLD_OPTS=' > /etc/sysconfig/mysql
systemctl daemon-reload && systemctl start mysqld && systemctl enable mysqld

⚠️ 演进坑:Restart=on-failure 会在演练 kill -9 后自动拉起 mysqld,掩盖故障检测。演练故障请用 systemctl stop(clean stop 不触发 restart)

3.5 初始化与密码(三节点)

# 执行节点: 141/142/143
/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf --initialize
grep 'temporary password' /data/mysql/3306/data/mysqld.err   # 取临时密码

# 改密(从库因super_read_only会报ERROR 1290,需init-file绕过):
# 执行节点: 142/143
echo 'SET GLOBAL super_read_only=0; SET GLOBAL read_only=0;' > /tmp/init.sql
chown mysql:mysql /tmp/init.sql
systemctl stop mysqld
/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf --init-file=/tmp/init.sql --daemonize
sleep 2
/usr/local/mysql/bin/mysql -uroot -S /data/mysql/3306/data/mysql.sock -p'<临时密码>' --connect-expired-password \
  -e "alter user user() identified by 'Root@123456';"
systemctl stop mysqld 2>/dev/null; pkill mysqld 2>/dev/null; sleep 5
systemctl start mysqld   # 交回systemd管理(重要!否则演练时状态不一致)
rm -f /tmp/init.sql

3.6 复制用户与 GTID 主从建立

# 执行节点: 141 ── 创建复制+探测账号
/usr/local/mysql/bin/mysql -uroot -S /data/mysql/3306/data/mysql.sock -p'Root@123456' -e "
CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
CREATE USER 'orch_client'@'%' IDENTIFIED WITH mysql_native_password BY 'Orch@123456';
GRANT RELOAD, PROCESS, SUPER, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'orch_client'@'%';
CREATE USER 'root'@'192.168.195.%' IDENTIFIED WITH mysql_native_password BY 'Root@123456';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'192.168.195.%' WITH GRANT OPTION;
FLUSH PRIVILEGES;"

# 执行节点: 142/143 ── 建立 GTID 复制
/usr/local/mysql/bin/mysql -uroot -S /data/mysql/3306/data/mysql.sock -p'Root@123456' -e "
SET GLOBAL super_read_only=0;
CHANGE MASTER TO MASTER_HOST='192.168.195.141', MASTER_USER='repl', MASTER_PASSWORD='123456',
             MASTER_AUTO_POSITION=1, GET_MASTER_PUBLIC_KEY=1;
START SLAVE;
SET GLOBAL super_read_only=1;"
# 验证: Slave_IO_Running: Yes / Slave_SQL_Running: Yes / Auto_Position: 1

3.7 测试数据

# 执行节点: 141
/usr/local/mysql/bin/mysql -uroot -S /data/mysql/3306/data/mysql.sock -p'Root@123456' -e "
CREATE DATABASE orch_test;
USE orch_test;
CREATE TABLE t1 (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), ts DATETIME DEFAULT CURRENT_TIMESTAMP);
INSERT INTO t1(name) VALUES ('init_row_1'),('init_row_2'),('init_row_3');"
# 执行节点: 142/143 验证: SELECT * FROM orch_test.t1; 三行一致即成功

四、元数据库准备(143 独立 3307 实例)

对应 PDF:“Orchestrator 的专属后端可以放到远程服务器上”。
⚠️ 本次踩坑:最初元数据库放在 143:3306(业务从库),其 super_read_only 阻止 Orchestrator 写元数据,报 Error 1290 ... --read-only option,服务反复 activating。解决方案:143 增开独立 3307 实例专职元数据。

# 执行节点: 143
mkdir -p /data/mysql/3307/data && chown -R mysql:mysql /data/mysql/3307
cat > /etc/my3307.cnf << 'EOF'
[client]
socket = /data/mysql/3307/data/mysql.sock
[mysqld]
basedir = /usr/local/mysql
datadir = /data/mysql/3307/data
user    = mysql
port    = 3307
socket  = /data/mysql/3307/data/mysql.sock
log_error = /data/mysql/3307/data/mysqld.err
log_timestamps = system
server-id = 337
EOF
/usr/local/mysql/bin/mysqld --defaults-file=/etc/my3307.cnf --initialize
# 取临时密码改密后建库授权:
/usr/local/mysql/bin/mysql -uroot -S /data/mysql/3307/data/mysql.sock -p'Root@123456' -e "
CREATE DATABASE orchestrator;
CREATE USER 'orchestrator'@'%' IDENTIFIED WITH mysql_native_password BY 'Orch@123456';
GRANT ALL PRIVILEGES ON orchestrator.* TO 'orchestrator'@'%';
FLUSH PRIVILEGES;"

五、Orchestrator 3.2.6 安装(三节点)

# 执行节点: 141(下载后内网分发)
cd /tmp && wget -q https://github.com/openark/orchestrator/releases/download/v3.2.6/orchestrator-3.2.6-linux-amd64.tar.gz
scp orchestrator-3.2.6-linux-amd64.tar.gz root@192.168.195.142:/tmp/
scp orchestrator-3.2.6-linux-amd64.tar.gz root@192.168.195.143:/tmp/

# 执行节点: 141/142/143
mkdir -p /home/orchestrator /var/lib/orchestrator
tar xf /tmp/orchestrator-3.2.6-linux-amd64.tar.gz -C /home/orchestrator
cp /home/orchestrator/usr/local/orchestrator/orchestrator /home/orchestrator/
cp -r /home/orchestrator/usr/local/orchestrator/resources /home/orchestrator/
chmod +x /home/orchestrator/orchestrator
/home/orchestrator/orchestrator --version   # 输出 3.2.6

# orchestrator-client(只需在常用管理机装,本次三台都装)
cp /home/orchestrator/usr/local/orchestrator/resources/bin/orchestrator-client /usr/local/bin/
chmod +x /usr/local/bin/orchestrator-client

5.1 client 环境变量(关键,本次踩坑)

# 执行节点: 141/142/143
cat > /etc/profile.d/orchestrator-client.sh << 'EOF'
export ORCHESTRATOR_API="http://127.0.0.1:3000/api"     # 必须带 /api 后缀!
export ORCHESTRATOR_AUTH_USER="orch_api"                # 变量名是 AUTH_USER 不是 USER
export ORCHESTRATOR_AUTH_PASSWORD="Orch@123456"
EOF
. /etc/profile.d/orchestrator-client.sh
# 验证: orchestrator-client -c clusters

⚠️ 排错记录:变量名写 ORCHESTRATOR_USER 无效(脚本读 ORCHESTRATOR_AUTH_USER);API 不带 /api 后缀时部分命令 404。ORCHESTRATOR_API 支持多节点空格分隔,client 自动探测 leader。


六、配置文件逐项讲解(orchestrator.conf.json)

完整配置(三节点仅 RaftBind 不同,其余一致):

{
  "Debug": false,                          // 调试模式,生产关闭
  "EnableSyslog": false,                   // 是否输出到系统日志
  "ListenAddress": ":3000",                // Web/API 监听端口
  "HTTPAuthUser": "orch_api",              // Web/API Basic认证用户
  "HTTPAuthPassword": "Orch@123456",

  "MySQLTopologyUser": "orch_client",      // 探测被监控集群的账号(所有实例都要有)
  "MySQLTopologyPassword": "Orch@123456",
  "MySQLTopologyUseMutualTLS": false,
  "MySQLTopologySSLSkipVerify": true,
  "MySQLTopologyMaxPoolConnections": 3,

  "MySQLOrchestratorHost": "192.168.195.143",  // 元数据库地址(专属后端,可远程)
  "MySQLOrchestratorPort": 3307,
  "MySQLOrchestratorDatabase": "orchestrator",
  "MySQLOrchestratorUser": "orchestrator",
  "MySQLOrchestratorPassword": "Orch@123456",

  "MySQLConnectTimeoutSeconds": 1,         // 连接 MySQL 超时
  "DefaultInstancePort": 3306,             // 被监控实例默认端口
  "DiscoverByShowSlaveHosts": true,        // 优先 show slave hosts 发现(依赖report_host)
  "InstancePollSeconds": 5,                // 探测间隔(敏感度核心参数,见第十一章)
  "SkipMaxScaleCheck": true,               // 无 MaxScale binlog server 设 true
  "UnseenInstanceForgetHours": 240,        // 消失实例保留时长
  "SnapshotTopologiesIntervalHours": 0,    // 拓扑快照间隔,0禁用
  "InstanceBulkOperationsWaitTimeoutSeconds": 10,
  "HostnameResolveMethod": "none",         // 不做DNS解析(用IP环境)
  "MySQLHostnameResolveMethod": "@@report_host",  // 用 report_host 识别主机
  "SkipBinlogServerUnresolveCheck": true,
  "ExpiryHostnameResolvesMinutes": 60,
  "RejectHostnameResolvePattern": "",
  "ReasonableReplicationLagSeconds": 10,   // 延迟>10s视为异常
  "ProblemIgnoreHostnameFilters": [],
  "VerifyReplicationFilters": false,
  "ReasonableMaintenanceReplicationLagSeconds": 20,  // 上移/下移维护阈值
  "CandidateInstanceExpireMinutes": 60,
  "AuditLogFile": "",                      // 审计日志文件(空=写元数据库audit表)
  "AuditToSyslog": false,
  "RemoveTextFromHostnameDisplay": ":3306",
  "ReadOnly": false,                       // 全局只读模式(false才能执行变更)
  "AuthenticationMethod": "basic",

  "FailMasterPromotionIfSQLThreadNotUpToDate": true,  // SQL线程未追平禁止提升(数据安全)
  "MasterFailoverLostInstancesDowntimeMinutes": 0,
  "MasterFailoverDetachSlaveMasterHost": false,
  "ApplyMySQLPromotionAfterMasterFailover": true,     // 提升后自动设 read_only=0 等
  "PreventCrossDataCenterMasterFailover": false,
  "DetachLostSlavesAfterMasterFailover": true,        // 故障中丢失的从库自动detach

  "RecoverMasterClusterFilters": ["*"],               // 允许自动主库恢复的集群
  "RecoverIntermediateMasterClusterFilters": ["*"],   // 允许自动中间主库恢复
  "RecoveryPeriodBlockSeconds": 3600,       // 恢复阻塞期:1小时内同集群不重复自动切换(防抖)
  "FailureDetectionPeriodBlockMinutes": 60, // 故障检测阻塞期

  "OnFailureDetectionProcesses": [          // 故障检测到时触发(报警)
    "/home/orchestrator/hooks/notify.sh OnFailureDetection {failureType} {failureCluster} {failedHost}:{failedPort}"
  ],
  "PreFailoverProcesses": [                 // 故障转移前触发(最后检查/杀旧主)
    "/home/orchestrator/hooks/notify.sh PreFailover {failureType} {failureCluster} {failedHost}:{failedPort} -> {successorHost}:{successorPort}"
  ],
  "PostFailoverProcesses": [                // 故障转移后触发(VIP漂移+通知)
    "/home/orchestrator/hooks/vip_failover.sh {failedHost} {successorHost}",
    "/home/orchestrator/hooks/notify.sh PostFailover {failureType} {failureCluster} {failedHost}:{failedPort} -> {successorHost}:{successorPort}"
  ],
  "PostMasterFailoverProcesses": [
    "/home/orchestrator/hooks/notify.sh PostMasterFailover {failureCluster} promoted {successorHost}:{successorPort}"
  ],
  "PostIntermediateMasterFailoverProcesses": [
    "/home/orchestrator/hooks/notify.sh PostIntermediateMasterFailover {failureCluster} {successorHost}:{successorPort}"
  ],
  "PostUnsuccessfulFailoverProcesses": [    // 转移失败时触发(回退+人工介入报警)
    "/home/orchestrator/hooks/notify.sh PostUnsuccessfulFailover FAILED {failureCluster} {failedHost}:{failedPort}"
  ],
  "PreGracefulTakeoverProcesses": [
    "/home/orchestrator/hooks/notify.sh PreGracefulTakeover {failureCluster} master {failedHost}:{failedPort}"
  ],
  "PostGracefulTakeoverProcesses": [
    "/home/orchestrator/hooks/vip_failover.sh {failedHost} {successorHost}",
    "/home/orchestrator/hooks/notify.sh PostGracefulTakeover {failureCluster} new master {successorHost}:{successorPort}"
  ],

  "RaftEnabled": true,                      // raft 多点模式(生产推荐)
  "RaftBind": "192.168.195.141",            // 本节点IP(三节点各不同!)
  "RaftDataDir": "/var/lib/orchestrator",
  "DefaultRaftPort": 10008,                 // raft 通信端口(三节点一致)
  "RaftNodes": [                            // 全部节点列表
    "192.168.195.141", "192.168.195.142", "192.168.195.143"
  ],
  "BackendDB": "mysql"
}

Hook 占位符(PDF 原文):{failureType} {failureDescription} {command} {failedHost} {failureCluster} {failureClusterAlias} {failureClusterDomain} {failedPort} {successorHost} {successorPort} {successorAlias} {countReplicas} {replicaHosts} {isDowntimed} {lostReplicas} {countLostReplicas} {isSuccessful} 等。


七、systemd 服务与启动

# 执行节点: 141/142/143
cat > /etc/systemd/system/orchestrator.service << 'EOF'
[Unit]
Description=orchestrator: MySQL replication management and visualization
Documentation=https://github.com/openark/orchestrator
After=syslog.target network.target mysqld.service

[Service]
User=root
Group=root
Type=simple
WorkingDirectory=/home/orchestrator
ExecStart=/home/orchestrator/orchestrator --config=/home/orchestrator/orchestrator.conf.json http
EnvironmentFile=-/etc/sysconfig/orchestrator
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload && systemctl start orchestrator && systemctl enable orchestrator

7.1 raft 集群验证

# 执行节点: 任意
curl -s -u orch_api:Orch@123456 http://127.0.0.1:3000/api/leader-check
# Leader 节点返回 "OK",Follower 返回 "Not leader"
# 也可看日志: journalctl -u orchestrator | grep -iE 'raft.*(leader|follower)'
# 本次结果: 141=Leader, 142/143=Follower

7.2 接入被监控集群(发现)

# 执行节点: 任意(自动转发到leader)
curl -s -u orch_api:Orch@123456 -X POST 'http://127.0.0.1:3000/api/discover/192.168.195.141/3306'
# 成功返回 {"Code":"OK","Message":"Instance discovered: 192.168.195.141:3306",...}

# 查看拓扑(自动发现整个集群):
orchestrator-client -c topology -i 192.168.195.141:3306
192.168.195.141:3306   [0s,ok,8.0.35,rw,ROW,>>,GTID]
+ 192.168.195.142:3306 [0s,ok,8.0.35,ro,ROW,>>,GTID]
+ 192.168.195.143:3306 [0s,ok,8.0.35,ro,ROW,>>,GTID]

⚠️ 本次踩坑:改密码等本地事务会让从库产生 errant GTID(拓扑显示 GTID:errant),会阻碍后续故障切换。解决:从库 STOP SLAVE; RESET SLAVE ALL; RESET MASTER; 后重搭复制(见 PDF"集群 GTID 复制不统一"同源问题)。


八、邮件与 VIP(第三方最小化服务)

8.1 最小化 SMTP 服务(143)

# 执行节点: 143  —— /opt/smtp_server.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""最小化SMTP邮件服务器:监听25端口,收到的邮件存为.eml文件"""
import smtpd, asyncore, os, time
MAILDIR = '/var/mailbox'
os.makedirs(MAILDIR, exist_ok=True)
class MailServer(smtpd.SMTPServer):
    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
        ts = time.strftime('%Y%m%d_%H%M%S')
        fname = os.path.join(MAILDIR, 'mail_%s.eml' % ts)
        with open(fname, 'wb') as f:
            f.write(data if isinstance(data, bytes) else data.encode())
        print('[SMTP] saved %s from=%s to=%s' % (fname, mailfrom, rcpttos), flush=True)
if __name__ == '__main__':
    MailServer(('0.0.0.0', 25), None)
    asyncore.loop()

# systemd 服务:
cat > /etc/systemd/system/orch-smtp.service << 'EOF'
[Unit]
Description=Minimal SMTP server for orchestrator alerts
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /opt/smtp_server.py
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload && systemctl start orch-smtp && systemctl enable orch-smtp
ss -tlnp | grep ':25 '    # 验证监听

8.2 Hook 脚本(三节点分发至 /home/orchestrator/hooks/)

notify.sh(通用通知:日志+邮件)

#!/bin/bash
# 用法: notify.sh <事件类型> <附加信息...>
EVENT="$1"; shift
MSG="[$(date '+%F %T')] [$EVENT] $*"
mkdir -p /var/log/orchestrator
echo "$MSG" >> /var/log/orchestrator/hooks.log      # 本地hook日志
python3 - << PYEOF 2>/dev/null                       # 发邮件到143 SMTP
import smtplib
from email.mime.text import MIMEText
from email.header import Header
msg = MIMEText("""$MSG""", 'plain', 'utf-8')
msg['Subject'] = Header('[Orchestrator] %s 告警通知' % '$EVENT', 'utf-8')
msg['From'] = 'orchestrator@orch.local'
msg['To'] = 'dba-alert@orch.local'
s = smtplib.SMTP('192.168.195.143', 25, timeout=5)
s.send_message(msg); s.quit()
PYEOF
exit 0

vip_failover.sh(VIP 漂移)

#!/bin/bash
# 用法: vip_failover.sh <旧主IP> <新主IP>
OLD_MASTER="$1"; NEW_MASTER="$2"
VIP="192.168.195.200/24"; DEV="ens32"; LABEL="ens32:0"
# 1.所有节点先清残留VIP(避免双VIP脑裂)
for h in 192.168.195.141 192.168.195.142 192.168.195.143; do
  ssh -o ConnectTimeout=3 root@${h} "ip addr del ${VIP} dev ${DEV} label ${LABEL}" 2>/dev/null
done
# 2.新主绑定VIP
ssh -o ConnectTimeout=3 root@${NEW_MASTER} "ip addr add ${VIP} dev ${DEV} label ${LABEL}"
# 3.验证并记录
sleep 1
CHECK=$(ssh -o ConnectTimeout=3 root@${NEW_MASTER} "ip addr show ${DEV} | grep '${VIP}'" 2>/dev/null)
if [ -n "$CHECK" ]; then
  echo "[$(date '+%F %T')] [VIP] ${VIP} moved ${OLD_MASTER} -> ${NEW_MASTER} OK" >> /var/log/orchestrator/hooks.log
else
  echo "[$(date '+%F %T')] [VIP] WARNING: ${VIP} not found on ${NEW_MASTER}" >> /var/log/orchestrator/hooks.log
fi
exit 0
# 分发与权限(三节点):
chmod +x /home/orchestrator/hooks/*.sh
# 手动验证:
/home/orchestrator/hooks/notify.sh TEST_MAIL 手动测试     # 143:/var/mailbox 出现新邮件
/home/orchestrator/hooks/vip_failover.sh 192.168.195.141 192.168.195.142  # VIP漂到142

8.3 初始 VIP 绑定

# 执行节点: 141(初始主)
ip addr add 192.168.195.200/24 dev ens32 label ens32:0
ip addr show ens32 | grep 192.168.195.200   # 验证

九、企业级场景全验证(实测记录)

场景A:拓扑重构(Refactoring)—— 级联与还原 ✅

A1. move-below:把 143 挂到 142 下(141->142->143 级联)

# 执行节点: 141
orchestrator-client -c move-below -i 192.168.195.143:3306 -d 192.168.195.142:3306
# 输出: 192.168.195.143:3306<192.168.195.142:3306

orchestrator-client -c topology -i 192.168.195.141:3306
192.168.195.141:3306     [0s,ok,8.0.35,rw,ROW,>>,GTID]
+ 192.168.195.142:3306   [0s,ok,8.0.35,ro,ROW,>>,GTID]
  + 192.168.195.143:3306 [0s,ok,8.0.35,ro,ROW,>>,GTID]    # 缩进=级联层级

验证(143 上 Master_Host 变为 142,双 Yes):

# 执行节点: 143
/usr/local/mysql/bin/mysql -uroot -S /data/mysql/3306/data/mysql.sock -p'Root@123456' -e 'show slave status\G' | grep -E '(Master_Host|Slave_IO_Running|Slave_SQL_Running)'
# Master_Host: 192.168.195.142  /  Slave_IO_Running: Yes  /  Slave_SQL_Running: Yes

级联下写入同步验证:141 插入 2 行 -> 142/143 均 5 行,一致。

A2. move-up:还原一主两从

# 执行节点: 141
orchestrator-client -c move-up -i 192.168.195.143:3306
# 输出: 192.168.195.143:3306<192.168.195.141:3306

知识点:move-below 要求两实例同主(兄弟关系);relocate 是通用移动;GTID 拓扑下这些操作安全无损。

场景B:手动在线切换(graceful-master-takeover,141->142)✅

# 执行节点: 141
orchestrator-client -c graceful-master-takeover -i 192.168.195.141:3306 -d 192.168.195.142:3306
# 输出: 192.168.195.142:3306 (成功)

切换后实测状态:

192.168.195.142:3306   [0s,ok,8.0.35,rw,ROW,>>,GTID]        # 142成为新主(rw)
- 192.168.195.141:3306 [null,nonreplicating,...,downtimed]   # 旧主暂时downtimed
+ 192.168.195.143:3306 [0s,ok,...,GTID]                      # 143跟随新主

Hook 链路(141:/var/log/orchestrator/hooks.log 实录):

[15:40:56] [PreFailover] DeadMaster 192.168.195.141:3306 ...
[15:40:56] [PostMasterFailover] 192.168.195.141:3306 promoted 192.168.195.142:3306
[15:40:58] [VIP] 192.168.195.200/24 moved 192.168.195.141 -> 192.168.195.142 OK
[15:41:00] [PostGracefulTakeover] 192.168.195.141:3306 new master 192.168.195.142:3306

旧主 141 手动加回(PDF:“DBA 对旧主故障处理完成后,手动将旧主加回到集群”)

# 执行节点: 141  -- takeover后旧主复制账号字段被清空,需补上再启动
/usr/local/mysql/bin/mysql -uroot -S /data/mysql/3306/data/mysql.sock -p'Root@123456' -e "
SET GLOBAL super_read_only=0;
CHANGE MASTER TO MASTER_USER='repl', MASTER_PASSWORD='123456';
START SLAVE;"
# 验证: 双Yes, 数据追平(orch_test.t1 三台都=6行)

# 清除downtime标记:
orchestrator-client -c end-downtime -i 192.168.195.141:3306

⚠️ 首次切换报错 Relocating 1 replicas ... turns to be too complex:因 143 与 142 不满足直接迁移条件。解法:先 move-below 把 143 挂到 142 下再切换,或直接指定目标。这是实际生产中规划切换路径的典型案例。

场景C:主库故障自动 failover(142 主宕机)✅

# 执行节点: 142  -- 预埋测试数据
/usr/local/mysql/bin/mysql ... -e "CREATE TABLE orch_test.failover_log (id INT PRIMARY KEY AUTO_INCREMENT, event VARCHAR(100), ts DATETIME DEFAULT CURRENT_TIMESTAMP);
INSERT INTO orch_test.failover_log(event) VALUES ('pre_failover_1'),('pre_failover_2'),('pre_failover_3');"

# 模拟宕机(注意用stop,kill -9会被systemd Restart=on-failure拉起,掩盖故障):
systemctl stop mysqld

故障检测(约 2 个探测周期后)

orchestrator-client -c replication-analysis
# 192.168.195.142:3306 (cluster 192.168.195.142:3306): DeadMaster

ack 阻塞机制(PDF 核心章节,本次完整触发)
自动恢复被阻塞(142 之前被提升过,处于 active period):

ERROR AttemptRecoveryRegistration: instance 192.168.195.142:3306 has recently been promoted
(by failover of 192.168.195.141:3306) and is in active period. It will not be failed over.
You may acknowledge the failure ... (-c ack-cluster-recoveries)

解除阻塞并确认(RecoveryPeriodBlockSeconds=3600 防抖的官方途径):

# 执行节点: 141
orchestrator-client -c ack-cluster-recoveries -a 192.168.195.141:3306 -reason "drill ack"
# 或 API: curl -u orch_api:Orch@123456 "http://127.0.0.1:3000/api/ack-recovery/cluster/192.168.195.141:3306?comment=drill"

failover 执行(hook 日志实录)

[15:44:16] [OnFailureDetection] DeadMaster 192.168.195.142:3306
[15:46:17] [PreFailover] DeadMaster 192.168.195.142:3306
[15:46:17] [PostMasterFailover] 192.168.195.142:3306 promoted 192.168.195.141:3306
[15:46:19] [VIP] 192.168.195.200/24 moved -> 141
[15:46:19] [PostFailover] DeadMaster 192.168.195.142:3306

数据零丢失验证:141 提升后 SELECT COUNT(*) FROM orch_test.failover_log = 3(宕机前 3 条全在)——GTID 复制 failover 数据完整的实证。

恢复 142/143 加回(PDF 手动流程):142 修复后 RESET SLAVE ALL; CHANGE MASTER TO MASTER_HOST='192.168.195.141' ... MASTER_AUTO_POSITION=1; START SLAVE;,143 同理重定向。

场景D:中间主库故障(IntermediateMaster)⚠️ 部分自动+手动恢复

级联 141->142->143 后停 142:

orchestrator-client -c replication-analysis
# 192.168.195.141:3306: MasterSingleReplicaDead
# 192.168.195.142:3306: DeadIntermediateMasterWithSingleReplica   <- IM故障正确识别

自动恢复未触发的根因排查(重要实战经验)

  • curl /api/recover/192.168.195.142/3306 返回 Recovery not attempted
  • 查分析详情:143 的 Slave_IO_Running=false(60 秒重连退避中),orchestrator 读到 UsingOracleGTID=false,将 IM 子树判定为非 GTID 拓扑,要求 Pseudo-GTID(未配置)而拒绝自动恢复;
  • 这正是 PDF"集群 gtid 复制不统一"案例的变体:故障瞬间的从库 IO 退避状态会误导 GTID 判定

手动恢复路径(生产 SOP)

# 方法1: orchestrator relocate-replicas(适合从库状态正常时)
orchestrator-client -c relocate-replicas -i 192.168.195.142:3306 -d 192.168.195.141:3306
# 方法2: 直接MySQL层把143重定向(本次采用,简单可靠)
# 执行节点: 143
STOP SLAVE; CHANGE MASTER TO MASTER_HOST='192.168.195.141', ..., MASTER_AUTO_POSITION=1; START SLAVE;

场景E:Orchestrator 自身高可用(raft leader 切换)✅

# 执行节点: 141(当前leader)
systemctl stop orchestrator
sleep 20
# 执行节点: 142/143 分别检查:
curl -s -o /dev/null -w "%{http_code}" -u orch_api:Orch@123456 http://127.0.0.1:3000/api/leader-check
# 142 -> 200 (新leader)   143 -> 404(Not leader响应)
# 新leader的拓扑视图完整,client自动跟随:
orchestrator-client -c topology -i 192.168.195.141:3306   # 正常输出

# 恢复141后:
systemctl start orchestrator   # 141回到集群,成为Follower
curl .../api/health | jq '.Details | {IsActiveNode, ActiveNodeHostname}'
# {"IsActiveNode": false, "ActiveNodeHostname": "192.168.195.142:10008"}  <- 142继续服务

场景F:auto_position=0 引发 Pseudo-GTID 需求(PDF 案例)✅

# 执行节点: 143  -- 模拟某从库被改成位置点复制
STOP SLAVE;
CHANGE REPLICATION SOURCE TO SOURCE_AUTO_POSITION=0, SOURCE_LOG_FILE='mysql-bin.000004', SOURCE_LOG_POS=4;
START SLAVE;

PDF 同款定位 SQL(元数据库)

-- 执行节点: 143(元数据库3307)
SELECT hostname, port, oracle_gtid FROM orchestrator.database_instance;
+-----------------+-------------+
| hostname        | oracle_gtid |
+-----------------+-------------+
| 192.168.195.141 |           0 |
| 192.168.195.142 |           1 |
| 192.168.195.143 |           1 |   <- auto_position=1的从库标记为1
+-----------------+-------------+
-- oracle_gtid=0 的节点会导致后续拓扑操作走 Pseudo-GTID 分支(未配置则失败)

修复(PDF:去对应节点 change master 把 auto_position 改回 1):

# 执行节点: 143
STOP SLAVE; CHANGE REPLICATION SOURCE TO SOURCE_AUTO_POSITION=1; START SLAVE;

注:主库 oracle_gtid=0 是正常现象(主库不使用复制),只关注从库。


十、Web UI 与 API 操作指引(需手动页面操作部分)

10.1 Web 访问

浏览器打开(三个节点任一,只有 leader 可写):

http://192.168.195.141:3000    (认证: orch_api / Orch@123456)
http://192.168.195.142:3000
http://192.168.195.143:3000

10.2 手动页面操作步骤(老板请按此操作)

  1. 登录:输入 Basic 认证 orch_api / Orch@123456
  2. 查看集群:首页 Clusters 列表点击 192.168.195.141:3306
  3. 看拓扑图:Web 页面显示 141 主 + 142/143 从的树状图,绿色=正常
  4. 常用页面操作
    • 拖拽从库节点到另一个主库节点 = relocate(对应命令行 move-below)
    • 点击节点 -> “Properties” 查看实例详情
    • 顶部 “Audit” 页面查看所有操作审计
    • “Audit / Recovery” 页面可对故障恢复进行 ack 确认(对应 ack-cluster-recoveries)
  5. 模拟演练(页面版):发现 -> 拖拽重构 -> 观察拓扑变化

10.3 API 速查(实测全部可用)

A="orch_api:Orch@123456"; B="http://127.0.0.1:3000/api"
curl -s -u $A $B/clusters                                    # 集群列表
curl -s -u $A $B/topology/192.168.195.141/3306               # 拓扑JSON
curl -s -u $A $B/instance/192.168.195.143/3306               # 实例详情
curl -s -u $A $B/replication-analysis                        # 故障分析
curl -s -u $A $B/audit-recovery                              # 恢复审计
curl -s -u $A $B/health                                      # 节点健康/raft状态
curl -s -u $A -X POST $B/discover/192.168.195.141/3306       # 发现实例
curl -s -u $A -X POST $B/forget/192.168.195.142/3306         # 忘记实例
curl -s -u $A "$B/ack-recovery/cluster/192.168.195.141:3306?comment=xx"  # ack

10.4 orchestrator-client 常用命令(实测)

orchestrator-client -c clusters                              # 集群列表
orchestrator-client -c topology -i <host>:3306               # 拓扑
orchestrator-client -c all-instances                         # 全部实例
orchestrator-client -c which-cluster -i <host>:3306          # 实例归属
orchestrator-client -c replication-analysis                  # 故障分析
orchestrator-client -c discover -i <host>:3306               # 发现
orchestrator-client -c forget -i <host>:3306                 # 忘记
orchestrator-client -c move-below -i <>:3306 -d <新主>:3306 # 级联挂载
orchestrator-client -c move-up -i <>:3306                  # 上移一层
orchestrator-client -c relocate-replicas -i <实例>:3306 -d <目标>:3306
orchestrator-client -c graceful-master-takeover -i <>:3306 -d <新主>:3306  # 在线切换
orchestrator-client -c recover -i <故障实例>:3306            # 手动恢复(忽略阻塞)
orchestrator-client -c force-master-failover -i <>:3306    # 强制切换
orchestrator-client -c begin-downtime -i <实例>:3306 -reason "维护" -duration 30m
orchestrator-client -c end-downtime -i <实例>:3306
orchestrator-client -c ack-cluster-recoveries -a <集群> -reason "xx"
orchestrator-client -c register-candidate -i <实例>:3306 --promotion-rule=prefer

十一、故障敏感度调整(PDF 章节)

  • InstancePollSeconds(默认5):探测间隔。调大可过滤短抖动(如30秒间隔可忽略大部分10秒级抖动)。
  • ReasonableInstanceCheckSeconds:单次探测允许的最长查询时间。实例能连但查询慢时,调大此参数容忍。
  • MySQLDiscoveryReadTimeoutSeconds(默认10):discover 查询超时,应 >= ReasonableInstanceCheckSeconds。
  • RecoveryPeriodBlockSeconds(本次=3600):同集群自动恢复阻塞期,防级联故障抖动。
  • FailureDetectionPeriodBlockMinutes(本次=60):故障重复检测阻塞期。

生产建议:小集群 InstancePollSeconds=5 保持快速检测;抖动频繁的环境调到 10~15;配合 VIP 健康检查阈值一起调。


十二、常见故障案例与解决(PDF 全部案例 + 本次新增实测)

#故障现象解决
1元数据库用只读从库orchestrator 起不来, journalctl 报 Error 1290 --read-only option元数据库用独立可写实例(本次143:3307)
2errant GTID拓扑显示 GTID:errant, failover 被阻从库 STOP SLAVE; RESET SLAVE ALL; RESET MASTER; 重搭复制
3主从版本不一致切换失败(版本校验不匹配)同集群保持同版本(PDF案例)
4复制异常+主宕机数据不一致风险切换前检查 SQL 线程 Yes(FailMasterPromotionIfSQLThreadNotUpToDate=true)
5从库 auto_position=0拓扑操作走 Pseudo-GTID 失败元数据库 SELECT * FROM database_instance WHERE oracle_gtid=0 定位, CHANGE MASTER 改回
6IM 故障但从库 IO 退避自动恢复返回 not attempted手动 relocate-replicas 或 MySQL 层重定向; 预防:Pseudo-GTID 或保持从库健康
7演练时 kill -9 被 systemd 拉起故障被掩盖,无法触发检测演练用 systemctl stop; 或临时 systemctl edit 去掉 Restart
8旧主重启变只读my.cnf 遗留 read_only=ON, 重启后新主只读所有节点 my.cnf 不写 read_only, Orchestrator 动态管理
9阻塞期内二次故障恢复被 active period 阻塞ack-cluster-recoveries -a <集群> -reason "xx" 解除
10orchestrator-client 连不上Cannot access orchestrator检查 ORCHESTRATOR_API 带 /api; 变量名 ORCHESTRATOR_AUTH_USER/PASSWORD
11graceful 切换报 too complex兄弟从库不满足直接迁移先 move-below 调整结构再切换, 或手动分步

十三、知识点补充(超出 PDF 的实战总结)

  1. raft 写转发:任何节点的 API/写操作自动转发给 leader,client 配多个地址自动探测。Follower 上 leader-check 返回非200。
  2. 拓扑发现机制(PDF 原理验证):DiscoverByShowSlaveHosts=true 时靠从库 report_host 参数(show slave hosts);否则走 information_schema.processlist 的 Binlog Dump。本次 report_host 直配 IP,发现秒级。
  3. GTID errant 的危害链:改密码等本地事务 → 从库 gtid_executed 多出主库没有的事务 → orchestrator 标记 errant → failover 候选被排除或恢复失败。预防:从库管理操作一律走 SQL_TIMEOUT 会长事务的方式,或操作前 SET SESSION sql_log_bin=0
  4. failover 数据零丢失的条件:GTID + 从库 SQL 线程追平(FailMasterPromotionIfSQLThreadNotUpToDate=true 强制)。本次 failover_log 3条全在实证。
  5. VIP Hook 时序:PostFailoverProcesses 数组按序执行,vip_failover.sh 放第一个保证业务尽快恢复;脚本里"先全网清理再绑定"避免双 VIP 脑裂。
  6. 元数据库表速查database_instance(实例状态) / topology_recovery(恢复记录) / topology_failure_detection(故障检测) / database_instance_downtime(维护标记) / audit(审计) / cluster_alias(集群别名)。
  7. 注册优先候选register-candidate --promotion-rule=prefer 可指定故障切换优先提升某从库(prefer/neutral/exclude)。

十四、本次验证结果总表

场景验证内容结果
安装三节点 orchestrator 3.2.6 + systemd + client
配置raft 模式 + Hook + 认证 + 元数据库
运行leader 选举(141)、写转发、发现集群
监控拓扑实时刷新、replication-analysis、problems
场景Amove-below 级联 + move-up 还原 + 数据同步
场景Bgraceful-master-takeover 全链路(VIP/邮件/旧主回收)
场景CDeadMaster 自动 failover + ack 阻塞解除 + 数据零丢失
场景DIM 故障识别 + GTID 判定陷阱 + 手动恢复⚠️ 自动恢复受限(已分析根因,给出SOP)
场景Eraft leader 宕机 follower 接管 + 回归
场景Fauto_position=0 定位与修复(元数据库SQL)
邮件9封告警邮件实测送达
VIP4次漂移实测(含hook自动)

环境 141+142+143 当前状态:141 主库 + 142/143 从库,全部双 Yes;orchestrator 三节点 active(leader=142);VIP 在 141;元数据库 143:3307。
说明:场景C后 leader 为 141→场景E后为 142,最终 142 为 leader,141 为 follower( IsActiveNode:false )。业务主库仍为 141。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值