1.场景复现
飞牛系统版本:1.1.14-1.1.20
系统环境:X86非虚拟机运行
网络环境:NetworkManager,双网口链路聚合且开启OVS,静态指定IPV4地址,自动获取IPV6地址
异常现象:网关重启后,/etc/NetworkManager/下文件除“bond1.nmconnection”外,包括
“bond1-ovs.nmconnection”
“bond1-slave1.nmconnection”
“bond1-slave2.nmconnection”
“Wired connection 1.nmconnection”
“Wired connection 2.nmconnection”
共5个文件,有关IP的信息全部丢失。系统IPV4信息丢失且无法自动恢复,IPV6正常,内部虚拟机未受影响可以正常访问。
2.问题分析(来源:deepseek)
针对以上问题,deepseek提示
“NetworkManager 在启动时,处理 OVS(Open vSwitch)复杂链接的激活顺序出错了,导致配置被清理或未能正确应用。
这通常是 NetworkManager 与 OVS 插件之间协作时的一个已知的薄弱环节。“
并给出建议
”启用调试日志:你需要在捕获 NetworkManager 的详细日志,这是定位根本原因的最有力手段。创建一个调试配置文件并重启服务以收集日志。“
# 创建调试配置文件
sudo tee /etc/NetworkManager/conf.d/debug.conf << EOF
[logging]
level=TRACE
domains=ALL
EOF
# 重启 NetworkManager 以应用调试设置并触发问题
sudo systemctl restart NetworkManager
# 查看详细的启动日志
sudo journalctl -u NetworkManager -b 0 > nm_debug.log
根据日志信息,deepseek指出
”NetworkManager 在启动过程中主动删除了 bond1-ovs 连接配置文件,然后重新创建了一个新的连接,导致原始文件丢失,并可能引发了从属接口(slave)的 IP 配置丢失。“
并强调
”这极有可能是 NetworkManager 在处理 OVS(Open vSwitch) bonding 时的已知竞争条件(race condition)或激活顺序错误。当系统重启后,NetworkManager 和 OVS 服务几乎同时启动,OVS 数据库(ovsdb)的初始化与 NetworkManager 的配置读取发生冲突。
在某些情况下,NetworkManager 会误判 OVS 端口已被移除,从而主动删除对应的连接配置文件。一旦原始连接被删除,NetworkManager 可能会基于当前 OVS 状态“假设”一个新连接,但这个新连接可能丢失了原始的 IP 配置(因为它是由 assume 机制自动生成的,而非从原文件恢复)。”
3.临时解决办法(来源:deepseek)
事实上,该问题由来已久,飞牛论坛上之前也有过讨论,但这并非只是用飞牛才会遇到的。解决方案有多种,但对像我这样的飞牛玩家来说,大多不适用。个人看来,暂时可行性比较大的有两种:
- 如果网关是定时刷新网络或者重启,那么可以通过配合网关定时重启飞牛系统。
- 通过bash脚本来备份“/etc/NetworkManager/”目录,并且通过脚本定时检测系统网络状态,当网络异常时停止NetworkManager服务,恢复“/etc/NetworkManager/”目录下文件,当网络可用后再启动NetworkManager服务。
我个人是通过启停NetworkManager服务来临时解决该问题,以下是deepseek给出的脚本代码
#!/bin/bash
# network_monitor.sh - 监控网络状态,异常时恢复 NetworkManager 连接配置
# 功能:
# - 初始化检查网络可用性,可用则备份 /etc/NetworkManager/system-connections/
# - 实时监控,连续3次失败(每次间隔5秒)触发恢复:停止 NetworkManager,恢复备份
# - 网络恢复正常后启动 NetworkManager 并执行 nmcli connection reload
# - 保留最近1天的日志,最近2次备份
# - 内置看门狗检查自身是否卡死,超时控制
set -euo pipefail
# -------------------- 配置变量 --------------------
LOG_FILE="/var/log/network_monitor.log"
BACKUP_BASE_DIR="/var/backups/network_connections"
CONNECTIONS_DIR="/etc/NetworkManager/system-connections"
PING_TARGET="8.8.8.8" # 用于 IPv4 连通性测试的目标
FAIL_THRESHOLD=3 # 连续失败次数阈值
CHECK_INTERVAL=5 # 检测间隔(秒)
HEARTBEAT_FILE="/tmp/network_monitor_heartbeat"
WATCHDOG_INTERVAL=30 # 看门狗检查间隔(秒)
WATCHDOG_TIMEOUT=60 # 看门狗超时(秒)
PID_FILE="/var/run/network_monitor.pid"
# -------------------- 函数定义 --------------------
# 记录日志,并自动清理超过1天的日志
log() {
local msg="$1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "$timestamp - $msg" | tee -a "$LOG_FILE"
# 检查日志文件修改时间,超过1天则清空
if [ -f "$LOG_FILE" ]; then
local last_mod=$(stat -c %Y "$LOG_FILE" 2>/dev/null || echo 0)
local now=$(date +%s)
if [ $((now - last_mod)) -gt 86400 ]; then
> "$LOG_FILE"
echo "$(date '+%Y-%m-%d %H:%M:%S') - Log file cleared (older than 1 day)" >> "$LOG_FILE"
fi
fi
}
# 检查 IPv4 网络连通性
check_network() {
timeout 10 ping -c3 -W2 "$PING_TARGET" >/dev/null 2>&1
}
# 备份当前连接配置,并清理旧备份(保留最近2次)
backup_connections() {
local timestamp=$(date '+%Y%m%d_%H%M%S')
local backup_dir="${BACKUP_BASE_DIR}/connections_backup_${timestamp}"
mkdir -p "$backup_dir"
cp -a "$CONNECTIONS_DIR"/* "$backup_dir"/ 2>/dev/null || true
log "Backup created at $backup_dir"
clean_old_backups
}
# 清理旧备份,只保留最近2个
clean_old_backups() {
local backups=($(ls -d ${BACKUP_BASE_DIR}/connections_backup_* 2>/dev/null | sort -r))
local count=${#backups[@]}
if [ $count -gt 2 ]; then
for ((i=2; i<$count; i++)); do
rm -rf "${backups[$i]}"
log "Removed old backup: ${backups[$i]}"
done
fi
}
# 从最新备份恢复连接配置(停止 NetworkManager,恢复文件,不启动)
restore_connections() {
local latest_backup=$(ls -d ${BACKUP_BASE_DIR}/connections_backup_* 2>/dev/null | sort -r | head -1)
if [ -z "$latest_backup" ]; then
log "ERROR: No backup found to restore!"
return 1
fi
log "Restoring from backup: $latest_backup"
# 停止 NetworkManager(加入超时)
log "Stopping NetworkManager..."
timeout 10 systemctl stop NetworkManager
sleep 2
# 清空目标目录(谨慎操作)
rm -f "$CONNECTIONS_DIR"/*
# 复制备份文件
cp -a "$latest_backup"/* "$CONNECTIONS_DIR"/
# 确保权限正确
chown root:root "$CONNECTIONS_DIR"/*
chmod 600 "$CONNECTIONS_DIR"/*
log "Restore completed. NetworkManager remains stopped until network becomes normal."
}
# 启动 NetworkManager 并重新加载配置
start_networkmanager() {
if ! systemctl is-active --quiet NetworkManager; then
log "Starting NetworkManager..."
timeout 10 systemctl start NetworkManager
sleep 2
else
log "NetworkManager is already running."
fi
log "Reloading connections..."
nmcli connection reload
}
# 启动看门狗子进程
start_watchdog() {
(
while true; do
sleep $WATCHDOG_INTERVAL
# 检查主进程是否存在
if ! kill -0 $MAIN_PID 2>/dev/null; then
exit 0 # 主进程已结束,看门狗退出
fi
# 检查心跳文件时间
if [ ! -f "$HEARTBEAT_FILE" ]; then
continue
fi
local last_heartbeat=$(stat -c %Y "$HEARTBEAT_FILE" 2>/dev/null)
if [ -z "$last_heartbeat" ]; then
continue
fi
local now=$(date +%s)
if [ $((now - last_heartbeat)) -gt $WATCHDOG_TIMEOUT ]; then
log "Watchdog: Main process seems stuck (heartbeat timeout). Restarting..."
# 强制结束主进程
kill -9 $MAIN_PID 2>/dev/null
sleep 2
# 清理 pid 文件
rm -f "$PID_FILE"
# 重新启动脚本
$0 &
exit 0
fi
done
) &
WATCHDOG_PID=$!
}
# -------------------- 主程序开始 --------------------
# 检查 root 权限
if [ "$EUID" -ne 0 ]; then
echo "Please run as root" >&2
exit 1
fi
# 创建必要目录
mkdir -p "$BACKUP_BASE_DIR"
mkdir -p "$(dirname "$LOG_FILE")"
# 单实例检查
if [ -f "$PID_FILE" ]; then
old_pid=$(cat "$PID_FILE")
if kill -0 "$old_pid" 2>/dev/null; then
echo "Another instance is already running (PID $old_pid). Exiting." >&2
exit 1
else
rm -f "$PID_FILE"
fi
fi
echo $$ > "$PID_FILE"
# 清理函数
cleanup() {
rm -f "$PID_FILE" "$HEARTBEAT_FILE"
if [ -n "${WATCHDOG_PID:-}" ]; then
kill -9 "$WATCHDOG_PID" 2>/dev/null || true
fi
exit
}
trap cleanup EXIT INT TERM
# 记录主进程 PID 并启动看门狗
MAIN_PID=$$
start_watchdog
# 初始化网络检测
log "Initial network check..."
if ! check_network; then
log "当前网络不可用,请配置网络并保证可用性后再执行该脚本"
exit 1
fi
log "Network is available. Creating initial backup..."
backup_connections
# 状态变量
network_status=0 # 0=正常, 1=异常
fail_count=0
# 主监控循环
while true; do
# 更新心跳(供看门狗检查)
touch "$HEARTBEAT_FILE"
if check_network; then
# 网络正常
if [ $network_status -eq 1 ]; then
log "Network became normal. Starting NetworkManager..."
start_networkmanager
network_status=0
fi
fail_count=0
else
# 网络异常
fail_count=$((fail_count + 1))
log "Network check failed ($fail_count/$FAIL_THRESHOLD)"
if [ $fail_count -ge $FAIL_THRESHOLD ] && [ $network_status -eq 0 ]; then
log "Network连续失败 $FAIL_THRESHOLD 次,触发恢复操作"
restore_connections
network_status=1
fail_count=0
fi
fi
sleep $CHECK_INTERVAL
done
使用方法
通过ssh远程登录飞牛,将上述代码保存为以“.sh”结尾的脚本文件。由于下面的指令涉及到管理权限,登录时需要登录管理员账号,且需要通过 sudo -i指令提权,密码即为当前登录的管理员账号的密码。
执行指令赋予脚本执行权限
chmod 755 /home/username/nm-ovs-monitor.sh
“/home/username/nm-ovs-monitor.sh”替换为自己的保存路径和文件名称。
在命令行先进行脚本测试
/home/username/nm-ovs-monitor.sh
“/home/username/nm-ovs-monitor.sh”替换为自己的保存路径和文件名称。
测试正常即可继续创建系统服务并设置开机启动,测试异常可以选择自行找AI解决,也可以贴出来随缘解答。
创建 systemd 服务
vi /etc/systemd/system/nm-ovs-monitor.service
先在命令行通过vi指令创建nm-ovs-monitor.service文件,以下为文件内容
[Unit]
Description=NetworkManager OVS Monitor and Recovery
After=network-online.target openvswitch.service
Wants=network-online.target openvswitch.service
[Service]
Type=simple
ExecStart=/usr/local/bin/nm-ovs-monitor.sh
Restart=always
RestartSec=10
CPUQuota=5%
MemoryMax=100M
[Install]
WantedBy=multi-user.target
默认设置CPU占用率最高5%,内存占用最高100M,根据配置自行修改 CPUQuota=5% MemoryMax=100M字段
在命令行启动服务
systemctl daemon-reload
systemctl enable nm-ovs-monitor.service
systemctl start nm-ovs-monitor.service
systemctl daemon-reload
重新加载 systemd 管理器的配置文件。
systemctl enable nm-ovs-monitor.service
将 nm-ovs-monitor.service服务设置为开机自启动。
systemctl start nm-ovs-monitor.service
立即启动 nm-ovs-monitor.service服务。
systemctl stop nm-ovs-monitor.service
立即停止 nm-ovs-monitor.service服务。