UPS 断电后,怎么把自动关机脚本写得稳一点
upsc ups@localhost 能读到电池状态,不代表断电时这套链路一定能把机器安全关掉。
UPS 装好只是开始。真正容易出问题的是断电后的几分钟:主机何时关机、Docker 和虚拟机谁先停、来电后是否会留下未清理状态。
这篇只讨论一件事:怎么把“断电后自动关机”写得更稳。
先想清楚:关机不是一个命令
UPS 不是“电没了就执行 shutdown”。它更像一个缓冲区,给系统争取几分钟完成收尾。
常见流程通常是:
- 市电断开,UPS 切到电池供电
- 监控程序收到事件
- 等待一个短暂观察期,避免瞬时掉电误判
- 进入关机流程:先停服务,再停宿主机
- 电量低到阈值时,执行最终关机
“观察期”很重要。电压抖动、插排误碰,都可能触发一次切电。如果每次都立刻关机,可用性会很差。
一个够用的判断条件
普通场景先抓两个信号就够了:
- 是否在电池供电:说明市电已经断了
- 剩余电量或剩余时间:说明还能撑多久
如果监控工具能提供这两个值,脚本就能写得比较实用:
- 前者决定“要不要准备关机”
- 后者决定“现在是不是必须关”
一个保守做法是:
- 断电后先等 60 到 180 秒
- 如果这段时间市电恢复,退出
- 如果仍在电池模式,再开始停服务
- 只在电量低于阈值时执行系统关机
目的不是优雅,而是减少误关机。
脚本最容易忽略的是幂等
UPS 事件可能重复上报,脚本也可能被再次触发,所以幂等很关键。
可以简单理解成:同一个动作重复执行,结果不应该越来越糟。
这些操作最好都按“可重复执行”来写:
- 已停止的容器,再停一次也不报致命错误
- 已卸载的网络共享,再执行一次直接跳过
- 已创建的锁文件,要能判断是不是旧任务残留
- 已发出的关机命令,不要再触发第二轮清理
一个实用做法是在脚本开头加锁,比如用锁文件或 flock,保证同一时间只有一个实例在跑。否则 UPS 连续上报几次事件,脚本可能并发执行,顺序会乱。
顺序比花哨更重要
如果 Homelab 里既有容器也有虚拟机,先列依赖,再写停机顺序。
比较常见的顺序是:
- 先停下载、转码、备份这类高 I/O 服务
- 再停数据库、面板、反向代理
- 然后停虚拟机或容器运行时
- 最后让宿主机关机
原因很直接:
- 高 I/O 服务最容易在断电时留下损坏
- 数据库通常需要更完整的退出过程
- 宿主机必须放最后
如果有 NAS 或远程挂载,也要先梳理依赖关系。不要先把存储断掉,再让还在写数据的服务继续跑。
日志要留,但别写成控制台表演
断电时最怕“看起来执行了,其实没做完”。所以脚本至少要记录三类信息:
- 什么时候检测到断电
- 什么时候开始停服务
- 最后有没有真正执行关机
日志不用复杂,能落到本地文件就够。重点不是格式,而是下次排查时能知道卡在哪一步。
如果条件允许,可以加失败通知,比如发到局域网通知服务、邮件或消息网关。但这不是必须,因为断网或路由器掉电时,外发通知本身就可能不可靠。
一个更稳的最小实现思路
下面这种流程,比单条 shutdown -h now 更接近可用状态。
1. 收到 UPS 事件后先判断
- 是否确实在电池供电
- 是否已经有脚本实例在运行
2. 进入短暂等待
- 睡眠 120 秒
- 再检查一次电源状态
3. 开始清理
- 按顺序停止容器、虚拟机、挂载、服务
- 每一步都允许重复执行
- 每一步都写日志
4. 最终关机
- 满足低电量阈值,或等待超时后仍未恢复市电
- 执行系统关机
这里有个取舍:是“尽快保护数据”,还是“尽量撑到来电”。没有统一答案,要结合电池容量和服务重要性来定。电池小,就早点关;电池足,就多等一会儿。
不要跳过演练
UPS 脚本平时很少有存在感,真停电时才会暴露问题。
演练也别直接拔总闸。更稳妥的做法是:
- 先确认监控程序能正确读到 UPS 状态
- 再用测试事件或短时断开市电做验证
- 观察日志里是否按预期进入等待、清理、关机流程
重点不是“测试一次成功”,而是确认重复触发时也不会乱。尤其是锁、重试、重复停止这几块,建议单独检查。
别把自动关机写成自动事故
UPS 场景里,脚本不需要过度聪明。
比如根据十几个指标动态计算关机时机,理论上很强,实际维护成本也更高;一旦某项数据缺失,行为可能更不稳定。对多数场景来说,清晰的阈值、固定的顺序、可重复执行的步骤,通常已经够用。
如果还要继续完善,优先考虑来电后的恢复策略:哪些服务自动启动,哪些保留人工确认。