4629 字
23 分钟
KVM虚拟化入门(三):宿主机桥接网络 DHCP 掉线排查与修复

环境:Dell PowerEdge R730 / Debian 13 宿主机 / KVM+libvirt / NetworkManager / 桥接网络

现象:宿主机 br0 过 2~3 小时后获取不到 IP,但虚拟机网络一切正常

风险等级:✅ 低(仅添加定时任务,不修改现有网络配置)

1. 问题现象#

宿主机通过 br0 桥接上网,运行一段时间后(约 2~3 小时),br0 接口 IP 地址丢失,导致宿主机无法通过 SSH 远程连接。但奇怪的是,宿主机上的 KVM 虚拟机网络一切正常。

这个问题困扰了我好几天——每次出门回家都得去机房接显示器重新获取 IP。既然虚拟机没问题,说明物理链路和交换机是好的,那问题一定出在 DHCP 续约机制上。

为什么虚拟机没问题? 因为网桥工作在 L2(数据链路层),相当于一个虚拟交换机。宿主机 br0 和 VM 虚拟网卡是交换机上的两个独立端口,各自有不同 MAC 地址,向路由器申请完全独立的 DHCP 租约。但更关键的是——VM 用的是 ifupdown + dhclient 守护进程(传统方案),而宿主机用的是 NM 内置客户端(有缺陷)。详见 2.4 节。

2. 排查过程#

2.1 理清网络拓扑#

先弄清楚谁在管理 br0:

Terminal window
# 检查 br0 接口状态
ip addr show br0
# 17: br0: <BROADCAST,MULTICAST,UP,LOWER_UP>
# inet 192.168.214.57/24 brd 192.168.214.255 scope global dynamic noprefixroute br0
# valid_lft 3386sec preferred_lft 3386sec

scope global dynamic 说明 IP 是 DHCP 获取的,valid_lft 是剩余有效时间。

2.2 确认桥接成员#

Terminal window
# 查看 br0 绑定了哪些物理网卡
sudo bridge link show
# eno1 是 br0 的从属接口(桥接成员)
# vnet4 也是——这是 debian13 VM 的虚拟网卡!

关键发现:虚拟机也在 br0 上! debian13 VM 的虚拟网卡 vnet4 直接桥接到了物理网卡所在的 br0,并没有走 NAT。那为什么宿主机掉线时 VM 还能上网?继续看。

2.3 验证:VM 和宿主机拥有独立的 IP 和 DHCP 租约#

网桥是一个二层设备,只转发以太网帧,不关心 IP。桥上的每个”端口”(无论是宿主机的 br0 接口还是 VM 的 vnet 接口)都有自己的 MAC 地址,对路由器来说就是不同的设备。

Terminal window
# 查宿主机 br0 的 MAC
ip link show br0 | grep link/ether
# link/ether 16:47:c9:c5:4f:ff
# 查 VM 虚拟网卡的 MAC
ip link show vnet4 | grep link/ether
# link/ether fe:54:00:e8:20:e9

显然 MAC 不同。然后扫描 br0 网段上所有活跃设备:

Terminal window
sudo nmap -sn 192.168.214.0/24

输出关键行:

Nmap scan report for 192.168.214.57 ← 宿主机 br0
MAC Address: 16:47:C9:C5:4F:FF
Nmap scan report for 192.168.214.152 ← debian13 VM
MAC Address: 52:54:00:E8:20:E9 (QEMU virtual NIC)
设备接口MACIPDHCP 租约
宿主机br016:47:c9:c5:4f:ff192.168.214.57独立于路由器
debian13 VMvnet4 → br052:54:00:e8:20:e9192.168.214.152独立于路由器

两台设备插在同一个”交换机”(br0)上,MAC 不同、IP 不同、DHCP 租约完全独立

但这只解释了”IP 不同所以互不影响”,还没解释为什么相同的路由器、相同的 1 小时租约,VM 从来不掉线?

2.4 深入 VM:为什么它不掉线?#

通过 virsh console 进入 VM 检查:

Terminal window
sudo virsh console debian13
# 在 VM 内:
nmcli device status

输出关键差异:

DEVICE TYPE STATE CONNECTION
enp1s0 ethernet 未托管 --

VM 的网卡被 NetworkManager 标记为”未托管”! 说明有另外的程序在管它:

Terminal window
cat /etc/network/interfaces
auto enp1s0
iface enp1s0 inet dhcp

是传统的 ifupdown 在管理! 进一步检查进程:

Terminal window
sudo ps aux | grep dhclient
# root 1153 dhclient -4 -v -i -pf /run/dhclient.enp1s0.pid \
# -lf /var/lib/dhcp/dhclient.enp1s0.leases \
# enp1s0

真相大白:

对比项VM (debian13)宿主机 (R730)
网络管理ifupdown (/etc/network/interfaces)NetworkManager
DHCP 客户端dhclient 独立守护进程(PID 1153)NM 内置 internal 客户端
dhclient 进程✅ 常驻运行,持续监控续约❌ 无
NM 中状态未托管(unmanaged)活跃管理

dhclient 是一个有 20 多年历史的独立 DHCP 守护进程,以常驻进程方式运行,对租约续约的处理非常成熟稳健。而宿主机上 NetworkManager 内置的 internal DHCP 客户端(NM v1.52.1),在处理桥接接口的短租约续约时存在缺陷——这就是宿主机掉线而 VM 正常的真正原因。

2.5 确诊:路由器租约只有 1 小时#

Terminal window
sudo nmcli -f DHCP4 device show br0

关键输出:

DHCP4.OPTION[3]: dhcp_lease_time = 3599 ← 只有 1 小时!
DHCP4.OPTION[4]: dhcp_server_identifier = 192.168.214.165
DHCP4.OPTION[6]: expiry = 1780550374

路由器 DHCP 租约只有 3600 秒(1 小时)!

DHCP 协议规定,客户端在 T1(租约 50% = 30 分钟)和 T2(租约 87.5% = 52 分钟)时会尝试续约。如果两次都失败,租约到期后 IP 就被释放。NetworkManager 的内置 DHCP 客户端在某些情况下(链路抖动、CPU 繁忙等)可能错过续约窗口,导致 IP 丢失。

3. 修复方案#

3.1 方案探究:让 NetworkManager 改用独立 dhclient?#

尝试失败。设置了 /etc/NetworkManager/conf.d/dhcp-client.conf 让 NM 调用外部 dhclient:

[main]
dhcp=dhclient

但 NetworkManager 报错:DHCP client 'dhclient' not available,仍然回退到 internal

虽然 /usr/sbin/dhclient 存在且在 PATH 中,但 NM 的新版本似乎不再支持外部 dhclient 作为 DHCP 后端,或者说需要额外的插件包。这条路走不通。

3.2 另一个思路:能照搬 VM 的 ifupdown 方案吗?#

既然 VM 用 ifupdown + dhclient 完美不掉线,那宿主机能不能也用这套?

先搞清楚现状。宿主机上 ifupdown 已安装且 networking 服务正在运行,但 /etc/network/interfaces 是空的——当前 br0 完全由 NetworkManager 一手包办:

NetworkManager 管理的三层关系:
┌─────────────────────────────────┐
│ 连接 "br0" → 创建网桥 br0,DHCP 获取 IP
│ 连接 "br0-slave-eno1" → 将物理网卡 eno1 绑定到 br0
└─────────────────────────────────┘

桥接(br0)和物理端口(eno1)是耦合的——NM 同时控制两者。迁移到 ifupdown 意味着:

迁移步骤 SSH 风险
─────────────────────────────────────────────────
1. 写 /etc/network/interfaces ✅ 安全(只写文件)
2. nmcli 解绑 eno1 ⚠️ 网络可能抖动
3. nmcli 删除 br0 连接 ❌ SSH 必断!
4. ifup br0 (dhclient 获取新 IP) ❌ IP 可能变

一旦放开 NM 对 br0 的控制,eno1 也会脱离桥接,整条 SSH 连接立即断开。而且 ifupdown 新获取的 IP 可能跟之前不同,即使物理上跑到机房直连,也得先查新 IP 才能 SSH 回去。

这和 VM 的情况完全不同——VM 里只是一块普通的 enp1s0 以太网卡,ifupdown 和 NM 可以和平共处(NM 看到 interfaces 有配置就自动”未托管”)。但宿主机的 br0 是一个 NM 创建的桥接设备,不能简单地”交给 ifupdown”。

💡 如果在物理机前操作,其实可以这样做。但在纯 SSH 环境下,一旦出错就得跑机房——对于一个已经通过 cron 解决的问题,不值得冒这个险。

3.3 定时续约(cron + nmcli reapply)#

核心思路:每 25 分钟用 nmcli device reapply 触发一次 DHCP RENEW。这个命令不会断开网络连接,只是重新申请 DHCP 租约,比 down/up 安全得多。

先手动验证一下:

Terminal window
sudo nmcli device reapply br0
# 成功重新应用连接到设备 "br0"。

确认没问题后,创建 cron 定时任务:

Terminal window
echo '*/25 * * * * root /usr/bin/nmcli device reapply br0 2>&1 | /usr/bin/logger -t dhcp-renew-cron' \
| sudo tee /etc/cron.d/dhcp-renew-br0

为什么是 25 分钟? 租约 T1=30 分钟,在 T1 之前主动续约,确保 NM 内置客户端到 T1 时已经是新鲜租约,即使它错过 T1 也没关系——反正 25 分钟后 cron 又会续一次。

3.4 保活巡检(watchdog)#

续约是主动续期,但万一 IP 已经丢了怎么办?再加一个每 5 分钟巡检的看门狗脚本:

sudo tee /usr/local/bin/dhcp-watchdog.sh << 'SCRIPT'
#!/bin/bash
# DHCP 保活脚本:双重检测
# 1. br0 IP 丢失 → 重连
# 2. eno1 僵尸 IP 抢占路由 → 清除
# --- 检测 1: eno1 作为 bridge slave 不应有 IP ---
# eno1 的 /24 路由 metric=0 会抢走 br0 的 metric=425 路由,导致全网不通
if ip link show eno1 2>/dev/null | grep -q "master br0"; then
if ip addr show eno1 | grep -q "inet "; then
logger -t dhcp-watchdog "eno1 has stale IP as bridge slave, flushing..."
ip addr flush dev eno1
fi
fi
# --- 检测 2: br0 没有 192.168.x.x IP ---
if ! ip addr show br0 | grep -q "inet 192.168"; then
logger -t dhcp-watchdog "br0 lost IP, reconnecting..."
nmcli con down br0 2>/dev/null
sleep 3
nmcli con up br0 2>/dev/null
fi
SCRIPT
sudo chmod +x /usr/local/bin/dhcp-watchdog.sh
Terminal window
echo '*/5 * * * * root /usr/local/bin/dhcp-watchdog.sh' \
| sudo tee /etc/cron.d/dhcp-watchdog

nmcli con down/up 会断开重连网络,影响约 5~10 秒。相比之前掉线几小时找不到,这 5 秒的代价完全可以接受。

3.5 最终部署架构#

┌──────────────────────────────────────────────────────────────┐
│ DHCP 保活体系 │
├──────────────┬─────────────────┬─────────────────────────────┤
│ NetworkManager │ cron 定时任务 │
│ 内置 DHCP 客户端 │ │
│ 正常管理租约 │ ┌─ 每 25 min ─────────────┐ │
│ │ │ nmcli device reapply │ │
│ │ │ (续约 br0) │ │
│ │ └─────────────────────────┘ │
│ │ ┌─ 每 5 min ──────────────┐ │
│ │ │ dhcp-watchdog.sh │ │
│ │ │ ├─检测 br0 IP → 重连 │ │
│ │ │ └─检测 eno1 IP → 清除 │ │
│ │ └─────────────────────────┘ │
└──────────────┴─────────────────┴─────────────────────────────┘

NM 内置客户端负责正常的 DHCP 获取和续约,cron 定时任务做多重保障:主动续约、掉线恢复、僵尸 IP 清理。

3.6 衍生故障:eno1 僵尸 IP 抢占路由#

DHCP 续约问题刚”修好”,过了一天又断网了。但这次不同——ip addr show br0 显示 IP 还在,网关却 ping 不通。

排查#

Terminal window
# br0 有 IP,但 ping 网关不通
ip addr show br0 | grep inet
# inet 192.168.43.101/24 ← IP 在!
ping 192.168.43.1
# 100% packet loss ← 但不通!

看路由表:

Terminal window
ip route show

关键发现:

192.168.43.0/24 dev eno1 proto kernel scope link src 192.168.43.69 ← metric 0!
192.168.43.0/24 dev br0 proto kernel scope link src 192.168.43.101 metric 425

eno1 上也出现了一个 IP:

Terminal window
ip addr show eno1 | grep inet
# inet 192.168.43.69/24

路由匹配规则是”最长前缀优先,同前缀比 metric”。两条都是 /24,eno1 的 metric=0(无显式 metric,内核默认最低值),br0 的 metric=425。结果发往网关 192.168.43.1 的包走了 eno1 而不是 br0

Terminal window
# 验证:从 eno1 发包能通吗?
ping -I eno1 192.168.43.1
# Destination Host Unreachable ← eno1 作为 bridge slave 根本不通!

这就是典型的 L2/L3 分离问题——eno1 入桥后降级为纯 L2 端口(bridge slave state forwarding),只转发帧不处理 IP。它上面就算绑了 IP 也是”僵尸”——存在但不可达。

为什么 eno1 会有 IP?#

回到之前的分析:NM 管理着一个叫 br0-slave-eno1 的连接,它把 eno1 绑定到 br0。但 NM 启动初期,在 br0 还没创建时 NM 就可能已经给 eno1 获取了 DHCP。或者在 DHCP 续约周期中,NM 内部逻辑偶发地又在 eno1 上跑了一次 DHCP——具体机制不太透明,但事实就是:每小时 DHCP 续约窗口,eno1 有一定概率出现僵尸 IP

永久修复#

思路很明确:bridge slave 不该有 IP。两步走:

① 即时修复:从 watchdog 脚本入手。每 5 分钟检测 eno1 上是否有僵尸 IP,有就清掉。

Terminal window
# 检测逻辑(已合并进 watchdog,见 3.4 节完整脚本)
if ip link show eno1 | grep -q "master br0"; then # 确认它是 bridge slave
if ip addr show eno1 | grep -q "inet "; then # 但它有 IP!
ip addr flush dev eno1 # 立即清除
fi
fi

② 为什么不用 metric 方案?有人可能会想给 eno1 路由加高 metric:

Terminal window
# 理论上可行,但不推荐
ip route change 192.168.43.0/24 dev eno1 metric 9999

但这只是”容忍”僵尸存在。bridge slave 本来就不该有 IP——留着是隐患,清掉最干净。

验证#

手动模拟故障,验证 watchdog 自动修复:

Terminal window
# 1. 手动给 eno1 注入僵尸 IP
sudo ip addr add 192.168.43.69/24 dev eno1
# 2. 确认路由表被污染
ip route show | grep "192.168.43.0"
# 192.168.43.0/24 dev eno1 ...
# 192.168.43.0/24 dev br0 ... metric 425
# 3. 运行 watchdog
sudo /usr/local/bin/dhcp-watchdog.sh
# 4. 验证已清理
ip addr show eno1 | grep inet || echo "✅ 已清除"
ip route show | grep "192.168.43.0"
# 只剩 br0 一条路由 ✅
Terminal window
# 查看清理日志
sudo journalctl -t dhcp-watchdog
# eno1 has stale IP as bridge slave, flushing...

4. cron 与 systemd timer 对比#

读者可能会问:为什么用 cron 而不是 systemd timer?两者都是 Linux 下的定时任务方案,但各有优劣。

4.1 cron#

Cron 是 Unix 系统最古老的定时任务工具,已有 40 多年历史。配置方式是在 crontab 文件中按格式写一行:

分 时 日 月 周 用户 命令
*/25 * * * * root /usr/bin/nmcli device reapply br0

优点

  • 📖 语法简单直观*/25 * * * * 一看就懂”每 25 分钟执行一次”
  • 🪶 极轻量,cron 守护进程通常只占几 MB 内存
  • 📧 邮件通知,命令输出默认发送到 root 邮箱(/var/mail/root
  • 📁 文件化管理,丢一个文件到 /etc/cron.d/ 即可,删除即卸载

缺点

  • ❌ 不支持秒级精度(最小粒度 1 分钟)
  • ❌ 没有依赖管理(不能指定”在网络启动后运行”)
  • ❌ 如果上次任务还没结束,下次仍会启动(可能产生多个实例)
  • ❌ 日志分散,排查问题需要靠 logger 手动写 syslog

4.2 systemd timer#

Systemd 是 modern Linux 的初始化系统。它的 timer 单元可以实现和 cron 一样的功能,但更现代化:

/etc/systemd/system/dhcp-renew.timer
[Timer]
OnCalendar=*:0/25
Persistent=true
[Install]
WantedBy=timers.target
/etc/systemd/system/dhcp-renew.service
[Service]
Type=oneshot
ExecStart=/usr/bin/nmcli device reapply br0
Terminal window
sudo systemctl enable --now dhcp-renew.timer

优点

  • ⏱️ 秒级精度,最小到微秒
  • 🔗 依赖管理,可以指定 After=network-online.target,确保网络就绪后才执行
  • 📊 统一日志,输出自动进入 journald,用 journalctl -u dhcp-renew 查看
  • 🔒 沙箱选项,可以限制任务的 CPU、内存、文件系统访问等
  • 📈 状态可查systemctl status dhcp-renew.timer 看下次触发时间

缺点

  • 📝 需要写两个文件(.timer + .service),配置比 cron 繁琐
  • 🏋️ 依赖 systemd 体系,传统 Unix 用户不太熟悉
  • 💾 更多内存开销(虽然也不大)

4.3 选择建议#

场景推荐方案
简单周期性任务(如本例)cron
需要依赖管理(如网络就绪后再跑)systemd timer
需要秒级精度systemd timer
临时一次性延迟任务at 命令
系统级服务定时触发systemd timer

本次选择 cron 的理由:任务足够简单(就一条命令),cron 一个文件搞定,出问题直接删文件。如果未来需要”先检测网络连通性再续约”这种逻辑,再迁移到 systemd timer 也不迟。

5. 定时任务管理#

5.1 查看已部署的 cron 任务#

Terminal window
# 查看 /etc/cron.d/ 下的系统任务
ls /etc/cron.d/
# dhcp-renew-br0 dhcp-watchdog anacron e2scrub_all
# 查看任务内容
cat /etc/cron.d/dhcp-renew-br0
# */25 * * * * root /usr/bin/nmcli device reapply br0 2>&1 | /usr/bin/logger -t dhcp-renew-cron
cat /etc/cron.d/dhcp-watchdog
# */5 * * * * root /usr/local/bin/dhcp-watchdog.sh

5.2 查看任务执行日志#

Terminal window
# 续约任务日志(我们手动用 logger 写入的)
sudo journalctl -t dhcp-renew-cron
# 保活巡检日志
sudo journalctl -t dhcp-watchdog
# 查看最近一次 cron 执行
sudo journalctl -u cron --since "10 minutes ago"

5.3 临时禁用 / 恢复#

Terminal window
# 禁用:注释掉 cron 行
sudo sed -i 's/^/#/' /etc/cron.d/dhcp-renew-br0
# 恢复:去掉注释
sudo sed -i 's/^#//' /etc/cron.d/dhcp-renew-br0
# 禁用:删除文件
sudo rm /etc/cron.d/dhcp-renew-br0 # 不想要了直接删
# cron 会自动检测目录变化,无需重启服务

⚠️ 不需要 systemctl restart cron!cron 守护进程会监控 /etc/cron.d/ 目录变化,修改立即生效。

5.4 手动触发一次测试#

Terminal window
# 手动执行续约
sudo nmcli device reapply br0
# 手动执行巡检脚本
sudo /usr/local/bin/dhcp-watchdog.sh
# 查看是否生效
ip addr show br0 | grep "valid_lft"

5.5 crontab 语法速查#

┌──── 分钟 (0-59)
│ ┌──── 小时 (0-23)
│ │ ┌──── 日 (1-31)
│ │ │ ┌──── 月 (1-12)
│ │ │ │ ┌──── 星期 (0-7, 0=周日)
│ │ │ │ │
* * * * * 用户 命令

常用示例:

表达式含义
*/5 * * * *每 5 分钟
*/25 * * * *每 25 分钟
0 * * * *每小时整点
0 2 * * *每天凌晨 2 点
0 0 1 * *每月 1 号零点
@reboot开机时执行一次

*/N 语法表示”每隔 N 个单位”。*/25 * * * * = 每当分钟数是 25 的倍数时触发,即 0、25、50,每小时三次。

6. 常见问题排查#

问题可能原因解决方案
cron 任务没执行命令路径错误务必使用绝对路径(/usr/bin/nmcli 而非 nmcli
journalctl 看不到日志logger 未安装或日志级别过高loggerutil-linux 自带,确认 which logger
nmcli reapply 报错NM 连接名称不对nmcli con show 确认连接名是 “br0”
watchdog 频繁触发br0 IP 真的在反复丢失检查物理网线、交换机端口、路由器日志
br0 有 IP 但网关不通eno1 僵尸 IP 抢占路由watchdog 会自动清理,或手动 ip addr flush dev eno1
想用 systemd timer 代替见第 4.2 节模板,创建 .timer + .service 文件

7. 总结#

问题根因#

路由器 DHCP 租约只有 1 小时,宿主机 NetworkManager 内置的 internal DHCP 客户端在处理桥接接口的短租约续约时存在缺陷,偶发续约失败导致 IP 过期丢失。

为什么虚拟机不掉线?#

VM 使用的是传统 ifupdown + dhclient 守护进程的组合。dhclient 作为一个独立常驻进程,对 DHCP 租约续约的处理比 NM 内置客户端成熟稳健得多。两者虽然都向同一个路由器申请 IP、租约都是 1 小时,但 dhclient 能正确续约,NM internal 不能——这就是宿主机掉线而 VM 正常的真正原因。

修复要点#

  1. 不变更现有网络配置——不碰 NetworkManager、不碰桥接设置
  2. 定时主动续约——每 25 分钟 nmcli device reapply br0,抢在 DHCP T1 之前续约
  3. 掉线自动恢复——每 5 分钟检测 br0 IP,丢失则重连
  4. 僵尸 IP 自动清理——每 5 分钟检测 eno1 作为 bridge slave 是否有 IP,有则清除
  5. 日志可追溯——所有操作通过 logger 写入 journald

衍生故障:eno1 路由劫持#

部署 cron 续约后隔了一天,又出现网关不通。这次 br0 IP 还在,但路由表里多了一条 192.168.43.0/24 dev eno1(metric 0),抢走了 br0 的 metric 425 路由。原因是 eno1 作为 bridge slave 不应有 IP,但 NM 在 DHCP 周期中偶发地给它也获取了一个。修复方式是 watchdog 脚本增加 eno1 检测——检测到 eno1 在桥上却持有 IP 就 ip addr flush。详见 3.6 节

延伸思考#

这个问题的根本解决方式是让路由器把 DHCP 租约时间改长(比如 86400 秒 = 24 小时),或者给宿主机 br0 配置静态 IP,彻底摆脱 DHCP 续约的烦恼。

也许你会想到——能不能像 VM 那样用 ifupdown + dhclient 接管宿主机网络?理论上可以,但在纯 SSH 环境下操作风险极高:NM 管理的桥接是一个耦合结构(br0 + eno1 绑定),任何一端的变更都会导致网络断开。详见 3.2 节

本文记录于 2026 年 6 月 4 日,环境 Debian 13 + NetworkManager + qemu-kvm

KVM虚拟化入门(三):宿主机桥接网络 DHCP 掉线排查与修复
https://www.mintlab.top/posts/tries/kvm-network-dhcp-fix-guide/
作者
Mint
发布于
2026-06-04
许可协议
CC BY-NC-SA 4.0
发表评论

输入用户名和邮箱后自动检查登录状态。登录后用户名和邮箱将被绑定, 只可以修改头像和主页链接。

未登录
昵称
邮箱
填写头像链接与主页链接

头像链接为空默认使用gravatar头像

头像
主页
人机验证
评论列表

以下是可爱的评论们:

暂无评论, 呜呜, 快来评论喵!