问题背景
公司生产环境有 6 套 MySQL 实例,运维团队编写了一套自动化备份脚本,通过 crontab 每天凌晨 2:00 执行全量备份(mysqldump),备份文件存储在专用的 NFS 挂载点 /backup/mysql/。备份保留 30 天,过期文件自动清理。Zabbix 配置了备份文件新鲜度监控(最新备份超过 26 小时告警)。
故障现象
11 月 19 日周二上午,DBA 按惯例进行每周备份巡检时发现:生产主库 db-prod-01 的最新备份文件日期停留在 11 月 13 日,其后 5 天的备份全部缺失。
|
|
令人不安的是:crontab 日志显示脚本"正常执行",Zabbix 监控也未触发告警。
排查过程
第一步:检查 crontab 执行日志
|
|
crontab 确实每天执行了,退出码为 0(成功)。
第二步:检查备份日志
|
|
关键发现:脚本检测到磁盘空间不足后打印了 WARNING,然后退出,但退出码仍为 0。
第三步:审查脚本退出码逻辑
|
|
脚本在磁盘空间不足时 echo 了警告信息,但用了 exit 0 而非 exit 1。crontab 只看退出码判断成功/失败——所以 crontab 认为脚本执行成功,Zabbix 的监控也未触发(因为监控也是通过检查退出码来判断的)。
第四步:检查为什么 Zabbix 没告警
Zabbix 备份监控项配置:
|
|
触发器逻辑:最新备份文件时间距当前时间超过 26 小时告警。
但问题是——11 月 13 日的备份文件仍然存在于磁盘上。Zabbix 的 vfs.file.time 检查的是文件系统的最后修改时间,而非备份是否真正包含了前一天的完整数据。备份脚本虽然跳过了 14-18 日,但 13 日的文件因为保留策略(30天)仍在磁盘上,Zabbix 每次都读到 13 日的文件并认为"最新备份在 26 小时内"。
等等,这不对——13 日的备份怎么可能在 14-18 日仍然被视为"25 小时内"?
更仔细地看 Zabbix 配置:
|
|
这里的 {DATE} 是 Zabbix 的宏,每天都会替换为当天的日期。比如 11 月 14 日检查的是 db-prod-01_20241114.sql.gz,这个文件不存在时 vfs.file.time 返回错误(而非 0),而触发器只配置了成功值 > 26h 才告警——文件不存在时的错误状态被忽略了!
这就是监控的双重失效:退出码 0 欺骗了 crontab,文件不存在的错误返回值欺骗了 Zabbix 触发器。
解决方案
- 修正备份脚本退出码:
|
|
- 清理磁盘空间:
|
|
-
补充缺失备份:手动执行一次全量备份。
-
修正 Zabbix 监控:
|
|
根因分析
直接原因:备份脚本在磁盘空间不足时以 exit 0 退出,crontab 认为执行成功,Zabbix 也未收到错误信号。
深层原因:
- 脚本的退出码使用不规范(非致命错误也返回 0)
- Zabbix 触发器未覆盖"文件不存在"的异常情况
- 备份磁盘空间没有独立的容量监控(30 天保留策略未考虑数据增长)
- 缺少备份成功后的验证步骤(如校验
gzip -t)
预防措施
- 退出码规范:脚本中任何非正常完成的情况必须返回非 0 退出码
- 备份验证:备份完成后
gzip -t校验完整性 + 检查文件大小是否合理(>50% 前一日大小) - 磁盘容量独立监控:对
/backup/mysql分区单独设置 >80% 告警 - 数据库增长监控:统计每月备份大小趋势,当增长率 >20% 时提前扩容
- 多维度监控:不要只依赖一种监控方式,退出码 + 文件时间 + 文件大小三者交叉验证
- 定期恢复演练:每季度随机抽取一个备份文件进行恢复验证
总结
备份系统最可怕的不是"备份失败",而是"备份失败了但没有人知道"。这次事故暴露了两个核心问题:退出码的滥用和监控的盲区。一个执行了 5 天、每次都"成功"的备份脚本,实际上从未生成过有效的备份文件。如果在第 6 天确实发生了数据丢失需要回滚,DBA 会惊讶地发现最近可用的备份停在 6 天前。备份脚本的退出码不应该是一个可选项——所有非正常路径都必须返回非 0,没有例外。