正在加载...

使用威联通UPS服务器控制PVE自动关机

技术杂货铺  ·  2025-08-18

预计阅读时间: 40.6 分钟
10023 字1 图250 字/分
⚠️
本文有一定时效性·3个月前更新
最后更新: 2025年08月18日

很久之前买了一个山特850的UPS,一直接在威联通的NAS上

image.png

自己也就仅限于控制自己的NAS断电自关机了,虽说IP里面写着通知PVE母鸡关机,但其实完全没在PVE上配置

晚上想到自己过段时间要去外地,心血来潮一次在pve里配置好了NUT client

话不多说,立马开始,先在PVE里安装一下nut-client

安装

apt update
apt install nut-client

配置NUT客户端连接到威联通NAS

先编辑/etc/nut/nut.conf

设置以下字段

MODE=netclient

继续编辑/etc/nut/upsmon.conf,为了方便维护,我把所有相关的脚本全部放到了/upssh文件夹下

# 监控威联通的UPS
MONITOR qnapups@<威联通IP> 1 upsmon <密码> slave
# 设置关机命令
SHUTDOWNCMD "/upssh/pve-shutdown.sh"
# 断电后等待时间(秒)
FINALDELAY 120
# 电池低电量时的延迟(秒)
HOSTSYNC 15
# 设置通知命令
NOTIFYCMD /upssh/upssched

检查NUT连接

root@pve:~# upsc qnapups@192.168.1.5 #配置的UPS服务器地址
Init SSL without certificate database
battery.charge: 100
battery.charge.low: 20
battery.runtime: 1296
battery.type: PbAc
device.mfr: EATON
device.model: SANTAK TG-BOX 850 
device.serial: Blank
device.type: ups
driver.name: usbhid-ups
driver.parameter.pollfreq: 30
driver.parameter.pollinterval: 2
driver.parameter.port: /dev/ttyS1
driver.parameter.synchronous: no
driver.version: 2.7.4
driver.version.data: MGE HID 1.39
driver.version.internal: 0.41
input.transfer.high: 264
input.transfer.low: 184
outlet.1.desc: PowerShare Outlet 1
outlet.1.id: 1
outlet.1.status: on
outlet.1.switchable: no
outlet.desc: Main Outlet
outlet.id: 0
outlet.switchable: yes
output.frequency.nominal: 50
output.voltage: 230.0
output.voltage.nominal: 220
ups.beeper.status: enabled
ups.delay.shutdown: 20
ups.delay.start: 30
ups.firmware: 02.08.0010
ups.load: 23
ups.mfr: EATON
ups.model: SANTAK TG-BOX 850 
ups.power.nominal: 850
ups.productid: ffff
ups.serial: Blank
ups.status: OL
ups.timer.shutdown: 0
ups.timer.start: 0
ups.type: offline / line interactive
ups.vendorid: 0463

配置自定义的关机脚本

PVE作为虚拟化平台,我们自然不能让他一下子就关机,而不考虑虚拟机的问题。所以我们的构想是,关机脚本需要先把所有虚拟机休眠(挂起到硬盘,方便恢复),等待全部休眠后再关闭PVE,这样会减少数据丢失的可能

在查过PVE的官方文档后,发现将虚拟机休眠到硬盘的指令是

qm suspend <vmid> --todisk

先创建一个/upssh/pve-shutdown.sh,内容如下(想要完美脚本的直接跳到最后一步)

#!/bin/bash
# 记录日志
logger "UPS: 开始休眠所有虚拟机到磁盘..."

# 获取所有运行中的VM
RUNNING_VMS=$(qm list | grep running | awk '{print $1}')
RUNNING_CTS=$(pct list | grep running | awk '{print $1}')

# 休眠所有运行中的VM到磁盘
for vmid in $RUNNING_VMS; do
    echo "正在休眠VM $vmid 到磁盘..."
    logger "UPS: 正在休眠VM $vmid 到磁盘"
    qm suspend $vmid --todisk
done

# 停止所有运行中的LXC容器
for ctid in $RUNNING_CTS; do
    echo "正在停止容器 $ctid..."
    logger "UPS: 正在停止容器 $ctid"
    pct stop $ctid
done

# 等待所有VM休眠完成
echo "等待虚拟机休眠到磁盘..."
while [ $(qm list | grep running | wc -l) -gt 0 ]; do
    sleep 5
    echo "仍有虚拟机在运行,继续等待..."
done

logger "UPS: 所有虚拟机已休眠到磁盘"
echo "测试完成:所有虚拟机已休眠到磁盘,状态已保存"

# 测试阶段先注释掉
# /sbin/shutdown -h now

然后给这个脚本相应的权限

chmod +x /upssh/pve-shutdown.sh

配置延迟关闭时间

打开/etc/nut/upssched.conf

修改中间的CMDSCRIPT为

CMDSCRIPT /upssh/upssched-cmd

在最后添加

PIPEFN /run/nut/upssched/upssched.pipe
LOCKFN /run/nut/upssched/upssched.lock
AT ONBATT * START-TIMER shutdown 300
AT ONLINE * CANCEL-TIMER shutdown

接下来创建/upssh/upssched-cmd,并写入

#!/bin/bash

case $1 in
    shutdown)
        logger "UPS: 电池供电已5分钟,开始关机流程..."
        /upssh/pve-shutdown.sh
        ;;
    *)
        logger "UPS: 未知命令 $1"
        ;;
esac

同样设置权限

chmod +x /upssh/upssched-cmd

重启服务

systemctl restart nut-client
systemctl enable nut-client
systemctl restart nut-monitor
systemctl enable nut-monitor

第一次执行出错

当我满怀信心的开始测试SH脚本能不能正常使用的时候,BUG出现了

root@pve:~# sh /upssh/pve-shutdown.sh
/upssh/pve-shutdown.sh: line 4: $'\r': command not found
/upssh/pve-shutdown.sh: line 6: syntax error near unexpected token $'do\r''
'upssh/pve-shutdown.sh: line 6: for vmid in $(qm list | grep running | awk '{print $1}'); do

居然出现了Windows格式的换行符,这时候就需要使用dos2unix了

# 安装dos2unix
apt install dos2unix

# 转换文件格式
dos2unix /upssh/pve-shutdown.sh
dos2unix /upssh/upssched-cmd

# 再次运行测试
sh /upssh/pve-shutdown.sh

第二次执行出错

我寻思现在应该没啥问题了,结果继续出问题了

我有一个虚拟机使用了硬盘直通,直接无法休眠了

root@pve:~# sh /upssh/pve-shutdown.sh
正在休眠VM 100 到磁盘...
cannot suspend VM to disk due to passed-through PCI device(s), which lack the possibility to save/restore their internal state
正在休眠VM 101 到磁盘...
Formatting '/mnt/ssd-1024g/images/101/vm-101-state-suspend-2025-08-18.raw', fmt=raw size=9114222592 preallocation=off
State saved, quitting
正在休眠VM 102 到磁盘...
Formatting '/mnt/ssd-1024g/images/102/vm-102-state-suspend-2025-08-18.raw', fmt=raw size=13409189888 preallocation=off
State saved, quitting
正在休眠VM 103 到磁盘...
Formatting '/mnt/ssd-1024g/images/103/vm-103-state-suspend-2025-08-18.raw', fmt=raw size=4819255296 preallocation=off
State saved, quitting
等待虚拟机休眠到磁盘...
仍有虚拟机在运行,继续等待...

该死的PVE居然不支持让直通虚拟机休眠

于是乎改变策略,让有PCI直通的虚拟机直接执行关机指令,懒得改脚本了,直接丢给Claude重新写一个

#!/bin/bash
# 记录日志
logger "UPS: 开始处理所有虚拟机..."

# 获取所有运行中的VM和容器
RUNNING_VMS=$(qm list | grep running | awk '{print $1}')
RUNNING_CTS=$(pct list | grep running | awk '{print $1}')

# 处理每个运行中的VM
for vmid in $RUNNING_VMS; do
    echo "正在检查VM $vmid..."
    
    # 检查是否有PCI直通设备
    if qm config $vmid | grep -q "^hostpci"; then
        echo "VM $vmid 有PCI直通设备,执行关机..."
        logger "UPS: VM $vmid 有PCI直通,执行关机"
        qm shutdown $vmid --timeout 60 &
    else
        echo "VM $vmid 正在休眠到磁盘..."
        logger "UPS: VM $vmid 休眠到磁盘"
        qm suspend $vmid --todisk &
    fi
done

# 等待处理完成
echo "等待所有操作完成..."
MAX_WAIT=180
WAITED=0

while [ $(qm list | grep running | wc -l) -gt 0 ] && [ $WAITED -lt $MAX_WAIT ]; do
    sleep 10
    WAITED=$((WAITED + 10))
    RUNNING=$(qm list | grep running | awk '{print $1}' | tr '\n' ' ')
    if [ -n "$RUNNING" ]; then
        echo "仍在运行: $RUNNING (已等待 ${WAITED}秒)"
    fi
done

# 强制停止超时的VM
if [ $(qm list | grep running | wc -l) -gt 0 ]; then
    echo "超时!强制停止剩余的虚拟机..."
    for vmid in $(qm list | grep running | awk '{print $1}'); do
        echo "强制停止VM $vmid"
        logger "UPS: 强制停止VM $vmid"
        qm stop $vmid
    done
fi

# 停止所有容器
for ctid in $RUNNING_CTS; do
    echo "正在停止容器 $ctid..."
    logger "UPS: 正在停止容器 $ctid"
    pct stop $ctid
done

logger "UPS: 所有虚拟机已处理完成"
echo "完成:所有虚拟机已处理"

# 生产环境取消注释
# /sbin/shutdown -h now
root@pve:~# sh /upssh/pve-shutdown.sh
正在检查VM 100...
VM 100 有PCI直通设备,执行关机...
正在检查VM 101...
VM 101 正在休眠到磁盘...
正在检查VM 102...
Formatting '/mnt/ssd-1024g/images/101/vm-101-state-suspend-2025-08-18.raw', fmt=raw size=9114222592 preallocation=off
VM 102 正在休眠到磁盘...
正在检查VM 103...
VM 103 正在休眠到磁盘...
等待所有操作完成...
Formatting '/mnt/ssd-1024g/images/102/vm-102-state-suspend-2025-08-18.raw', fmt=raw size=13409189888 preallocation=off
Formatting '/mnt/ssd-1024g/images/103/vm-103-state-suspend-2025-08-18.raw', fmt=raw size=4819255296 preallocation=off
State saved, quitting
State saved, quitting
State saved, quitting
仍在运行: 100  (已等待 10秒)
完成:所有虚拟机已处理

先在已经完全没问题了,就可以把脚本最后一行的关机指令取消注释了,十分完美

上一篇:寄夏
评论
正在加载验证组件