NUT 架构分为三个部分:
- 驱动 (upsdrvctl / nut-driver@xx.service):负责跟 UPS 硬件通信,获取 UPS 状态等信息。
- 服务端 (upsd / nut-server.service):作为网络服务,监听 TCP 端口,把 UPS 状态信息提供给客户端。
- 客户端 (upsmon / nut-monitor.service):向服务端获取 UPS 状态信息,执行通知命令,执行关机流程。
这种模块化设计非常灵活,可以实现多个 UPS 和多个客户端的配合。本文以最简单的独立模式为例,在 pve 或 debian 一台主机上部署 NUT,实现市电中断后优雅地让系统关机。
安装 NUT #
debian 有个 nut 元包,这会自动安装 nut-server 和 nut-client 包
apt update && apt install nut
模式配置 #
/etc/nut/nut.conf
## 配置运行的模式
MODE=standalone
nut-driver 配置 #
/etc/nut/ups.conf
通过nut-scanner -U命令扫描USB接口查找UPS打印配置信息,然后编辑ups.conf。更多USB接口相关配置选项可以查看 usbhid-ups(8)
## 驱动程序首次启动的最大失败次数
maxretry = 3
## 驱动程序轮询 UPS 的频率。
pollinterval = 5
## myups为自定义的ups标识名称,可以自行定义
[myups]
driver = usbhid-ups
port = auto
vendorid = "06DA"
productid = "FFFF"
编辑ups.conf文件后,nut-driver-enumerator.path监控到配置文件发生变化,应该会自动调用nut-driver-enumerator.service创建相应的nut-driver@myups.service文件。
nut-server 配置 #
/etc/nut/upsd.conf
## 暴露网络接口供nut-client连接
LISTEN 0.0.0.0 3493
/etc/nut/upsd.users
## 配置nut-client连接所使用的用户、密码、权限
[upsadmin]
password = 123456
actions = set
actions = fsd
instcmds = all
upsmon primary
nut-client 配置 #
/etc/nut/upsmon.conf
## 连接 nut-server
MONITOR myups@127.0.0.1:3493 1 upsadmin 123456 primary
## 轮询 nut-server 的频率
POLLFREQ 12
## 通信失败或数据未更新多少秒后,ups被判断为故障
## 如果此时 UPS 最后一次已知状态是ONBATT,则假定为LOWBATT
DEADTIME 60
## 运行的用户
RUN_AS_USER root
## 关机命令
SHUTDOWNCMD /usr/sbin/poweroff
## 在关机流程最后一步通知UPS关机的标志文件
## 系统关机后,如果你想让UPS继续为其他设备供电,可以注释掉
POWERDOWNFLAG "/etc/killpower"
## 电源事件发生变化时所执行的脚本,$1为NOTIFYMSG,以及环境变量NOTIFYTYPE
## 需要NOTIFYFLAG带有EXEC标志
NOTIFYCMD /etc/nut/notify.sh
## 通知的消息
NOTIFYMSG ONLINE "UPS %s on line power"
NOTIFYMSG ONBATT "UPS %s on battery"
NOTIFYMSG LOWBATT "UPS %s battery is low"
NOTIFYMSG FSD "UPS %s: forced shutdown in progress"
NOTIFYMSG COMMOK "Communications with UPS %s established"
NOTIFYMSG COMMBAD "Communications with UPS %s lost"
NOTIFYMSG SHUTDOWN "Auto logout and shutdown proceeding"
NOTIFYMSG REPLBATT "UPS %s battery needs to be replaced"
NOTIFYMSG NOCOMM "UPS %s is unavailable"
NOTIFYMSG NOPARENT "upsmon parent process died - shutdown impossible"
NOTIFYMSG CAL "UPS %s: calibration in progress"
NOTIFYMSG OFF "UPS %s: administratively OFF or asleep"
NOTIFYMSG NOTOFF "UPS %s: no longer administratively OFF or asleep"
NOTIFYMSG BYPASS "UPS %s: on bypass (powered, not protecting)"
NOTIFYMSG NOTBYPASS "UPS %s: no longer on bypass"
## 通知标志
NOTIFYFLAG ONLINE SYSLOG+EXEC
NOTIFYFLAG ONBATT SYSLOG+EXEC
NOTIFYFLAG LOWBATT SYSLOG+EXEC
NOTIFYFLAG FSD SYSLOG+EXEC
NOTIFYFLAG COMMOK SYSLOG+EXEC
NOTIFYFLAG COMMBAD SYSLOG+EXEC
NOTIFYFLAG SHUTDOWN SYSLOG+EXEC
NOTIFYFLAG REPLBATT SYSLOG+EXEC
NOTIFYFLAG NOCOMM SYSLOG+EXEC
NOTIFYFLAG NOPARENT SYSLOG+EXEC
NOTIFYFLAG CAL SYSLOG+EXEC
NOTIFYFLAG OFF SYSLOG+EXEC
NOTIFYFLAG NOTOFF SYSLOG+EXEC
NOTIFYFLAG BYPASS SYSLOG+EXEC
NOTIFYFLAG NOTBYPASS SYSLOG+EXEC
/etc/nut/notify.sh
由upsmon.conf的NOTIFYCMD触发。我们让电源事件的通知信息写入自定义的日志文件/etc/nut/events.log,方便查看。
#!/bin/bash
NOW=$(date "+%Y-%m-%d %H:%M:%S %z")
FILE=/etc/nut/events.log
echo "$NOW: [$NOTIFYTYPE] $1" >> "$FILE"
/etc/nut/upssched.conf
对电源事件执行一些计时器操作,由于我们已经将NOTIFYCMD指向我们的自定义脚本,这里不做更多介绍。
使配置生效 #
## 赋予通知脚本执行权限
chmod +x /etc/nut/notify.sh
## 重启驱动,注意修改myups为你在ups.conf中定义的名称
systemctl restart nut-driver-enumerator.service
systemctl restart nut-driver@myups.service
## 重启服务端
systemctl restart nut-server.service
## 重启客户端,nut-client.service是nut-monitor.service的符号链接
systemctl restart nut-monitor.service
执行upsc myups命令查看 UPS 状态
root@pve:~# upsc myups
Init SSL without certificate database
battery.charge: 100
battery.charge.low: 30
battery.runtime: 2050
battery.type: PbAc
battery.voltage: 27.50
device.mfr: PPC
device.model: Offline UPS
device.serial: 000000000
device.type: ups
driver.debug: 0
driver.flag.allow_killpower: 0
driver.flag.ignorelb: enabled
driver.name: usbhid-ups
driver.parameter.override.battery.charge.low: 30
driver.parameter.pollfreq: 30
driver.parameter.pollinterval: 5
driver.parameter.port: auto
driver.parameter.productid: FFFF
driver.parameter.synchronous: auto
driver.parameter.vendorid: 06DA
driver.state: quiet
driver.version: 2.8.1
driver.version.data: Phoenixtec/Liebert HID 0.41
driver.version.internal: 0.52
driver.version.usb: libusb-1.0.28 (API: 0x100010a)
experimental.battery.capacity: 100
experimental.battery.capacity.nominal: 100
input.voltage: 232.0
output.frequency: 50.00
output.frequency.nominal: 50
output.voltage: 232.0
output.voltage.nominal: 220
ups.beeper.status: enabled
ups.delay.shutdown: 20
ups.delay.start: 30
ups.load: 12
ups.mfr: PPC
ups.model: Offline UPS
ups.power.nominal: 7
ups.productid: ffff
ups.serial: 000000000
ups.status: OL DISCHRG
ups.test.result: No test initiated
ups.vendorid: 06da
root@pve:~#
LOWBATT #
LOWBATT(低电量)的判定至关重要,NUT 的工作流程:
- nut-driver 轮询 UPS 硬件获取电量、状态等数据,并通过 socket 喂给 nut-server
- nut-client 通过 tcp 轮询 nut-server 获取数据。
- nut-client 识别电源事件变化,执行
NOTIFYCMD,写入日志。 - nut-client 识别 UPS 状态为
ONBATT+LOWBATT,并且MINSUPPLIES为1(默认)时,将执行关机流程。
对于中高端的UPS,会主动报告LOWBATT状态以及battery.charge.low参数。通常当battery.charge小于battery.charge.low时,报告LOWBATT状态,如果此时是电池供电ONBATT,NUT 将触发关机流程。
如果执行upsc xx命令查看 UPS 的信息中没有battery.charge.low,我们不知道 UPS 是否会报告LOWBATT,建议手动在 NUT 中进行定义。编辑ups.conf:
[myups]
driver = usbhid-ups
port = auto
vendorid = "06DA"
productid = "FFFF"
ignorelb
override.battery.charge.low = 30
DEADTIME #
DEADTIME是upsmon.conf中的一个重要参数,当 nut-client 连续轮询 nut-server 失败,或 UPS 数据长期未更新的情况下,超过一定时间,NUT 就认为 UPS 处于故障状态。如果此时 UPS 最后一次已知状态是ONBATT,则假定为LOWBATT,从而触发关机流程。
如果相关轮询频率参数配置不当,可能导致系统意外关机。一个经验是:
upsmon.conf.DEADTIME > upsmon.conf.POLLFREQ x 3
upsmon.conf.POLLFREQ > ups.conf.pollinterval x 2
实用程序 #
## 扫描所有可用的通信总线
nut-scanner -C
## 扫描可用USB设备
nut-scanner -U
## 列出主机上的已配置的UPS名称
upsc -l
## 列出连接的客户端
upsc -c myups
## 查看ups信息
upsc myups
upsc myups battery.charge
## 显示该UPS支持的即时命令列表
upscmd -l myups
## 向UPS发送特定命令
upscmd myups beeper.toggle
## 查看哪些变量可写
upsrw -l myups
## 更改UPS内部变量的值
upsrw -s ups.delay.shutdown=30 myups