Orchestrator Hook:让MySQL高可用切换不再靠“人肉”搬VIP
干了这么多年MySQL运维,我见过太多的主从切换现场:半夜两点,告警电话把人吵醒,登录主库一看磁盘满了,再一看从库延时两万秒,只好硬着头皮做切换。切换本身不难,stop slave; change master to; start slave;一条龙下来也就几十秒。真正让人头疼的反而不是数据同步,而是切换完之后那一堆“周边杂事”——比如VIP(虚拟IP)要不要漂移、监控告警要不要改、下游消费端连接的IP到底指向谁。
前几年还没有Orchestrator的时候,我们内部搞的所谓“高可用”其实就是写一堆shell脚本,探测主库健康状态,发现挂了就去某个从库上执行change master,然后手动把VIP指过去。这方案能用,但问题不少:脚本自身会不会误判、切换日志靠不靠谱、分布式锁怎么处理。后来引入了Orchestrator,用它的Hook机制把VIP管理和切换流程打通,才真正体会到“数据库自动切换”和“应用无感知”之间那块拼图到底长什么样。
今天这篇就围绕Orchestrator Hook来聊。适合正在搞MySQL高可用、打算引入Orchestrator但还没弄懂Hook机制怎么落地的同学,也适合那些已经跑起来但只是把Hook当作“切换后发个通知”的团队——因为你可能浪费了它一半的能力。
1. 高可用切换的最后一公里:为什么必须有Hook
1.1 没有Hook之前,我们是怎么被VIP折磨的
先描述一个最常见的场景。你有一套MySQL主从架构,主库192.168.1.10,从库192.168.1.11和192.168.1.12。为了应用不用来回改连接地址,你在前面挂了一个VIP(比如192.168.1.100),平时VIP落在主库上,应用连接的是VIP,主库挂了VIP就漂到新主上。
听起来挺美好。问题在于“怎么漂”。早期方案分两派。
第一派是用Keepalived,主库上跑着Keepalived的vrrp实例,主库挂了VIP由Keepalived自动漂到备用节点。这方案对于“独主单从”的简单架构没问题,但Keepalived只知道IP有没有通,它不知道MySQL复制拓扑里谁才是当前真正的主库。假如主库没宕机但MySQL进程hang死了,Keepalived认为节点还活着,VIP死活不漂,应用那边的连接全部堵塞——这种场景我真实遇过好几次。
第二派是自己写脚本。通过mysqladmin ping检查主库状态,连续几次不通就执行VIP漂移脚本。这方案的毛病更隐蔽:如果网络抖动导致脚本误判,本来主库好好的结果VIP被挪走了,业务直接被切到从库,线上就真“从”了。又或者两个节点同时认为自己是主,各自绑定同一VIP,出现IP冲突,整个局域网都跟着遭殃。
这两种方案本质上都缺一个东西:一个全局的、能理解复制拓扑的、“知道”谁才是真正主库的决策者。Orchestrator正好是干这个的,而Hook则是它把决策结果“输出”给外部系统的出口。
1.2 Orchestrator是什么,Hook塞在哪个环节
Orchestrator是GitHub开源的一个MySQL复制拓扑管理工具。它做的事情大致有三件:第一,自动发现你的MySQL实例并构建出完整的复制拓扑图;第二,持续探测每个实例的存活状态和复制延迟;第三,在主库故障时自动执行恢复流程,把最合适的从库提升为新主,并让其他从库重新指向新主。
恢复流程本身有一套状态机,大概路径是:检测到主库不可达 → 进入候选库评估 → 提升候选库为主库 → 通知所有从库change master → 更新拓扑信息。Orchestrator的Hook机制就是在这些关键节点上“插一脚”,允许你执行自定义脚本。它的配置文件是JSON格式,里面的Hooks段落定义了不同事件的回调。
{ "Hooks": { "PostFailover": [ { "Command": "/usr/local/mysql-ha/vip_drifter.sh", "Args": ["{{IsSuccess}}", "{{FailedHostname}}", "{{SuccessorHostname}}"] } ], "PostIntermediateMasterFailover": [] } }上面这段是我配置里的实际片段。PostFailover事件会在一个主库恢复流程成功结束后触发,这个时机点非常关键:旧主已经被隔离,新主已经在线,所有从库已经重指向。这时候漂移VIP,是最安全、最不容易出岔子的窗口。
很多第一次接触Orchestrator的同学会有一个误区:以为Hook就是“切换后发个邮件打个电话”的辅助功能。实际上,VIP管理、DNS更新、注册中心下线、监控自愈、下游消息队列的Producer重连——这些全是Hook能干的活。你完全可以把Orchestrator理解成一个大脑,Hook就是它的手和脚。
2. Hook配置深度拆解:事件类型、模板变量与执行机制
2.1 必须搞懂的Hook事件类型
Orchestrator里的Hook事件不算多,但每个的触发时机和适用场景差别很大。用错了事件,就可能发生VIP还没准备好就被绑定、或者该清理的IP没清理的尴尬。
先上表格,这是我自己整理的一份速查表,给读者交个底:
| 事件名 | 触发时机 | 典型用途 |
|---|---|---|
PreFailover | 恢复流程开始前,尚未选主时 | 通知上下游准备、暂时隔离流量 |
PostFailover | 恢复流程成功结束后 | VIP漂移、DNS更新、告警恢复 |
PostUnsuccessfulFailover | 恢复流程失败后 | 升级告警、通知DBA介入 |
PostIntermediateMasterFailover | 中间级主库(中继主)发生切换后 | 级联复制场景下的VIP或连接调整 |
PostPrimarySecondaryMasterFailover | 主库的Secondary Master切换后 | 复杂拓扑中辅助主库的联动处理 |
PostDeadMasterRemoval | 死主被移除出拓扑后 | 清理监控项、回收资源 |
最常用的两个是PreFailover和PostFailover。前者我习惯用来在切换前把入口层的流量暂时摘掉——比如通知负载均衡设备把VIP下的后端摘除,防止切换过程中有请求打到正在提升的节点上。后者则是本次博文的重头戏:VIP漂移、把旧主的SSH访问禁掉、打开新主的访问策略等等。
这里必须强调:PostFailover定义的是“成功恢复”之后执行,但它不保证命令一定执行成功。Orchestrator只负责调用Hook脚本,不会管脚本里面发生了什么。脚本失败了,Orchestrator不会回滚切换动作,也不会重试Hook。所以Hook脚本本身必须尽可能健壮,写完一定要反复验证,别等线上出问题才去翻日志。
2.2 Hook模板变量:从JSON传参到Shell变量的桥
Orchestrator在执行Hook命令时可以向命令后面拼参数,参数来自一组预定义的模板变量。这些变量是Orchestrator在执行时动态填充的,非常关键。
常用变量包括:
{{IsSuccess}}:恢复是否成功,值为true或false{{FailedHostname}}:旧主(故障实例)的主机名{{SuccessorHostname}}:新任主库(提升成功的实例)的主机名{{FailureType}}:失败类型,比如RegisterFailure、DeadMaster{{Command}}:实际执行的Option命令名(比如begin-maintenance等){{ClusterName}}:集群的域名或名称{{Duration}}:恢复过程耗时(秒数)
注意主机名和IP在这里的区分:Orchestrator的拓扑存储通常以hostname:port为实例的键。但很多环境下主机名解析可能有问题——比如容器环境、跳板机环境,Orchestrator拿到的hostname可能没法被其他机器直接解析。这种情况下,你的Hook脚本不能直接用{{FailedHostname}}去连机器,得先在脚本里通过你自己的映射关系把它转换成IP。我在生产环境里的做法是:在每台MySQL机器上把hostname写入一个/etc/mysql-ha-node.info文件,同时注册到Orchestrator的实例键里,确保hostname能用dig或getent解析出来。另外,我在脚本里会优先通过Orchestrator的API去查询实例的IP信息,避免在脚本里硬编码映射表,维护成本太高。
模板变量拼接进命令时,Arg数组里每个元素对应一个位置参数。我见过有人图省事把所有变量拼成一个字符串传进去,结果Shell解析时遇到空格就裂开。正确姿势是每个变量单独作为一个Args数组元素。
{ "Command": "/usr/local/mysql-ha/handover_vip.sh", "Args": ["{{FailedHostname}}", "{{SuccessorHostname}}", "{{IsSuccess}}"] }脚本内部这样取:
#!/bin/bash FAILED_HOST="$1" NEW_MASTER_HOST="$2" IS_SUCCESS="$3" if [[ "$IS_SUCCESS" != "true" ]]; then echo "Failover not success, skip VIP drift." >> /var/log/mysql-ha/vip_drift.log exit 1 fi2.3 执行机制:阻塞还是非阻塞?超时怎么设
Orchestrator的Hook执行是阻塞式的,也就是说Orchestrator在Hook没执行完之前不会继续后面的流程。这个特性既是保险也是坑。
好处是,如果你在PreFailover里做了摘流量操作,Orchestrator会等你摘完再进行主从切换,避免切换的半中间请求打上来。坏处是,如果你的脚本里面有长时间阻塞的操作(比如等待锁、sleep、远程调用卡住了),会把恢复流程整个拖慢,甚至拖到Orchestrator自身超时。
所以Hook脚本必须自己定义超时。网上很多方案用timeout 10包住核心命令,跟我现在的做法一致——在脚本开头就设定timeout命令包裹所有外部调用。这里的注意点是:如果你在脚本里用mysql命令连接服务器,一定要加--connect-timeout和--max-allowed-packet等参数,同时配合timeout命令做双重保险,避免因为网络分区导致脚本挂住,最终把整个切换拖死。
另外,Orachstrator默认对Hook执行时间是有限制的(RecoveryPeriodBlockSeconds等参数相关),咱们这边分析后可以自行调整,但我个人不建议把Hook超时设得太大。VIP漂移这种操作,正常情况3~5秒内就该完成,超过10秒基本可以认定出问题了,与其干等不如先让Orchestrator把切换流程走完,VIP随后通过手动补漂。
3. 从架构设计到脚本落地:VIP管理完整方案
3.1 方案选型:为什么我不用Keepalived而是用脚本+ARP
你可能会问:既然VIP要漂移,直接上Keepalived让Orchestrator去操作Keepalived实例不就行了?说实话我也走过这条路,但最终放弃了,原因有两方面。
第一,Keepalived的vrrp协议需要节点之间定期互发组播报文,一旦网络层面隔离了组播或丢包严重,可能出现“双主同时持有VIP”的情况。这种问题排查起来非常痛苦,而且要动网络设备权限,DBA说了不算。
第二,Orchestrator自己已经有完整的故障判定逻辑了,再叠一个Keepalived等于搞了两套选主逻辑,两套逻辑之间一旦不一致,听谁的都别扭。VIP漂移本质上是一个“操作”而不是“决策”,应该由决策方(Orchestrator)触发操作,而不是让VIP自己通过健康检查去“猜”该不该漂。
我之前在极客时间的一篇文章里也提到过,MySQL高可用体系里最怕的就是各组件自己做主,缺少一个统一的指挥者。Orchestrator加脚本就是这么个分工:Orchestrator负责判断和决策,脚本负责执行和落地。所以我最终选的是“Orchestrator Hook + shell脚本直接绑定/解绑VIP”方案。
这个方案的核心思想:VIP的绑定和解绑全部由Hook脚本执行,脚本内部通过ip命令直接操作网卡,保证当前机器的VIP要么存在要么不存在,绝对不让两台机器同时持有同一个VIP。
3.2 VIP漂移脚本:从“能用”到“敢用”的关键细节
先上一版我用得最顺的漂移脚本,整体逻辑不复杂,但每个细节都是踩过坑之后才加上的。
#!/bin/bash # /usr/local/mysql-ha/vip_drift.sh # Usage: vip_drift.sh <FAILED_HOST> <NEW_MASTER_HOST> # 负责将VIP从旧主解绑、在新主上绑定 VIP="192.168.1.100" IFACE="eth0" VIP_PREFIX="24" FAILED_HOST="$1" NEW_MASTER_HOST="$2" LOG="/var/log/mysql-ha/vip_drift.log" # 带锁执行,防止多个Hook并发跑导致双绑 LOCK_FILE="/var/run/vip_drift.lock" exec 9>"$LOCK_FILE" if ! flock -n 9; then echo "$(date '+%F %T') [ERROR] Another vip_drift process running, exit." >> "$LOG" exit 1 fi # 先尝试从旧主上解绑(如果旧主还能连上的话) if [[ -n "$FAILED_HOST" ]]; then timeout 5 ssh -o StrictHostKeyChecking=no -o ConnectTimeout=3 \ "$FAILED_HOST" "sudo ip addr del $VIP/$VIP_PREFIX dev $IFACE 2>/dev/null; sudo arping -c 3 -U -I $IFACE -s $VIP 2>/dev/null; exit 0" fi # 在新主上绑定VIP,并发送免费ARP通告 NEW_MASTER_IP=$(dig +short "$NEW_MASTER_HOST" | head -1) if [[ -z "$NEW_MASTER_IP" ]]; then echo "$(date '+%F %T') [ERROR] Cannot resolve $NEW_MASTER_HOST" >> "$LOG" exit 1 fi timeout 5 ssh -o StrictHostKeyChecking=no -o ConnectTimeout=3 \ "$NEW_MASTER_HOST" "sudo ip addr add $VIP/$VIP_PREFIX dev $IFACE 2>/dev/null; sudo arping -c 3 -U -I $IFACE -s $VIP 2>/dev/null; exit 0" # 校验VIP是否确实在新主上 CURRENT_VIP_OWNER=$(ssh -o StrictHostKeyChecking=no -o ConnectTimeout=3 \ "$NEW_MASTER_HOST" "ip addr show $IFACE | grep $VIP | awk '{print \$2}' | cut -d'/' -f1") if [[ "$CURRENT_VIP_OWNER" == "$VIP" ]]; then echo "$(date '+%F %T') [INFO] VIP drifted to $NEW_MASTER_HOST successfully." >> "$LOG" else echo "$(date '+%F %T') [ERROR] VIP drift check failed, VIP not found on $NEW_MASTER_HOST." >> "$LOG" # 这里可以考虑调用外部告警接口,比如企业微信机器人 fi这个脚本有几个细节值得展开讲。
第一,arping免费ARP通告。IP绑定到网卡之后,交换机ARP缓存里可能还是旧位置。MAC地址没变的话问题不大,但换了一台机器(MAC变了)却不发ARP通告,下游交换机和网关可能半天都缓不过来,应用照样连不通。我曾经因为没有加这行arping,切换完VIP之后足足花了三十分钟排查“为什么IP明明通了却连不上MySQL”,最后发现是上游交换机老ARP条目没刷新。血泪教训。
第二,解绑旧主这一步用的是2>/dev/null加exit 0。为什么?因为切换过程中旧主可能已经彻底挂了,SSH根本连不上,timeout 5会超时,脚本会返回非零状态。如果脚本因为解绑失败而退出,新主上的绑定动作就不执行了,VIP就彻底没人绑定。所以解绑旧主这一步要“做了最好、做不了也得继续”。清理旧主上残留VIP不是最重要的,防止旧主恢复后双绑才是关键,这一步我会结合维护模式在Orchestrator里一起处理。
第三,用flock做互斥锁。Orchestrator同一个集群的恢复事件一般是串行的,但保险起见,加上文件锁避免脚本误并发,尤其在Orchestrator的Web UI上手动执行恢复时,可能在同一集群上触发多个恢复流程。这个锁很便宜,但能挡住最坏情况。
3.3 双主防脑裂:VIP管理里最容易被忽略的一环
VIP漂移最怕一个词:脑裂。新主已经绑上VIP了,旧主在故障恢复后又“活”过来,它不知道自己是旧主,或者维护脚本没清理干净,又把自己的IP加到网卡上。这时候网络上就有两台机器同时持有同一个VIP,谁先响应ARP谁就收到流量。
Orchestrator本身在恢复时会尝试给故障实例打上“dead”标记,也会通过SSH去停止旧主的MySQL,但MySQL可以停,网卡上的VIP可不归MySQL管。
我的做法是双保险。第一层,是上面的漂移脚本里检测到新主绑定成功后,会在Orchestrator的维护机制里给旧主加一条自动维护记录,防止它再次被选为主库。第二层,是写一个定期巡检脚本,每个节点每30秒检查一次自己是否“持有VIP且不是当前主库”。如果发现自己不是Orchestrator拓扑里的主库却绑着VIP,立刻解绑。
巡检逻辑的核心是从Orchestrator API获取当前集群主库的hostname:
# /usr/local/mysql-ha/vip_guard.sh CLUSTER_NAME="my-cluster" ORCH_API="http://127.0.0.1:3000/api" VIP="192.168.1.100" MY_HOSTNAME=$(hostname) ACTUAL_MASTER=$(curl -s "$ORCH_API/master/$CLUSTER_NAME" | jq -r '.hostname') if [[ "$ACTUAL_MASTER" != "$MY_HOSTNAME" ]]; then # 自己不是主库但持有VIP,立刻解绑 ip addr show eth0 | grep -q "$VIP" if [[ $? -eq 0 ]]; then ip addr del $VIP/24 dev eth0 echo "$(date '+%F %T') [WARN] Removed stray VIP on non-master node." >> /var/log/mysql-ha/vip_guard.log fi fi这套巡检脚本我配置成每30秒执行一次,用crontab跑。它解决的是Hook执行成功但状态被后续操作破坏的兜底问题。你可以把它理解成“VIP管理的地心引力”——不管前面哪一步出了错,最终会回到“只有主库持有VIP”的正确状态。
4. 实操全过程:从Orchestrator安装到Hook生效,一步步来
4.1 环境准备与Orchestrator部署要点
我以一个三节点MySQL主从环境为例:三台机器分别是mysql-node1(当前主库)、mysql-node2(从库)、mysql-node3(从库),系统是CentOS 7,MySQL是5.7.44。Orchestrator单独部署在一台管理机上(也可以是其中一台节点,但不建议生产环境这么干)。
Orchestrator的安装方式有两种:二进制包方式(需要装好依赖)和RPM包方式。我推荐用RPM包方式,它会把systemd服务注册好,省去自己写启动脚本的麻烦。来看看关键配置。
Orchestrator的配置文件是/etc/orchestrator.conf.json。我生产环境里的核心配置如下:
{ "Debug": true, "ListenAddress": ":3000", "MySQLTopologyCredentials": { "User": "orc_topology", "Password": "你的密码", "DefaultAuth": "native_password" }, "MySQLReplicaCredentials": { "User": "orc_replica", "Password": "你的密码" }, "BackendDB": "mysql", "MySQLBackendDB": { "User": "orc_backend", "Password": "你的密码" }, "TopologyConsistencyCheck": true, "RecoveryPeriodBlockSeconds": 60, "RecoveryPeriodBlockMinutes": 5, "DetectClusterAliasQuery": "SELECT SUBSTRING_INDEX(@@hostname, '-', 1)", "RecoverMasterClusterFilters": ["*"], "Hooks": { "PreFailover": [ { "Command": "/usr/local/mysql-ha/pre_failover.sh", "Args": ["{{FailedHostname}}", "{{IsSuccess}}"] } ], "PostFailover": [ { "Command": "/usr/local/mysql-ha/vip_drift.sh", "Args": ["{{FailedHostname}}", "{{SuccessorHostname}}"] } ], "PostUnsuccessfulFailover": [ { "Command": "/usr/local/mysql-ha/notify_dba.sh", "Args": ["{{FailedHostname}}", "{{ClusterName}}"] } ] } }关于账号权限,我多说两句。Orchestrator的拓扑账号(orc_topology)需要的权限包括:REPLICATION CLIENT、PROCESS、SUPER等,官方文档有明确要求。我的实践是额外再加一个BACKUP_ADMIN(如果MySQL版本支持),避免后续做备份管理时又要提权。另外账号的主机匹配要精确,比如orc_topology@'10.0.0.%',不要用'%',否则审计出问题的时候你有10张嘴也说不清。
RecoverMasterClusterFilters设为["*"]表示对所有集群启用自动恢复。生产环境你应当精确到集群名列表,否则新接入的集群还没测试就会被意外纳入自动恢复范围,这个坑我接手一个团队时遇到过——他们接了一个跑报表的实例,结果半夜自动切换把报表链路弄断了。
4.2 验证Hook有没有生效:模拟故障切换的完整流程
配置写完只是第一步,重点是要验证。我强烈建议你先不要直接杀主库,而是用Orchestrator的graceful方式验证。
第一步:检查拓扑发现是否正常。用orchestrator -c topology命令或直接看Web UI,确保三个节点已经被正确发现,复制关系清晰。
第二步:进入Orchestrator节点,手动执行一次受控恢复操作。Orchestrator的命令行工具支持直接调用恢复接口:
orchestrator -c recover -i mysql-node1:3306 --debug这个命令会让Orchestrator执行一次故障恢复流程,但跟真实故障相比,它的好处在于你可以全程看日志,而且出问题随时可以回滚。
第三步:观察Hook脚本日志。如果配置正确,你应该能在/var/log/mysql-ha/vip_drift.log里看到类似“VIP drifted to mysql-node2 successfully”的记录。同时去新主上执行ip addr show eth0确认VIP已经绑上。
第四步:全链路验证。模拟真实应用连接VIP,执行简单的读写操作。这一步特别关键,因为VIP绑上不代网络层已经通了。如果读不写、写不同,去排查交换机ARP表是否刷新了。
第五步:故障注入测试。挑业务低峰期,直接kill -9 mysqld模拟主库crash,观察Orchestrator的自动恢复流程是否在预期时间内完成,VIP是否自动漂移。这个测试做完,才叫真正的“可用”。
我遇到过不少团队,Orchestrator装好了、Hook配置也贴进去了,但从来没真正模拟过故障,结果线上第一次切换就各种翻车。Orchestrator本身是个挺好的工具,但不代表你贴个配置就完事了。高可用系统是需要“反复演练”才能信的。
4.3 与MySQL 5.7常见问题的适配经验
如果你的环境也是MySQL 5.7,有两点需要特别注意。
第一,5.7默认的复制账号权限跟8.0有差异,Orchestrator连接拓扑库时如果用8.0版本的授权语句,会把5.7的账号权限搞坏(比如CREATE USER IF NOT EXISTS语法在新旧版本兼容性上有区别)。所以给Orchestrator建账号时先确认一下MySQL版本,语法里别带8.0新增的特性。
第二,MySQL 5.7的super_read_only默认是OFF,主库切换时如果新主还开着写入权限而旧主没有及时设置super_read_only=ON,就可能出现两边同时写入的尴尬。解决方案是在Hook的PreFailover阶段通过Orchestrator的--set-super-read-only命令把所有节点设为只读,新主提升后再关闭。具体在Hook脚本里可以用mysql -e "SET GLOBAL super_read_only=1"来做,但要在SSH到对应节点后执行。
第三,MySQL 5.7的gtid_mode默认是OFF。Orchestrator做自动切换时更推荐开启GTID,否则定位复制位点会非常痛苦。如果你还在用老式的File-Position复制,建议尽早规划GTID改造。这不是Orchestrator的问题,是整个高可用体系的共性问题,但切换时往往会集中暴露。
5. 实战中的坑:Hook脚本常见问题与排查思路
5.1 踩得最多的五个坑
先说我在把Hook方案推给多个团队后总结出的典型问题,我整理成了一个表格,后面逐一展开。
| 症状 | 大概率原因 | 解决方案 |
|---|---|---|
| Hook脚本没执行 | 事件名拼错,或Orchestrator的JSON格式有误 | 检查配置JSON语法,用orchestrator -c configuration -d验证 |
| 脚本执行了但VIP没漂 | 脚本内部SSH认证失败 | 配置免密SSH,或使用Orchestrator的MySQLTopologyCredentials |
| 切换后应用仍旧连旧IP | ARP缓存未刷新 | 在脚本里加免费ARP通告;检查交换机端口状态 |
| 切换流程被拖慢 | Hook里有阻塞调用 | 给所有外部调用加timeout |
| 双节点同时有VIP | 脑裂防护没做 | 加vip_guard.sh巡检脚本兜底 |
逐条说说我在实际处理中的经验。
第一,事件名拼错。这个错很低级但真的最容易犯。Orchestrator的事件名是大小写敏感的,PostFailover写成了postFailover,配置加载不报错,脚本也永远不执行。排查方法就是看Orchestrator日志,有没有Found hook之类的关键字。
第二,SSH认证问题。Hook脚本要远程操作VIP,必须有免密SSH通道。我记得某个环境里,Orchestrator管理机跟数据库节点之间的SSH用的是root用户,但Orchestrator服务本身跑在另外一个用户下,导致免密不生效。排查这类问题的方法很朴素:先在Orchestrator机器上手动执行一遍Hook脚本,看能否正常完成,再在Hook脚本里加set -x,把执行过程全部打到日志里,一切就真相大白了。
第三,ARP缓存问题。这个我前面已经强调过,这里再补充一点。arping -c 3 -U -I eth0 -s 192.168.1.100这条命令发送的是免费ARP,它会主动告诉同网段的主机和网关“这个IP对应的MAC变了”。但是要注意,如果交换机开启了端口安全或者动态ARP检测,免费ARP可能被丢弃。这种场景就得跟网络团队沟通,或改用send-garp的方式。
第四,阻塞调用问题。很多人在Hook脚本里写mysql -e "FLUSH TABLES WITH READ LOCK"这类命令,一旦数据库忙,命令就一直卡着。所有外部命令都套一层timeout是必须养成的习惯。我甚至会把整个脚本通过timeout 60包裹,双保险。
第五,脑裂防护不是可选项。前面说的vip_guard.sh巡检脚本是值得全套配上。它不是管理VIP的必要条件,但却是保证VIP管理安全的必要防线。
5.2 用Orchestrator日志定位Hook问题
Hook相关日志一般出现在Orchestrator自己的日志里。默认日志位置取决于部署方式,RPM包一般在/var/log/orchestrator.log(也可能是/var/log/orchestrator/orchestrator.log,这个路径我不确定每个发行版都一样,建议检查一下)。
定位问题时,我常用的日志检索姿势:
grep -i "hook" /var/log/orchestrator.log | tail -50 grep -i "failover" /var/log/orchestrator.log | tail -50如果日志里能看到PostFailover command hook triggered字样,说明Orchestrator已经调用了脚本,那就是脚本自身的问题。如果连这个关键字都没有,说明配置根本没被加载,回过去检查JSON格式和事件名。
额外提醒一句:Orchestrator版本升级以后,Hook的行为有变化吗?我自己的经验是,从v3.0到现在的版本,Hook配置格式基本稳定,但某些事件名有过调整。升级前务必读一遍docs/configuration.md里的Hook章节,别想当然。
6. 扩展玩法与最终建议
6.1 基于Hook还能做什么:不只是VIP
VIP管理只是Hook最基础的应用。我看到的更高级玩法包括:切换后自动更新DNS A记录(对于不用VIP而直接用域名访问的架构)、切换后自动把新主注册到Consul或Etcd(服务发现场景)、切换后自动从负载均衡池里摘除旧主并把新主加进去。这些本质都是同一件事:把外部依赖状态和Orchestrator的决策结果对齐。
比如DNS更新场景。有些团队为了保证机房故障时的容灾,会做跨机房的MySQL高可用。VIP方案在跨机房场景下不太灵活,因为二层网络不通。用域名加DNS解析权重就顺很多。Hook脚本在PostFailover里调云厂商的DNS API,把域名记录改成新主的IP,TTL设短一些,应用端就能较快感知。
再比如服务发现场景。如果你用了Consul或Nacos,Hook脚本可以调它们的注册接口,把新主的地址同步过去。这里有个细节:任然需要Hook脚本保证幂等性。因为Orchestrator可能会对同一次故障触发多次恢复(比如第一次失败第二次成功),你的Hook脚本要允许重复执行。
只要符合“Orchestrator决策 → Hook执行 → 外部状态收敛”这个模式,玩法几乎无穷尽。
6.2 我的几点体会
最后说几句实在的。
第一,Hook脚本里一定要写日志。别嫌日志烦,线上出问题时它就是你的救命稻草。我见过太多“切换了但不知道发生了什么”的现场,最后都是用日志一步步还原的。我的习惯是每个Hook脚本都单独一个日志文件,里面如实记录时间、参数和关键操作结果。
第二,Hook脚本要幂等。Orchestrator的Hook可能重试,也可能在同一事件上触发多次。脚本的每一步都要考虑“如果已经执行过会怎样”。比如绑定VIP时用ip addr add ... 2>/dev/null,重复执行也不会报错,这本身就是一种幂等设计。
第三,Hook不是越复杂越好。你的Hook脚本写得越简洁,出问题的概率越低。所有复杂的判定逻辑都尽量往Orchestrator本身的能力上靠,脚本只是执行,不要做太多决策。
在我自己负责的这套体系里,Orchestrator加Hook已经稳定运行了两年多,经历的自动切换不下几十次,其中很多是半夜的真实故障。有了这套机制之后,我最大的感受是:底层的MySQL故障不再需要人肉介入,VIP漂移也不再是“赌运气”的手工活。数据库高可用终于从“脚本集合”进化成了“有决策能力、有执行力、还能自省”的完整系统。
希望这篇文章能让你少踩一些我踩过的坑。如果你正打算上Orchestrator,建议先从Hook做VIP漂移这个切口入手,它见效最快,也最容易验证正确性。跑稳之后再逐步加DNS、加服务发现,把高可用的边界一点点撑大。这个过程没法急,但每一步都值得。