跳过正文
  1. 博客文章/

华为 UPS2000 接入 NUT Server:编译 huawei-ups2000 驱动并完成联动

··2156 字·11 分钟·
DevOps SRE NUT UPS Huawei UPS2000 Modbus Linux DevOps SRE
Zayn
作者
Zayn
专注 Kubernetes、CI/CD、可观测性等云原生技术栈,记录生产环境中的实战经验与踩坑复盘。
目录
生产变更复盘 - 这篇文章属于一个选集。
4: 本文
Huawei UPS2000G 接入 NUT Server,关键在两件事:把串口和 Modbus 通信验通,把 huawei-ups2000 驱动接进 upsdupsmonupssched 的事件链路。
文中的主机名、IP、NUT 密码、UPS 序列号和客户端地址统一写成示例值。部署时按自己的机房网络、权限模型和关机策略替换。

0. 结论和使用边界
#

文中的 UT / UNT serverNUT: Network UPS Tools。这台机器承担 NUT Server 角色:通过 huawei-ups2000 驱动读取 UPS2000G,再把状态暴露给本机和远端 NUT 客户端。

当前部署状态:

状态
操作系统Ubuntu 22.04.4 LTS,Linux 5.15
发行版 NUT 包nut / nut-server / nut-client / nut-cgi2.7.4-14ubuntu2
实际 UPS 驱动/lib/nut/huawei-ups2000,从 NUT 源码编译安装
源码版本NUT 2.8.2.1,本地源码目录保留在 /root/nut
驱动版本huawei-ups2000 internal version 0.06
UPS 型号Huawei UPS2000G,额定功率 3000 VA
连接方式UPS USB 口识别为 /dev/ttyUSB0,USB UART 为 04e2:1410 Exar XR21V1410
NUT 模式MODE=netserverupsd 监听 0.0.0.0:3493
Web 状态入口Apache 暴露 nut-cgi,状态页为 /cgi-bin/nut/upsstats.cgi
systemd 服务nut-drivernut-servernut-monitor 均为 active
当前 UPS 状态OL CHRG,在线供电并充电,电量 100%,负载约 38%

主要覆盖:

  • 如何从一台空 Ubuntu 机器准备 NUT Server 的编译和运行环境。
  • 如何确认 Huawei UPS2000G 的 USB/串口链路可用。
  • 如何从 NUT 源码编译 huawei-ups2000 驱动并放入 Ubuntu 的 NUT 服务目录。
  • 如何配置 ups.confupsdupsmonupssched,让 UPS 与 NUT Server 关联起来。
  • 如何用 upscupscmdupsrwnut-cgi 和 systemd 日志验证接入结果。

操作边界:

  • 生产 NUT 密码、UPS 序列号、客户端 IP 和主机地址只写示例值。
  • 不在生产环境演示 load.offdriver.killpowershutdown.return 等会影响供电的命令。
  • 不把当前现场策略包装成唯一方案。尤其是 upssched-cmd 里的主节点不关机逻辑,需要结合业务保护目标再确认。
  • 不建议手工改 NUT 源码。当前只处理“编译已有驱动并接入服务”,不维护 fork。

1. 架构关系:UPS 如何接入 NUT Server
#

NUT 接入要先看链路。真正需要对齐的是驱动、服务端、监控端和调度脚本,而不是某一个单独命令。

flowchart LR
    ups["UPS
后备电源
停电后通知服务器执行关机"] subgraph vm_zone["虚拟化节点"] nutvm["NUT Server VM
Ubuntu
示例地址:192.0.2.10/24"] driver["huawei-ups2000 driver
/dev/ttyUSB0"] upsd["upsd
TCP 3493"] monitor["local upsmon
master"] cgi["nut-cgi
/cgi-bin/nut/upsstats.cgi"] end subgraph phy_zone["受 UPS 供电的物理机"] nas["nas-node
400W"] node01["compute-node-01
400W"] node02["compute-node-02
400W"] node03["compute-node-03
500W"] node04["compute-node-04
450W"] node05["compute-node-05
500W"] node06["compute-node-06
300W"] backup["vm-backup-node
150W"] end ups -->|USB 直通到 VM| nutvm nutvm --> driver --> upsd upsd --> monitor upsd --> cgi upsd -. Network .-> nas upsd -. Network .-> node01 upsd -. Network .-> node02 upsd -. Network .-> node03 upsd -. Network .-> node04 upsd -. Network .-> node05 upsd -. Network .-> node06 upsd -. Network .-> backup ups -. 提供电源 .-> phy_zone

现场的三层关联是:

层级配置入口作用
UPS 到驱动/etc/nut/ups.conf 中的 [huawei]指定 driver = huawei-ups2000port = /dev/ttyUSB0
驱动到 NUT Servernut-driver.service + upsdupsdrvctl 启动 /lib/nut/huawei-ups2000 -a huaweiupsd 对外发布状态
事件到动作upsmon.conf + upssched.conf断电、恢复供电、低电量等事件进入调度脚本

1.1 为什么需要单独编译驱动
#

Ubuntu 22.04 的 NUT 包提供了稳定的 systemd 服务和 upsd / upsmon 运行框架,但现场需要的 huawei-ups2000 驱动来自 NUT 较新的源码树。最终做法是:

  1. 保留发行版 NUT 包作为服务框架。
  2. 从 NUT 源码启用 libmodbus 编译 huawei-ups2000
  3. 只把编译出的驱动二进制安装到 /lib/nut/huawei-ups2000
  4. 让发行版自带的 nut-driver.service 继续管理驱动进程。

这样改动范围小:不用替换整套 NUT 服务,也不用改 systemd unit。代价也明确:驱动二进制来自源码编译,升级 NUT 包或重装机器时,要重新确认 /lib/nut/huawei-ups2000 是否还在。

1.2 UPS2000G 的连接约束
#

现场 UPS 通过 /dev/ttyUSB0 接入,设备信息显示为:

ID_VENDOR_ID=04e2
ID_MODEL_ID=1410
ID_MODEL=1410

这对应 Huawei UPS2000 常见的 Exar XR21V1410 USB-to-serial 芯片。NUT 源码里的 huawei-ups2000 文档说明了一个关键点:UPS2000 的 USB 和 RS-232 虽然都能作为串口通道使用,但同一时间只应该使用一个端口;如果切换线缆或端口类型,UPS 端口可能需要断电冷启动后才恢复响应。

部署时按顺序检查:

dmesg | grep -Ei 'ttyUSB|xr_serial|ch341|cdc_acm'
ls -l /dev/ttyUSB0
udevadm info -q property -n /dev/ttyUSB0 | grep -E 'ID_VENDOR_ID|ID_MODEL_ID|ID_MODEL'

通过标准:

  • 能看到 /dev/ttyUSB0 或明确的串口设备。
  • nut 运行用户对串口设备有读写权限。
  • 如果设备被识别成不兼容的 cdc_acm,不要继续调 NUT 配置,应先处理内核驱动或改用 RS-232。

2. 从零部署:准备系统和依赖
#

从空机器开始,最小路径如下。已部署机器上的 history、包列表和源码目录能还原出完整步骤。

2.1 安装基础包
#

Ubuntu 22.04 上先安装发行版 NUT 和编译依赖:

apt update
apt install -y \
  git \
  build-essential \
  pkgconf \
  autoconf \
  automake \
  libtool \
  libmodbus-dev \
  nut \
  nut-server \
  nut-client \
  nut-cgi

验证依赖:

dpkg -l | awk '/^(ii)/ && ($2 ~ /^(nut|nut-client|nut-server|libmodbus|build-essential|pkgconf|autoconf|automake|libtool)/) {print $2, $3}'
pkgconf --libs libmodbus

现场能看到的关键包版本:

版本
nut2.7.4-14ubuntu2
nut-server2.7.4-14ubuntu2
nut-client2.7.4-14ubuntu2
libmodbus-dev3.1.6-2
build-essential12.9ubuntu3

2.2 准备串口权限
#

现场 /dev/ttyUSB0 权限是:

crw-rw---- 1 root dialout ... /dev/ttyUSB0

nut 用户加入了 dialout 组:

uid=110(nut) gid=115(nut) groups=115(nut),20(dialout)

配置命令如下:

usermod -aG dialout nut
chown root:dialout /dev/ttyUSB0
chmod 660 /dev/ttyUSB0

chownchmod 只对当前设备节点生效,重启或重新插拔后可能丢失。生产环境建议增加 udev 规则,把设备稳定映射为 /dev/ups2000

cat >/etc/udev/rules.d/60-ups2000.rules <<'EOF'
SUBSYSTEM=="tty", ATTRS{idVendor}=="04e2", ATTRS{idProduct}=="1410", GROUP="dialout", MODE="0660", SYMLINK+="ups2000"
EOF

udevadm control --reload-rules
udevadm trigger
ls -l /dev/ups2000

如果使用 udev symlink,后续 ups.conf 里的 port 建议改成 /dev/ups2000。当前部署仍使用 /dev/ttyUSB0,后面会把现状和推荐做法分别说明。

3. 先用原型程序确认 UPS 通信
#

现场 /root/ups2000_test 保留了一个早期 UPS2000 测试程序,里面有 MakefileREADMEups2000.c。它不是最终服务的一部分,作用是把硬件链路和 Modbus 通信先单独验掉。

3.1 编译原型程序
#

原型程序依赖 libmodbus

cd /root/ups2000_test
make

Makefile 的核心逻辑是编译 serial.cups2000.cstub.cmain.c,并链接 libmodbus

MODBUS_CFLAGS=`pkgconf --cflags libmodbus`
MODBUS_LIBS=`pkgconf --libs libmodbus`

all: serial.c serial.h ups2000.c stub.c stub.h main.c
	cc -c serial.c -o serial.o $(CFLAGS)
	cc -c ups2000.c -o ups2000.o $(CFLAGS) $(MODBUS_CFLAGS)
	cc -c stub.c -o stub.o $(CFLAGS)
	cc -c main.c -o main.o $(CFLAGS)
	cc -o ups2000 main.o stub.o serial.o ups2000.o $(MODBUS_LIBS)

3.2 运行通信测试
#

测试命令形如:

./ups2000 /dev/ttyUSB0

通过标准:

  • 程序能读到 UPS 型号、固件和实时状态。
  • 失败时能明确区分权限问题、串口不可读、Modbus 超时或 UPS 端口无响应。
  • 如果切换过 USB / RS-232 连接方式,先按 UPS 侧流程冷启动,再重新测试。

先用单一程序确认串口通信,再接入 NUT。排障时可以避免把“硬件不通”和“NUT 配置错误”混在一起。

4. 编译并安装 huawei-ups2000 驱动
#

NUT 源码目录保留在 /root/nut。现场源码的 Git HEAD 是 3e31b39,版本宏为 2.8.2.1,远端为 https://github.com/networkupstools/nut.git

4.1 拉取源码
#

git clone https://github.com/networkupstools/nut.git --depth=1 /root/nut
cd /root/nut

4.2 生成 configure 脚本
#

./autogen.sh

如果缺少 automakeautoconflibtoolautogen.sh 会失败。不要跳过报错继续 make,先补齐依赖。

4.3 按 Ubuntu NUT 路径配置编译参数
#

现场 config.log 记录的配置命令如下:

./configure \
  --with-modbus \
  --with-user=nut \
  --with-group=nut \
  --datadir=/usr/share/nut \
  --sysconfdir=/etc/nut \
  --includedir=/usr/include \
  --with-statepath=/run/nut \
  --with-pidpath=/run/nut \
  --with-altpidpath=/run/nut \
  --with-drvpath=/lib/nut \
  --with-udev-dir=/lib/udev \
  --with-systemdsystemunitdir=/lib/systemd/system \
  --with-systemdshutdowndir=/lib/systemd/system-shutdown

关键不在参数数量,而在两点:

  • --with-modbushuawei-ups2000 进入 Modbus 驱动列表,并链接 libmodbus
  • --with-drvpath=/lib/nut 对齐 Ubuntu 发行版 NUT 的驱动目录,避免 systemd 仍去旧路径找驱动。

4.4 编译并安装驱动
#

可以只编译目标驱动:

cd /root/nut
make -j"$(nproc)" huawei-ups2000
install -m 0755 drivers/huawei-ups2000 /lib/nut/huawei-ups2000

验证二进制:

/lib/nut/huawei-ups2000 -V
ldd /lib/nut/huawei-ups2000 | grep modbus

现场验证结果:

Network UPS Tools - NUT Huawei UPS2000 (1kVA-3kVA) RS-232 Modbus driver 0.06 (2.8.2.1)
libmodbus.so.5 => /lib/x86_64-linux-gnu/libmodbus.so.5

huawei-ups2000 -h 会提示这是 experimental driver。生产使用时不需要回避这个事实,应该把它写进风险说明:驱动可用,但升级 NUT、切换内核或更换 UPS 型号时都要重新验证。

5. 配置 NUT Server
#

NUT Server 的配置目标是:upsdrvctl 能启动 huawei-ups2000 驱动,upsd 能发布 huawei@localhostupsmon 能监控这个 UPS 并把事件交给 upssched

5.1 /etc/nut/nut.conf
#

现场使用 netserver 模式:

MODE=netserver

这个模式适合一台机器连接 UPS,多台机器通过网络读取 UPS 状态的场景。

5.2 /etc/nut/ups.conf
#

现场配置:

maxretry = 3

[huawei]
    driver = huawei-ups2000
    port = /dev/ttyUSB0
    desc = "Huawei UPS2000"
    user = nut

如果采用 udev symlink,推荐改成:

[huawei]
    driver = huawei-ups2000
    port = /dev/ups2000
    desc = "Huawei UPS2000"
    user = nut

验证命令:

upsdrvctl -t start
systemctl restart nut-driver
systemctl status nut-driver --no-pager

验收时要能看到 /lib/nut/huawei-ups2000 -a huawei 进程,日志里包含 Startup successful

5.3 /etc/nut/upsd.conf
#

现场配置为:

LISTEN 0.0.0.0 3493

这让远端客户端可以访问 NUT Server。生产环境需要配合防火墙或安全组,只允许可信网段访问 3493/tcp。如果只给本机使用,可以改为监听 127.0.0.1

验证:

ss -lntp | grep ':3493'

现场结果是 upsd 监听在 0.0.0.0:3493

5.4 /etc/nut/upsd.users
#

示例配置如下,真实密码不要写进文档或仓库:

[monuser]
    password = "<UPS_MONITOR_PASSWORD>"
    upsmon = master

[remoteuser]
    password = "<UPS_REMOTE_MONITOR_PASSWORD>"
    upsmon = secondary

[admin]
    password = "<UPS_ADMIN_PASSWORD>"
    upsmon = master

NUT 2.8 的文档把监控角色称为 primary / secondary。现场老配置里的 master / slave 仍能兼容,但新增客户端建议按新名称写。NUT Server 本机使用 monuser 作为 primary 监控端;远端物理机使用单独的 remoteuser,不要把 primary 凭据分发到每台客户端。

文件权限建议:

chown root:nut /etc/nut/upsd.users
chmod 640 /etc/nut/upsd.users

NUT 的用户密码通常以明文形式保存在配置文件里,因此重点不是“把它写进 Git”,而是限制文件权限、避免日志输出、按需轮换。

5.5 /etc/nut/upsmon.conf
#

现场有效配置可以整理为:

RUN_AS_USER root
MONITOR huawei@localhost 1 monuser <UPS_MONITOR_PASSWORD> master

MINSUPPLIES 1
SHUTDOWNCMD "/usr/sbin/poweroff"
NOTIFYCMD /usr/sbin/upssched

POLLFREQ 5
POLLFREQALERT 5
HOSTSYNC 30
DEADTIME 15
POWERDOWNFLAG /etc/killpower

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 NOPARENT SYSLOG+EXEC

RBWARNTIME 43200
NOCOMMWARNTIME 300
FINALDELAY 5

这个配置要盯住三点:

  • MONITOR huawei@localhost ... master 把本机设为 UPS 的 master 监控端。
  • NOTIFYCMD /usr/sbin/upssched 让状态变化进入调度器。
  • RUN_AS_USER root 是现场现状,目的是允许执行关机命令;如果改成非 root,需要重新设计关机权限。

5.6 /etc/nut/upssched.conf
#

现场事件策略:

CMDSCRIPT /etc/nut/upssched-cmd
PIPEFN /var/run/nut/upssched.pipe
LOCKFN /var/run/nut/upssched.lock

AT ONBATT * START-TIMER power-off 60
AT ONLINE * CANCEL-TIMER power-off
AT ONLINE * EXECUTE power-on

事件含义:

  • UPS 转电池后,启动一个 60 秒的 power-off 计时器。
  • 如果 60 秒内恢复市电,取消这个计时器。
  • 恢复市电时执行 power-on 事件,记录恢复日志。

5.7 /etc/nut/upssched-cmd
#

现场脚本逻辑如下,已去掉无关细节:

#!/bin/bash

is_master() {
    grep -q "master" /etc/nut/upsmon.conf
    return $?
}

case "$1" in
  power-off)
    if is_master; then
      logger "NUT: Master UPS monitor - Not shutting down."
    else
      logger "NUT: Slave system - Initiating shutdown due to prolonged power outage."
      /usr/sbin/poweroff
    fi
    ;;
  power-on)
    logger "NUT: Power has been restored."
    ;;
  *)
    logger "NUT: Unknown command $1"
    ;;
esac

这段逻辑需要单独 review:当前机器作为 master 时不会自动关机,只记录日志;只有非 master 角色会执行 poweroff。这可能是有意为之,也可能是早期调试留下来的保护逻辑。发布到生产前,需要明确 UPS 保护目标:

  • 如果 NUT Server 本身也需要受保护,应设计 master 的关机策略。
  • 如果 NUT Server 必须保持在线,负责通知下游主机,则要确认它的供电容量和外部告警链路。
  • 如果下游机器依赖远端 upsmon 自行关机,要逐台验证客户端配置,而不是只看 Server。

脚本权限:

chmod 755 /etc/nut/upssched-cmd

5.8 日志分流
#

rsyslog 额外把 NUT 日志写到独立文件:

if $programname == 'upsd' then /var/log/nut/upsd.log
if $programname == 'upsmon' then /var/log/nut/upsmon.log

对应目录权限建议:

mkdir -p /var/log/nut
chown syslog:adm /var/log/nut
chmod 750 /var/log/nut
systemctl restart rsyslog

5.9 远端物理机接入
#

UPS 不只保护 NUT Server 本机。其他物理机可以不直连 UPS,只运行 upsmon,通过网络订阅 NUT Server 的 huawei UPS 状态。这样断电事件由 NUT Server 统一发布,各物理机按自己的策略关机。

远端物理机只需要客户端组件:

apt install -y nut-client

/etc/nut/nut.conf 使用 netclient 模式:

MODE=netclient

/etc/nut/upsmon.conf 的核心配置如下。示例里的 nut.example.internal 替换为 NUT Server 地址,用户名和密码保持和 Server 端 /etc/nut/upsd.users 里的 [remoteuser] 一致。最后一列 secondary 表示这台机器跟随 NUT Server,不负责发起 FSD:

RUN_AS_USER root
MONITOR huawei@nut.example.internal 1 remoteuser <UPS_REMOTE_MONITOR_PASSWORD> secondary

MINSUPPLIES 1
SHUTDOWNCMD "/usr/sbin/poweroff"
NOTIFYCMD /usr/sbin/upssched

POLLFREQ 5
POLLFREQALERT 5
HOSTSYNC 30
DEADTIME 15
POWERDOWNFLAG /etc/killpower

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 NOPARENT SYSLOG+EXEC

RBWARNTIME 43200
NOCOMMWARNTIME 300
FINALDELAY 5

如果远端物理机也使用 60 秒延迟关机策略,/etc/nut/upssched.conf 可以和 Server 保持一致:

CMDSCRIPT /etc/nut/upssched-cmd
PIPEFN /var/run/nut/upssched.pipe
LOCKFN /var/run/nut/upssched.lock

AT ONBATT * START-TIMER power-off 60
AT ONLINE * CANCEL-TIMER power-off
AT ONLINE * EXECUTE power-on

客户端侧的 upssched-cmd 不要复用 Server 上“master 不关机”的判断。远端物理机是 secondary,计时器到期后应按维护策略执行本机关机:

#!/bin/bash

case "$1" in
  power-off)
    logger "NUT: Secondary system - Initiating shutdown due to prolonged power outage."
    /usr/sbin/poweroff
    ;;
  power-on)
    logger "NUT: Power has been restored."
    ;;
  *)
    logger "NUT: Unknown command $1"
    ;;
esac

启动客户端监控服务,并按主机启动策略决定是否设置开机自启。现场见过 nut-monitor 处于 active 但 disabled 的状态;如果这台物理机重启后仍需要自动受 UPS 保护,应显式 enable:

systemctl restart nut-monitor
systemctl status nut-monitor --no-pager

# 确认需要随系统启动后再执行
systemctl enable nut-monitor

某台远端物理机上,nut-monitor 已作为客户端长期运行。一次断电后很快恢复的日志链路如下,主机名和 NUT Server 地址已替换为示例值:

nut-client-01 upssched: Timer daemon started
nut-client-01 upssched: New timer: power-off (60 seconds)
nut-client-01 nut-monitor: Network UPS Tools upsmon 2.8.0
nut-client-01 nut-monitor: UPS huawei@nut.example.internal on line power
nut-client-01 upssched: Cancelling timer: power-off
nut-client-01 nut-monitor: Executing command: power-on
nut-client-01 upssched: Executing command: power-on
nut-client-01 upssched: Timer queue empty, exiting
nut-client-01 root: NUT: Power has been restored.

这条链路说明远端物理机已经收到 NUT Server 发布的 UPS 状态,并能在恢复供电时取消本地关机计时器。它仍然不能替代完整断电演练:如果要验证 60 秒后真的关机,应在维护窗口单台灰度,不要直接对所有物理机同时演练。

6. 启动顺序和服务关联
#

配置完成后,按驱动、服务端、监控端的顺序启动,排障路径更短。

systemctl restart nut-driver
systemctl restart nut-server
systemctl restart nut-monitor

systemctl enable nut-server
systemctl enable nut-monitor

服务状态:

服务状态作用
nut-driver.serviceactive running启动 /lib/nut/huawei-ups2000 -a huawei
nut-server.serviceactive running启动 /lib/nut/upsd,监听 3493
nut-monitor.serviceactive running启动 /lib/nut/upsmon,处理 UPS 事件
ups-monitor.servicemasked现场未使用这个旧服务名

进程关系:

nut   /lib/nut/huawei-ups2000 -a huawei
nut   /lib/nut/upsd
root  /lib/nut/upsmon
root  /lib/nut/upsmon

验证命令:

systemctl --no-pager --plain status nut-driver nut-server nut-monitor
ps -eo user,pid,cmd | grep -E '[h]uawei-ups2000|[u]psd|[u]psmon'
ss -lntp | grep ':3493'

如果 nut-driver 失败,优先看串口权限和驱动路径;如果 nut-server 失败,优先看 upsd.usersups.conf;如果 nut-monitor 失败,优先看 MONITOR 行里的用户名、密码和 UPS 名称是否一致。

6.1 Web 状态入口:nut-cgi
#

nut-cgi 由 Apache 通过 CGI 目录暴露状态页。有效入口是:

http://<nut-server>/cgi-bin/nut/upsstats.cgi

这个入口只用于浏览 UPS 状态,值班时可以快速确认 ups.status、电池电量、输入输出电压和负载。它不是远程控制入口。

状态总览页展示每个 UPS 的核心字段:

NUT CGI UPS 状态总览

点击 All data 后可以进入单台 UPS 的详情视图:

NUT CGI UPS 单台详情

本机验证命令:

curl -I http://127.0.0.1/cgi-bin/nut/upsstats.cgi

返回 HTTP/1.1 200 OK 即可。下面两个路径不是现场有效入口,返回 404 属于预期结果:

http://<nut-server>/nut/upsstats.cgi
http://<nut-server>/upsstats.cgi

upsset.cgi 虽然随 nut-cgi 安装,但现场没有启用控制入口。这个 CGI 涉及修改 UPS 变量和执行控制动作,只有在 CGI 目录已经做访问控制、审计和网络隔离后,才应该考虑在 /etc/nut/upsset.conf 中打开安全确认项。普通状态查看场景保持关闭即可。

7. 功能验证矩阵
#

上线验收按下面的矩阵跑。服务 active 只能说明进程在,不能证明 UPS 数据正常刷新。

验证项命令通过标准
驱动启动systemctl status nut-driver --no-pager看到 huawei-ups2000 进程和 Startup successful
NUT Server 监听`ss -lntpgrep ‘:3493’`
UPS 数据读取upsc huawei@localhost返回 driver.nameups.status、电压、负载、电量等字段
即时命令列表upscmd -l huawei@localhost能列出 beeper、test、shutdown 等命令
可写变量upsrw huawei@localhost能看到 ups.delay.*ups.beeper.status 等变量
Web 状态页curl -I http://127.0.0.1/cgi-bin/nut/upsstats.cgi返回 HTTP/1.1 200 OK
远端物理机systemctl status nut-monitor --no-pagersecondary 物理机的 nut-monitor 为 active
远端订阅状态查看远端 journalctl -u nut-monitor出现 UPS huawei@nut.example.internal on line power 或对应状态事件
断电事件查看 journalctl -u nut-monitorONBATT 触发 upssched 计时器
恢复供电查看 journalctl -u nut-monitorONLINE 取消计时器并记录恢复日志

upsc huawei@localhost 的关键字段如下:

字段
device.mfrHuawei
device.modelUPS2000G
driver.namehuawei-ups2000
driver.version2.8.2.1
driver.version.internal0.06
driver.parameter.port/dev/ttyUSB0
battery.charge100.0
battery.runtime1400
input.voltage231V
output.voltage220V
output.realpower900W
ups.load38.0
ups.power.nominal3000
ups.statusOL CHRG
ups.temperature22.6
ups.typeonline

断电后又恢复供电时,nut-monitor 日志应出现类似链路:

UPS huawei@localhost on battery
New timer: power-off (60 seconds)
UPS huawei@localhost on line power
Cancelling timer: power-off
Executing command: power-on
NUT: Power has been restored.

这类日志能说明 upsmon -> upssched -> upssched-cmd 通知链路已经跑通,但不能当作完整的生产关机验收。市电在 60 秒内恢复,没有进入最终关机动作。

8. 继续和停止条件
#

上线或迁移时,用条件判断替代“看起来没问题”。

可以继续的条件:

  • /dev/ttyUSB0/dev/ups2000 存在,且 nut 用户可读写。
  • /lib/nut/huawei-ups2000 -V 返回预期版本。
  • nut-drivernut-servernut-monitor 都为 active。
  • upsc huawei@localhost 能稳定返回数据,且 ups.status 符合现场供电状态。
  • nut-cgi 的状态页只在受控网络内可访问,并且 /cgi-bin/nut/upsstats.cgi 返回 200。
  • 远端物理机能登录 upsd,secondary nut-monitor 日志能收到 huawei@<server> 的状态事件。
  • 断电恢复事件能触发 upssched,并按策略取消或执行计时器。

必须停止的条件:

  • 串口设备被识别为不兼容驱动,或者 upsc 数据长时间不刷新。
  • upsd.usersupsmon.conf 中的真实密码被写入文档、Git、工单或聊天记录。
  • LISTEN 0.0.0.0 3493 暴露到非可信网络,且没有防火墙限制。
  • Apache CGI 入口暴露到非可信网络,或者 upsset.cgi 在没有访问控制的情况下被启用。
  • upssched-cmd 的关机策略没有经过业务确认。
  • 需要执行 load.offdriver.killpowershutdown.return 这类会影响供电的命令,但没有维护窗口和现场人员。

9. 回滚和 fallback
#

如果驱动或配置变更导致 NUT 不稳定,回滚要优先保证 UPS 本体供电不受影响。NUT 是监控和联动层,不应该让回滚动作影响 UPS 输出。

9.1 变更前备份
#

BACKUP_DIR="/root/nut-backup-$(date +%Y%m%d%H%M%S)"
mkdir -p "${BACKUP_DIR}"

cp -a /etc/nut "${BACKUP_DIR}/etc-nut"
cp -a /lib/nut/huawei-ups2000 "${BACKUP_DIR}/huawei-ups2000" 2>/dev/null || true
systemctl status nut-driver nut-server nut-monitor --no-pager >"${BACKUP_DIR}/systemd-status.txt" || true

9.2 回滚驱动和配置
#

systemctl stop nut-monitor
systemctl stop nut-server
systemctl stop nut-driver

cp -a "${BACKUP_DIR}/etc-nut/." /etc/nut/
cp -a "${BACKUP_DIR}/huawei-ups2000" /lib/nut/huawei-ups2000

systemctl start nut-driver
systemctl start nut-server
systemctl start nut-monitor

回滚后验证:

upsc huawei@localhost
journalctl -u nut-driver -u nut-server -u nut-monitor -n 80 --no-pager

9.3 fallback 策略
#

如果 huawei-ups2000 驱动无法恢复,fallback 不是立刻尝试高风险关机,而是退回到更保守的保护方式:

  • UPS 继续作为独立供电设备运行。
  • 暂停依赖 NUT 的自动关机策略。
  • 用 UPS 面板、厂商管理卡或现场检查确认电池状态。
  • 保留 /etc/nut 和串口日志,离线复现驱动问题。
  • 在测试环境验证新驱动后再恢复生产 NUT 联动。

10. 常见问题
#

10.1 Driver not connected 怎么排查
#

按顺序检查:

ls -l /dev/ttyUSB0
id nut
systemctl status nut-driver --no-pager
journalctl -u nut-driver -n 80 --no-pager

常见原因是 nut 用户没有串口权限、ups.conf 里的 port 写错、驱动二进制不在 /lib/nut,或者 UPS 端口在切换 USB / RS-232 后没有恢复响应。

10.2 upsc huawei@localhost 连接失败
#

检查 upsd

systemctl status nut-server --no-pager
ss -lntp | grep ':3493'
grep -vE '^#|^$' /etc/nut/upsd.conf

如果本机能访问、远端不能访问,再检查防火墙和 LISTEN 地址。不要为了临时排障把 3493 暴露给全网。

10.3 远端客户端认证失败
#

核对三处名称和值:

  • ups.conf 里的 UPS 名称是 [huawei]
  • upsd.users 里的用户名和密码与客户端一致。
  • 客户端 upsmon.confMONITOR huawei@<server> ... 指向正确 NUT Server。

真实密码只保存在配置文件和密钥管理系统里,不进入文章、Git 或截图。

10.4 upssched 没有触发
#

检查这些点:

grep -E 'NOTIFYCMD|NOTIFYFLAG' /etc/nut/upsmon.conf
grep -vE '^#|^$' /etc/nut/upssched.conf
test -x /etc/nut/upssched-cmd && echo ok
journalctl -u nut-monitor -n 120 --no-pager

NOTIFYFLAG 必须包含 EXEC,否则 upsmon 只写日志,不会调用 upssched

10.5 能不能直接测试 upsmon -c fsd
#

只能在受控维护窗口测试。upsmon -c fsd 会触发 forced shutdown 语义,可能导致关联主机执行关机流程。生产环境验证应先用日志、模拟环境和单台客户端灰度确认,再做全链路演练。

11. 遗留项和加固建议
#

待确认项:

当前状态建议
NUT 监听地址upsd 监听 0.0.0.0:3493配合防火墙限制可信客户端;如果只本机使用则改为 127.0.0.1
CGI 状态页Apache 暴露 /cgi-bin/nut/upsstats.cgi只放在受控网络;需要公网访问时先加反向代理认证和访问日志
CGI 控制页upsset.cgi 已安装但未启用保持关闭;确需启用时先完成目录级认证、审计和控制命令评审
串口设备名使用 /dev/ttyUSB0增加 udev symlink,避免插拔后设备名变化
NUT 明文密码NUT 配置文件需要保存认证密码限制文件权限,发布文档必须过滤敏感值,必要时轮换
master 关机策略当前 master 收到 power-off 只记录日志结合业务目标确认是否应该保护 NUT Server 自身
驱动来源源码编译的 experimental driver升级内核、升级 NUT 或更换 UPS 型号后重新验证
破坏性命令upscmd 暴露 shutdown / load 控制能力只给可信管理员开放,生产禁止随意执行

12. 参考资料
#

参考资料分两类:运行配置和服务状态用于还原部署方式;驱动与厂商文档用于确认硬件约束和驱动参数。

资料用途
NUT 官方 huawei-ups2000 驱动文档确认 UPS2000 支持范围、USB/RS-232 约束、libmodbus 编译参数、offdelay / ondelay 等驱动参数
NUT Huawei 兼容性列表确认 NUT 设备列表中包含 Huawei UPS2000-G-3KRTS
huawei-ups2000 当前 man page补充较新版本中对 UPS2000G、XR21V1410、CH341 和已知问题的说明
华为 UPS2000 产品文档入口查询厂商手册、验收、维护、通信矩阵等资料
知乎上的同类实践文章同类部署思路参考

13. 可复用原则
#

硬件接入不要从最终服务配置开始调。更稳的顺序是:

  1. 先确认物理链路:线缆、串口设备、内核驱动和权限先过关。
  2. 再做最小通信测试:用独立程序或驱动 debug 模式确认能读到 UPS 数据。
  3. 接入服务框架:让 upsdupsmonupssched 分层验证。
  4. 保留路径一致性:如果使用发行版 systemd unit,就让自编译驱动落在发行版期望的 /lib/nut
  5. 把关机策略当生产变更:任何会影响负载供电的命令都需要维护窗口、回滚方案和现场确认。
  6. 文档只沉淀方法,不沉淀秘密:NUT 密码、UPS 序列号、客户端地址和生产 IP 都不应该出现在公开文章里。

服务 active 只是开始。只有 upsc 持续返回正确状态、远端客户端按权限登录、断电恢复事件按策略进入 upssched,这套 NUT Server 才算真正接入完成。

生产变更复盘 - 这篇文章属于一个选集。
4: 本文

相关文章

生产级 DNS 集群从零部署:dnsdist 与 Technitium 三节点架构
·2755 字·13 分钟
SRE DNS DDNS SRE DevOps +4
生产 DNS 集群平滑升级复盘:dnsdist 与 Technitium 的滚动变更
·989 字·5 分钟
SRE DNS SRE DevOps Dnsdist +3
Gitea Actions ActRunner 基于 Systemd 部署安装
·142 字·1 分钟
SRE Linux DevOps