上个月小区检修,一周里停了两次电。笔记本服务器有电池,没什么感觉;NAS 那台旧台式机可就惨了,两块机械盘直接断电。事后用上次配好的 smartctl 看了一眼,Power-Off_Retract_Count(非正常断电时磁头紧急归位的次数)又涨了。盘倒是没坏,但心里发毛,于是下单了一台 UPS。

硬件和拓扑

买的是一台 650VA 的在线互动式 UPS,带 USB 通信口,接上电脑会被识别成一个 HID 设备,用 NUT(Network UPS Tools)的 usbhid-ups 驱动就能读。挂在它上面的有 NAS、笔记本、路由器和交换机,总负载六十瓦左右,标称能撑十几分钟。

NUT 的结构是“一台机器连着 UPS,其他机器通过网络听它的”:

  • NAS(192.168.10.20)用 USB 线连着 UPS,运行驱动和 upsd 服务,自己是 primary;
  • 笔记本上的 Proxmox(192.168.10.5)作为 secondary,通过网络监听 UPS 状态。

为什么让 NAS 当主节点?因为它最怕断电,而且主节点会等其他机器都开始关机后才最后一个关,并在关机的最后通知 UPS 切断输出。

主节点配置(NAS)

apt install nut
lsusb            # 先确认能看到 UPS
nut-scanner -U   # 自动探测 USB 上的 UPS,输出可以参考着写 ups.conf

Debian 12 里的 NUT 是 2.8 版本,配置都在 /etc/nut/ 下面,一共要改五个文件。先是驱动和用户:

# /etc/nut/ups.conf
[homeups]
    driver = usbhid-ups
    port = auto
    desc = "UPS next to the NAS"
    # 不信 UPS 自己报的低电量,改由下面两个阈值来判断
    ignorelb
    override.battery.charge.low = 40
    override.battery.runtime.low = 300

# /etc/nut/upsd.users
[upsmon_local]
    password = CHANGE_ME_LOCAL
    upsmon primary

[upsmon_remote]
    password = CHANGE_ME_REMOTE
    upsmon secondary

然后是运行模式、监听地址和 upsmon:

# /etc/nut/nut.conf
MODE=netserver

# /etc/nut/upsd.conf
LISTEN 127.0.0.1 3493
LISTEN 192.168.10.20 3493

# /etc/nut/upsmon.conf(只列出改动的几行)
MONITOR homeups@localhost 1 upsmon_local CHANGE_ME_LOCAL primary
SHUTDOWNCMD "/sbin/shutdown -h +0"

NUT 2.8 开始用 primary/secondary 取代了以前的 master/slave,旧写法暂时还认,但网上老教程里的配置最好顺手改掉。

改完之后我是直接重启了一次 NAS,比挨个重启驱动和服务省心。起来以后执行 upsc homeups@localhost,能看到 battery.charge、battery.runtime、ups.status 等一长串数据,就说明驱动正常了。ups.status 里 OL 表示市电供电,OB 表示在用电池,LB 表示低电量。

从节点配置(Proxmox)

PVE 宿主机上只需要客户端:

apt install nut-client
sed -i 's/^MODE=.*/MODE=netclient/' /etc/nut/nut.conf
echo 'MONITOR homeups@192.168.10.20 1 upsmon_remote CHANGE_ME_REMOTE secondary' >> /etc/nut/upsmon.conf
systemctl restart nut-monitor

upsc homeups@192.168.10.20 ups.status    # 能读到 OL 就说明连上了

PVE 关机时会先挨个关掉虚拟机和容器,所以不需要在每台虚拟机里都装 NUT。

关机的逻辑

整个流程是这样的:停电后 UPS 状态变成 OB,upsmon 只记录、发通知,并不会马上关机;等电量低于 40% 或者预计剩余时间少于 300 秒,驱动把状态标成 LB,主节点随即发出强制关机(FSD);secondary 收到后开始关机,主节点等它们断开连接(默认最多等 15 秒,由 HOSTSYNC 控制)后自己关机,最后让 UPS 延迟一小段时间切断输出。

如果想要“停电满 5 分钟就关”这种按时间的策略,可以用 NUT 自带的 upssched 做定时器。我觉得按电量判断更直接,就没上。

踩过的坑

  1. UPS 自带的低电量阈值太低。 我这台默认要到电量很低、只剩两三分钟时才报 LB,而 PVE 关掉几台虚拟机就要一两分钟,旧电池的剩余时间估算又不准。所以我用了 ignorelb 加 override 的写法,宁可早点关。
  2. 驱动认不到设备。 装完包第一次启动时报权限错误,原因是 NUT 的 udev 规则在 UPS 插上时还没装好。把 USB 线拔了重插一下就好了。
  3. 测试别乱用 fsd。 upsmon -c fsd 会真的触发整套关机流程,所有机器都会关掉。我的测试方法是挑晚上没人用的时候,直接拔掉 UPS 的插头,盯着 journalctl -u nut-monitor -f 看,先能看到“on battery”的日志,等电量掉到阈值,再看各台机器是不是按顺序关机。
  4. 主节点关得太快? 笔记本关机时要等虚拟机挨个关完,有时候 UPS 切断输出时它还没关完。好在它有自己的电池,断不断电都无所谓,这也算是笔记本当服务器的一个意外好处。
  5. 来电后自动开机。 NAS 是台式机,BIOS 里把“断电恢复后的行为”设成开机就行,UPS 切断输出再恢复供电时它会自己起来。笔记本就麻烦了:它有电池,从头到尾没真正断过电,关机之后不会自己开。好在它的有线网卡支持 Wake-on-LAN,我在 NAS 上加了一个开机后执行的小任务,等几分钟后发一个唤醒包给笔记本。

小结

NUT 的配置文件多,第一次看有点绕,但理清楚“谁连着 UPS、谁听谁的”之后其实很直观。配好之后又赶上了一次停电,NAS 和笔记本都按顺序关了机,来电后十分钟内全部恢复,算是没白折腾。