资讯详情

资讯详情

虚拟机内网单向丢包?断电后宿主机网卡NO-CARRIER的排查与修复

1. 故障现象与排查前必须搞清楚的拓扑1.1 这次的故障现场一个看起来能通却又通不了的怪问题机房异常断电这种事经历过的人都知道最怕的不是硬件直接烧掉而是重启之后一切看起来都在运行网络却暗藏玄机。我这次遇到的就是一台宿主机带多台虚拟机断电恢复后出现了一组非常迷惑的网络症状从宿主机 SSH 连接虚拟机的内网地址一直超时连不上但从公网地址 SSH 同一个虚拟机却秒连正常在虚拟机里 ping 宿主机内网地址持续丢包反过来从宿主机 ping 虚拟机内网地址却完全正常零丢包。刚看到这个现象时我第一反应是虚拟机防火墙是不是把内网来源给挡了第二反应是是不是 SSH 服务只监听了公网口。但仔细一看公网 SSH 正常说明 SSH 服务本身活着主机又能 ping 通虚拟机说明二层和三层的通路也在。那问题到底出在哪这类部分连通、部分不通的故障比完全断网更难搞。完全断网无非是线路、网卡、服务三者之一而这种单向通、双向不通的组合意味着问题大概率藏在路由回程、网卡状态、或主机侧的数据包处理逻辑里。我后来花了大概四十分钟逐层排查才把根因锁定。这篇文章就把完整排查链路写下来希望对遇到类似现象的运维同行有帮助。1.2 先把网络拓扑画在纸上再谈排查动手之前一定要先把拓扑理清楚。很多人一上来就在虚拟机上抓包、重启网络服务折腾半天没头绪就是因为没先想明白数据包到底走了哪条路。我这次的宿主机是双网卡配置拓扑大概是这样的物理网卡 enp1s0接内网交换机宿主机内网 IP 为 192.168.10.10/24物理网卡 enp2s0接外部链路宿主机公网 IP 用于对外映射虚拟交换机 vbr0宿主机内部创建的虚拟二层设备IP 为 192.168.10.11/24虚拟机 VM-A两块虚拟网卡一块 vnet0 接在 vbr0 上内网 IP 为 192.168.10.20/24另一块 vnet1 桥接到 enp2s0用于公网访问。看清楚这个拓扑之后很多现象就能解释了。宿主机和虚拟机其实是通过虚拟交换机 vbr0 在做二层通信物理内网网卡 enp1s0 是否正常并不影响宿主机到虚拟机之间的直连。也就是说主机 ping 虚拟机通根本说明不了内网物理链路是好的。这是这次排查中最容易误判的地方。所以我把建议写在前面任何一个网络故障先花五分钟把物理网卡、虚拟网桥、虚拟机的 IP 归属和连接关系画出来再开始测。你连数据包该走哪条路都不知道抓包和改配置都是瞎蒙。1.3 建一个连通性矩阵圈出可疑范围把现象整理成表格可以非常直观地缩小排查范围探测方向协议/端口结果宿主机 → 虚拟机内网 IPpingICMP通0% 丢包虚拟机 → 宿主机内网 IPpingICMP丢包约 30%~50%宿主机 → 虚拟机内网 IPSSHTCP 22超时失败公网 → 虚拟机SSHTCP 22正常连接宿主机 → 虚拟机公网映射地址SSHTCP 22正常连接这个矩阵给出来的信息量其实非常大。公网 SSH 正常说明虚拟机里的 sshd 进程、TCP 协议栈、虚拟机的第二块网卡链路都是好的宿主机 ping 虚拟机通说明宿主机内核到虚拟机网卡之间的数据通路是好的。那么问题就被压缩到了一个非常窄的范围虚拟机的回程数据包或者宿主机处理来自虚拟机的数据包时出了问题。接下来要做的就是把这个窄范围进一步定位到具体某一层。从链路层、网络层一层一层剥开看。2. 逐层排查接口、路由、邻居表与防火墙2.1 第一眼神检查接口状态物理链路是不是假死登录宿主机我第一件事就是看接口状态。注意这里不是看 IP 还在不在而是看接口的底层状态。命令很简单ip addr show ip link show输出里最有价值的是每个接口的状态字段。正常工作的物理网卡应该是state UP并且有LOWER_UP标志如果看到state DOWN、NO-CARRIER就要高度怀疑了。我当时看到的输出是这样3: enp1s0: NO-CARRIER,BROADCAST,MULTICAST,UP,LOWER_UP mtu 1500 qdisc mq master vbr0 state DOWN group default qlen 1000 link/ether 00:16:3e:xx:xx:xx brd ff:ff:ff:ff:ff:ffNO-CARRIER这个标志的意思是物理层没有检测到载波信号——说白了就是网线这段没有链路驱动状态虽然是 UP但对端设备交换机或者线缆没有给出正常的协商信号。但注意NO-CARRIER并不代表这块网卡彻底废了它可能只是断电重启后没有和对端交换机重新完成协商。再用 ethtool 确认一下ethtool enp1s0输出中的Link detected: no就实锤了物理链路断掉。到这一步我已经基本确定问题在内网物理网卡链路上而不是虚拟机或者 SSH 服务本身。这里要特别提醒一句如果你用的是基于内核的虚拟化环境虚拟网桥上的vnet接口状态是 UP不代表物理网卡也是 UP。虚拟网桥是内核里的二层设备它只管自己端口上的数据转发物理出口链路断了桥内通信照样是通的。这就是主机 ping 虚拟机正常但真正走物理内网的流量全挂的核心原因。2.2 路由表检查谁在负责回包这条路径接口状态异常后我紧接着查了路由表。为什么因为即使 ping 不通也一定要搞清楚数据包如果发出去了会被路由到哪个接口。很多时候故障的答案不在入口而在出口。ip route show table main当时的输出类似这样default via 198.51.100.1 dev enp2s0 192.168.10.0/24 dev enp1s0 proto kernel scope link src 192.168.10.10 192.168.10.0/24 dev vbr0 proto kernel scope link src 192.168.10.11注意这里有一个非常关键的细节192.168.10.0/24这个网段同时出现在enp1s0和vbr0上。Linux 内核路由表里如果多条路由都匹配目标地址会优先选最具体的即前缀最长的如果前缀一样长则看路由的 metric 和管理距离。但这里两条路由前缀都是 /24它们之间的优先级就非常微妙通常由proto kernel和 metric 共同决定。问题就在这里当内核要给 192.168.10.20虚拟机发送应答包时它需要决定走enp1s0还是vbr0。如果因为路由度量、或者因为源地址 192.168.10.10 属于 enp1s0 而被内核绑定到了 enp1s0 这个出口那这个包就会交给一个NO-CARRIER的接口发送结果自然是石沉大海。这个现象在 Linux 网络里很容易被忽视。很多人以为目标 IP 在哪个网段就从哪个网段出去但内核真正选择出口时还要看源地址、接口路由、策略路由规则等多个因素。断电之后接口状态发生变化内核在启动时生成的路由表可能把流量指向了一个实际上不可用的出口。2.3 ARP 与邻居表二层到底通没通既然二层是虚拟网桥物理网卡又异常那 ARP 表就成了判断到底哪些路径是真的通的关键证据。在宿主机上执行ip neigh show看虚拟机的 MAC 地址是被解析到了哪个接口上。正常情况下192.168.10.20 应该有一条REACHABLE或STALE的邻居记录且对应的 dev 是 vbr0。如果这条记录的状态是FAILED或者INCOMPLETE说明 ARP 请求根本没得到回应二层就没通。我这边看到虚拟机在 vbr0 上有一条正常的记录说明宿主机和虚拟机之间通过虚拟交换机的通路没问题。这也再次验证了从宿主机到虚拟机数据包走的是 vbr0根本不需要物理网卡参与。然后我进到虚拟机里ping 了一下宿主机内网 IP同时在宿主机上用 tcpdump 抓包tcpdump -i vbr0 -n icmp抓到的情况很能说明问题虚拟机的 ICMP 请求包确实进入了宿主机内核但宿主机回应的 ICMP reply 包并没有出现在 vbr0 上。换句话说请求进来了但应答没从原路返回。数据包被内核收下后交给了另一个出口——很可能就是那个物理链路异常的内网网卡 enp1s0。二层没问题三层路由选的出口有问题这就把故障范围进一步锁死了。2.4 防火墙与 rp_filter快速排除干扰项在确认路由问题之前我还快速过了一遍防火墙和反向路径过滤。因为异常断电后有些环境会自动加载持久化的 iptables 规则顺序变化或者服务启动不完整都可能导致诡异丢包。排除它们不需要花太久但能避免误判。先看防火墙服务状态和规则systemctl status firewalld iptables -nvL INPUT iptables -nvL FORWARD重点检查 INPUT 链里有没有针对内网网段、ICMP 或 SSH 端口的 DROP/REJECT 规则FORWARD 链则要关注虚拟网段之间的转发策略。断电重启可能导致网络服务加载顺序变化规则虽然还是那几条但命中条件可能就变了。再检查 rp_filtersysctl net.ipv4.conf.all.rp_filter sysctl net.ipv4.conf.vbr0.rp_filterrp_filter 是 Linux 内核的反向路径过滤机制简单说就是从哪个接口收到的包如果回包走的不是同一个接口就把这个包丢掉。在多网卡环境下rp_filter 开启时非常容易造成单向丢包。这次我的环境里 rp_filter 是关闭的所以排除了它。但很多真实环境里 rp_filter1 是默认值遇到类似现象时这也是一个高度可疑的点后面我会单独展开讲。3. 定位根因内网网卡假死回包走了错误出口3.1 去程通、回程断的完整数据流复盘排查到这里根因其实已经浮出水面了。我完整复盘一下数据包的走向大家就明白为什么现象会这么拧巴。先看宿主机 ping 虚拟机通这条路径宿主机发起 ping 192.168.10.20内核查路由表目标 192.168.10.0/24 匹配出口选择 vbr0数据包通过 vbr0 转发给虚拟机网卡 vnet0虚拟机收到请求生成回应包虚拟机查自己的路由表认为 192.168.10.10/11 是同网段直接从 vnet0 回应回应包进入 vbr0宿主机内核找到对应的 ICMP 请求标记为 ping 通。再来看虚拟机 ping 宿主机丢包这条路径虚拟机 ping 192.168.10.10宿主物理内网口地址数据包从 vnet0 发出进入 vbr0宿主机内核收到请求包需要生成 ICMP reply内核查路由表目标 192.168.10.10 是本地地址对应接口是 enp1s0内核把应答包交给 enp1s0 发送enp1s0 处于 NO-CARRIER物理链路不通应答包发不出去虚拟机这边只能看到请求发出永远等不到回应表现为丢包。SSH 内网连接失败也是同一个道理。三次握手时主机发送的 SYN 能通过 vbr0 到达虚拟机虚拟机也会通过 vbr0 把 SYN-ACK 送回来但宿主机内核收到 SYN-ACK 后在处理和响应时涉及本地 IP 192.168.10.10 的相关逻辑回包又被错误地引向 enp1s0连接自然建立不起来。这个问题的本质就是内核路由选择把回包送进了一个物理链路死亡的接口而不是让回包从接收请求的 vbr0 原路返回。3.2 为什么公网 SSH 正常反而帮我们排除了大量可能很多人在排障时会忽视公网 SSH 正常这个信息觉得它只是说明服务没挂。但实际上它帮我排除了至少以下几类可能性虚拟机内部防火墙没挡 SSH如果虚拟机防火墙挡了来源公网来源也连不上sshd 配置只监听特定地址公网能连说明 sshd 至少监听在公网接口上宿主机到虚拟机的整体转发存在大问题公网 SSH 通常要经过宿主机的 NAT 端口映射能通说明宿主机的 FORWARD 链、NAT 规则、conntrack 状态基本正常虚拟机系统本身网络栈卡死公网 SSH 正常代表虚拟机可以正常收发外部流量。把这几类排除之后故障点就只剩宿主内网物理链路和内网回包路径这两个高度相关的环节。这种用反证法来缩小范围的思路在处理任何网络故障时都非常有用。记住一个原则通的那条路径是帮你排除干扰的不通的那条路径才是你要去挖的。3.3 容易误判的另一种可能rp_filter 在多网卡环境下的定向丢包这次虽然 rp_filter 不是根因但我强烈建议所有看到单向丢包现象的同行都认真检查一下这个参数。因为在双网卡、虚拟网桥、多路由并存的环境里rp_filter 造成的故障现象和这次几乎一模一样甚至连主机 ping 虚拟机通、虚拟机 ping 主机丢包的诡异组合都能复现。rp_filter 的原理是内核在收到一个数据包时会模拟性地查一下路由表看如果给这个包的源地址回一个包回包是否应该从当前这个收包接口出去。如果反向路径不是当前接口这个包就被判定为源地址伪装或者路由不对称直接丢掉。这个机制本意是防止 DDoS 和 IP 欺骗但在多网卡、多出口、策略路由的复杂环境下它经常误杀合法流量。验证方法很简单临时关闭所有接口的 rp_filtersysctl -w net.ipv4.conf.all.rp_filter0 sysctl -w net.ipv4.conf.default.rp_filter0然后立刻测试虚拟机 ping 宿主机。如果丢包消失说明就是 rp_filter 干的。注意要逐层检查因为某些发行版的配置里all.rp_filter1但某个具体接口的 rp_filter 可能被单独设成了 0反向也是。可以用sysctl -a | grep rp_filter把所有接口的值都看一遍。因为 rp_filter 和接口状态异常导致回包路径错误这两类问题最终表象高度相似所以我的建议是先做 rp_filter 快速排除再深入查路由和物理链路这样排障效率最高。4. 修复与验证从临时拉起网卡到开机自愈4.1 临时恢复确认物理链路后手动拉起网卡面对 NO-CARRIER 的网卡第一步不是改配置而是确认物理原因。我先检查了网线两端确认没有松动然后检查了对端交换机端口——因为断电后交换机可能比服务器启动得慢服务器网卡起来时还没检测到交换机的协商信号就进入了 NO-CARRIER 状态之后又没有重新协商。如果物理链路本身没问题通常手动把网卡 down 再 up 就能触发重新协商ip link set enp1s0 down ip link set enp1s0 up然后再看 ethtoolethtool enp1s0输出变为Link detected: yes后再测试从虚拟机 ping 宿主机丢包立即消失。但这里要强调手动 up 恢复只是治标如果不搞清楚为什么断电后网卡没有自动恢复下次照样会出同样的问题。尤其要注意有时候ip link set enp1s0 up后虽然 ethtool 显示 Link detected但内核路由缓存里还有旧的异常路由条目最好再执行一次ip route flush cache让路由重新生效。4.2 让网卡在断电后自动恢复从网络管理器配置下手这次问题的深层原因大概率是网络管理服务在开机时检测到 enp1s0 没有载波就把对应的连接标记为不可用没有继续自动连接。等交换机起来后网卡也没有主动重新触发协商。如果你用的是 NetworkManager 管理网络可以用以下命令查看和管理物理网卡的连接nmcli con show nmcli dev status找到内网物理网卡对应的 connection检查connection.autoconnect是否为 yesnmcli -f connection.autoconnect con show enp1s0-connection-name如果显示 no修改为 yesnmcli connection modify enp1s0-connection-name connection.autoconnect yes另外还需要注意connection.autoconnect-retries参数NetworkManager 默认会自动重试但有些环境会把它设成 0导致一次失败后就彻底放弃。手动改为一个正数可以保证网卡在交换机晚启动的情况下也能恢复nmcli connection modify enp1s0-connection-name connection.autoconnect-retries 5如果你用的是 systemd-networkd则要检查对应的.network文件里是否设置了[Link] RequiredForOnlineno并确保没有把ConfigureWithoutCarrier配成错误的值。systemd-networkd 在遇到 no carrier 时如果配置了RequiredForOnlineyes会把整个网络视为离线可能导致后续服务起不来。4.3 完整验证清单双向 ping、SSH、丢包率一次测完修复完成后不要只 ping 两下就完事建议按下面的清单完整验证一遍并且把结果留存下来宿主机 ping 虚拟机内网 IP连续 1000 个包确认 0% 丢包ping -c 1000 192.168.10.20虚拟机 ping 宿主机内网 IP连续 1000 个包确认 0% 丢包。这一步是复现原故障的直接验证必须通过ping -c 1000 192.168.10.10宿主机 SSH 虚拟机内网 IP确认能正常登录ssh user192.168.10.20公网 SSH 虚拟机确认没被修复动作破坏。再抓一次包确认应答包是从 vbr0 原路返回的而不是从 enp1s0 出去tcpdump -i vbr0 -n icmp and host 192.168.10.20 tcpdump -i enp1s0 -n icmp and host 192.168.10.20正常情况下enp1s0 上不应该再有任何 ICMP 应答流量vbr0 上能看到成对的 request/reply。这一步是整个修复验证里最有价值的它能从数据面确认回包路径已经恢复正常。5. 复盘笔记断电后半身不遂网络的通用排查套路5.1 这类故障的共性特征服务活着链路半死把这次故障和之前遇到过的几次同类问题放在一起看它们都有一个明显特征服务进程、虚拟网络、部分链路都活着但另一部分链路处于半死状态。单独看每个服务状态都是正常的拼在一起却无法通信。这类故障最容易坑人的地方在于重启服务器往往能暂时恢复但也把现场证据抹得一干二净。你还没来得及抓包判断根因重启后一切正常下次再断电又复现。所以我强烈建议遇到类似问题不要第一时间重启先按接口状态 → 路由表 → ARP → 抓包的顺序把证据收集齐。还有一个判断口诀可以分享公网通、内网断优先查内网物理链路去程通、回程断优先查路由选择和网卡状态。我这次问题两条都中了公网通内网断、去程通回程断。如果你也遇到类似组合基本不用犹豫直接聚焦到路由回程路径 物理链路状态这两个点上。5.2 断电后必查清单比盲目重启靠谱一百倍把这次排查用到的命令整理成了一张清单任何人都可以照着执行排查项命令异常信号网卡底层状态ip link showNO-CARRIER、state DOWN物理链路协商ethtool 接口Link detected: noIP 地址归属ip addr showIP 停留在异常接口上路由表ip route show table main回包路径指向死接口ARP 邻居表ip neigh showFAILED、INCOMPLETE防火墙规则iptables -nvL针对内网网段的 DROP/REJECT反向路径过滤sysctl -a | grep rp_filter多接口 rp_filter1连接跟踪状态conntrack -L大量异常 ESTABLISHED 条目网络服务日志journalctl -u NetworkManager网卡连接激活失败记录这个清单不一定每次全用得上但它能保证你在 10 分钟内把链路层、网络层、传输层、策略层全部覆盖一遍不会漏掉关键信息。其中前四项也就是接口状态、ethtool、IP 归属和路由表是本次排查中真正起决定作用的。5.3 四个预防动作让下次断电不这么狼狈最后总结几个预防措施。这些不是我拍脑袋想出来的而是这次踩坑之后实实在在补上的第一给交换机和服务器同一套 UPS。如果 UPS 只保服务器不保交换机服务器永远会比交换机先启动网卡协商失败的坑就会反复出现。至少要保证要么一起断电一起恢复要么交换机先于服务器恢复。第二检查所有物理网卡的 NetworkManagerautoconnect配置。尤其是那些用静态 IP 的内网网卡一定要确保connection.autoconnect yes并且autoconnect-retries是正数。有些运维喜欢把不需要自动连接的管理口设成 no断电后第一个跪的就是它。第三保存一份接口配置基线。把正常状态下ip addr、ip route、ethtool 接口、nmcli con show的输出存到一个文件里下次出问题了直接对比一分钟就能看出谁变了。我这次就是靠路由表里两条 /24 路由的双出口异常状态才快速确认问题的。第四上网卡服务加一个开机自检延迟。可以在 systemd 的 network-online.target 之后加一个小的 ping 测试脚本检测内网网关如果连续几秒不通会重新触发一次ip link set 接口 down/up。这个小脚本放在生产环境里可能一年都用不上一次但每次断电后它都能帮你省掉一次半夜去机房手动拉网卡的麻烦。最后再分享一个我个人的操作习惯处理这类网络故障时我习惯在服务器上起一个后台探测脚本同时 ping 宿主机和虚拟机两边把丢包率带时间戳记到文件里。这次如果没有这个日志我大概率也只会在 SSH 彻底连不上的时候才反应过来根本不知道虚拟机 ping 主机其实已经丢了一早上的包。脚本不复杂核心就是双向探测 时间戳 日志滚转但它能让你在故障发生后清楚地知道异常是从哪个时间点开始的、丢包率是缓慢恶化还是骤变。下次你遇到虚拟机单向丢包也不妨先建这么一份日志再开始逐层排查。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →