收起左侧

针对网关路由器重启导致飞牛IP异常丢失的分析及临时解决办法

0
回复
70
查看
[ 复制链接 ]

1

主题

13

回帖

0

牛值

江湖小虾

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)

事实上,该问题由来已久,飞牛论坛上之前也有过讨论,但这并非只是用飞牛才会遇到的。解决方案有多种,但对像我这样的飞牛玩家来说,大多不适用。个人看来,暂时可行性比较大的有两种:

  1. 如果网关是定时刷新网络或者重启,那么可以通过配合网关定时重启飞牛系统。
  2. 通过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服务。

收藏
送赞
分享
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则