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.confNOTIFYCMD触发。我们让电源事件的通知信息写入自定义的日志文件/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 #

DEADTIMEupsmon.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

## 扫描所有可用的通信总线
nut-scanner -C

## 扫描可用USB设备
nut-scanner -U

upsc

## 列出主机上的已配置的UPS名称
upsc -l

## 列出连接的客户端
upsc -c myups

## 查看ups信息
upsc myups 
upsc myups battery.charge

upscmd

## 显示该UPS支持的即时命令列表
upscmd -l myups

## 向UPS发送特定命令
upscmd myups beeper.toggle

upsrw

## 查看哪些变量可写
upsrw -l myups

## 更改UPS内部变量的值
upsrw -s ups.delay.shutdown=30 myups