Linux 网络高级配置实战:Bonding、RAID 卡、故障诊断与虚拟交换机

Bonding、RAID、网桥其实是同一个抽象:用冗余换可用性,同时把"故障检测"挪进了软件。miimon 这类参数决定的不是会不会断,而是断多久。

技术文章约 26 分钟9 次阅读

一台服务器插着两根网线,你说"断一根没事"——这句话只有在你真的拔过一根并计时之后才成立。因为冗余的关键从来不在"有没有备用路径",而在主路径失效到备用路径接管之间的那段时间;而这段时间不是硬件给的,是软件参数给的。

这篇文章要解决的问题是:如何在 Linux 上把多块物理网卡聚合成一个逻辑接口(bond),再在这个聚合接口之上承载多个 VLAN 的 trunk 流量,并且在出故障时知道它断了多久、断在哪一层。背后最基础的事实是:Bonding 的聚合发生在内核二层(MAC 层)——它把多块物理网卡"接管"成一张虚拟网卡 net_device,链路层的故障检测、负载分担,以及 VLAN 标签的插入与剥离,全部在这一层完成;IP 层只看到这张虚拟网卡,对它而言"主备切换"和"两条物理链路"是不存在的。先把这个层次关系看清楚,后面所有配置、验证和排障才有坐标。

#原理层:bond 在内核里到底做了什么

#从一块网卡出发:数据包怎么走到 IP 层

先建立基准。一块普通物理网卡在内核里对应一个 struct net_device,它既代表硬件,也代表一层收发的边界。数据到达时,典型的路径是:

  1. 网卡 DMA 把帧写入 ring buffer,触发中断(或 NAPI 轮询);
  2. 驱动把缓冲区包成 sk_buff(skb),调用收包入口交给协议栈;
  3. 协议栈先做二层处理(目的 MAC 是否是本机、是否是广播/组播),再按 EtherType 分发——IPv4 交给 IP 层、ARP 交给 ARP 层、802.1Q 交给 VLAN 处理;
  4. IP 层按路由表决定这些包属于哪台主机、哪个会话,最终交给 TCP/UDP 或上层应用。

到这里会注意到:IP 层面对的单位是"一个 net_device",而不是"一根网线"。发送方向也是一样,IP 层选择出口设备,然后把这个设备交给链路层去完成封装。Bonding 利用的正是这个抽象边界:IP 层不关心出口设备背后是几块网卡,只要交给它的这个 net_device 能收发即可。

#bond 是一张"虚拟网卡",成员网卡被接管

Bonding 驱动做的事,可以概括为三步:

  • 注册一个新的 net_device(如 bond0),它没有自己的硬件,只有一组参数和状态;
  • 把物理网卡(slave,成员)"接管"进来:挂上 IFF_SLAVE 标志,把成员网卡的 MAC 统一成 bond 的 MAC(由第一个活跃成员继承或显式指定),此后成员网卡不再作为独立可寻址设备参与网络栈;
  • 在收、发两条路径上接管流量:接收时,成员网卡收到的帧统一走 bond 的接收处理(收包看到的是 bond0);发送时,bond 按所选模式调度的 bond_xmit 选择某一块成员网卡真正把帧发出去。

用一句话记:bond0 是 IP 层看到的"接口",成员网卡只是它背后的"出口集合"。 物理网卡 ⇄ bond0 ⇄ IP 层,这个层次是最重要的心智模型。

顺带一提,Linux 的 team(libteam/teamd)解决的是同一类问题,只是把调度策略搬到了用户态守护进程 teamd 里实现,内核只保留一个通用的 team 设备。概念上两者等价:都是"多个物理口聚合成一个逻辑接口"。

#为什么 bond 的成员网卡不能配 IP

这是一个常被误解的点,用上面的层次模型可以直接推导出来。

第一,成员网卡被接管后不再是独立设备。IFF_SLAVE 标志意味着内核把它从常规的 IP 收发路径中摘除,收包被移交 bond 处理,发包由 bond 统一调度。你在成员口上配 IP,等于对着一张"已经失去独立身份"的网卡做 IP 层配置——它本应是 bond 的端口,而不是主机的一个地址入口。

第二,MAC 地址已经统一。成员网卡 enslave 后,硬件地址被设成 bond 的 MAC(cat /proc/net/bonding/bond0 里能看到 Permanent HW addr 是出厂地址,而 bond 使用的是统一 MAC)。如果成员口再配一个独立 IP,就会出现"同一块物理口、同一个 MAC、却挂着两个地址语义"的混乱。二层交换机的 MAC 表、ARP 邻居表都将无所适从:同一个 MAC 应该从哪个口出去、应该应答哪个 IP,结果变成"时通时不通"。

第三,IP 层的路由与 ARP 语义要求"地址 ⇄ 出口设备"一一对应。Linux 在做源地址选择和 ARP 应答时都绑定到具体出口设备;同一个子网里出现 bond0 和 eno1 两个接口(且 eno1 是 bond 的成员),就触发了内核的多网卡同网段保护逻辑(arp_filter、arp_announce 等),最典型的现象是:ping 问 eno1 上的 IP,应答却从 bond0 发出去,或者干脆不应答。

所以实践结论是硬的:成员网卡不配 IP,IP 只配在 bond0 或 bond0 之上的 VLAN 子接口上。 发行版工具也按这个约定工作——NetworkManager 创建 slave 连接时默认 ipv4.method=disabled,旧式 ifcfg 里 SLAVE=yes 的接口即使写了 IPADDR 也会被忽略。不要手工往 slave 上 ip addr add,就算命令能执行,得到的也是不可预期的行为。

#三种常用模式:mode 0 / mode 1 / mode 4 各自的机制与依赖

Bonding 共 7 种模式(0–6),但本质上它们只在回答两个不同的问题:"我要断一根线不断网",还是"我要把多根线的带宽合并使用"。 这两种目标的机制和依赖完全不同,混在一起写需求,通常只会配出一个两边都不理想的东西。

模式名称机制需要交换机配合吗
0balance-rr逐包轮询在成员间轮流发送建议静态聚合,否则回程可能集中到单口并乱序
1active-backup主备,同一时刻只有一块成员转发不需要,两个口放同一 VLAN 即可
2balance-xor按 XOR 哈希选成员发送建议静态聚合或 LACP
3broadcast所有成员同时发送同一帧通常不需要
4802.3ad与交换机 LACP 动态协商聚合必须,两端都要配 LACP
5balance-tlb发送侧自适应负载均衡(按成员负载调整)不需要
6balance-albtlb + 接收侧 ARP 协商负载均衡不需要

下面把最常用的 0、1、4 讲透。

mode 1(active-backup)——只要冗余。 机制是:同一时刻只有一块成员网卡(active slave)承担收发,其余成员 standby;当 active 链路被判定故障,bond 立即把收发切换到下一块。它不需要交换机做任何聚合配置(交换机只看到一块"网卡"在活动),所以是所有模式里最容易验证、最不容易被交换机坑到的。它可以配 primary 指定首选成员,例如让 eno1 优先,eno1 恢复后自动切回。故障检测有两套手段:miimon(监测链路载波/状态,快,简单)和 arp_interval + arp_ip_target(定期向网关等目标发 ARP 探测,用于"链路 up 但网络不通"的场景,例如中间接了交换机且对端故障),两者互斥,不能同时开。

mode 4(802.3ad,LACP)——要带宽叠加,但要付出配置代价。 机制是:bond 与交换机通过 LACP(IEEE 802.3ad/802.1AX)协议协商,动态确认两端都认可同一个聚合组后,成员口才真正进入转发。两端各有一个"actor"和一个"partner":Linux 是 actor,交换机是 partner,协商成功会形成聚合组(Aggregator),交换机会按组的哈希把回程帧分散到各成员口。代价有两条,必须提前接受:

  1. 交换机侧必须预先配置 LACP。 一边配了、一边没配,会出现很难查的连通性异常——因为两端对"这是个聚合组"的理解不一致(详见下文"半边通")。
  2. 带宽叠加不等于单连接变快。 802.3ad 和 balance-xor 一样,按哈希算法逐流分配,单个 TCP 连接只会走一条成员链路(xmit_hash_policy 决定哈希因子:layer2 用 MAC、layer2+3 加 IP、layer3+4 再加端口)。想靠 Bonding 让一个 SCP 下载变快,基本不会成功;能变快的是"很多个并发连接加起来的总吞吐"。

mode 0(balance-rr)——特例。 它逐包轮流发送,同一 TCP 流的包也会分散到不同链路,属于"发送侧真·叠加";但接收侧由交换机决定回程走哪个口,而交换机又把同一目的 MAC 的帧大都发到同一个口上,于是极易乱序,TCP 会因乱序触发大量重传,实测经常比单口还慢。它适用于极少数对吞吐要求苛刻、且流量本身可容忍乱序的场景(或配合对端做严格静态聚合时谨慎使用)。绝大多数生产环境,可靠性选 mode 1,带宽选 mode 4,mode 0 放在"知道它为什么慢"的前提下再考虑。

#切换速度由参数决定:miimon / updelay / downdelay / arp_interval

这是全篇最实用的一节。miimon、updelay、downdelay 这三个参数决定的不是"会不会切换",而是切换的速度与稳定性:

miimon=100     每 100ms 检查一次链路(默认量级)
updelay=...    成员恢复后,等多久才启用它
downdelay=...  链路异常后,等多久才判它坏了
arp_interval=1000  每 1000ms 发一次 ARP 探测(与 miimon 互斥)
arp_ip_target=192.0.2.1  探测的目标地址,可多个

它们之间存在一个必须理解的取舍:

  • miimon 太小 → 链路抖动(比如交换机做 STP 收敛、光模块瞬断)会被判成故障,切换过于频繁,反而制造中断;
  • miimon 太大 → 真故障时切换慢,中断时间变长;
  • updelay 太短 → 成员刚"看起来好了"就被启用,可能马上又坏,形成来回抖动(flapping)。

所以判断标准不是"哪个值更安全",而是:这台机器能容忍多长的中断,和它的链路有多爱抖。 对波动大的链路,宁可切换慢一点也别来回跳。miimon 是个参数,但选它是选择"更怕中断还是更怕抖动"——这正是工程选择与价值判断混在一起的典型例子。

还有一个最容易被忽略的观测点,来自 /proc 状态文件:

# 看当前 bond 状态(哪块是 active、切换统计)
cat /proc/net/bonding/bond0

关键几行:

Currently Active Slave: eno1
MII Status: up
Link Failure Count: 2        # 切换过几次,静默发生的故障在这里

Link Failure Count 是一本"沉默的故障账本"。如果你的告警从来没有触发过,但这个计数却在涨,说明发生过短暂中断且没人知道。巡检查看它是很有价值的习惯。

#VLAN trunk:tag 是怎么打到 bond 之上的

VLAN 的机制是 802.1Q:在以太网帧的源 MAC 之后插入 4 字节标签,其中 12 bit 是 VLAN ID(VID),交换机用这个标签区分同一物理链路上的多个虚拟网络。所谓 trunk 口,就是允许带标签帧通过、并在出入口按需打标/去标的端口。

关键问题:在 bond 上做 trunk,标签该加在哪一层?

答案是加在 bond0 之上,而不是成员网卡上。层次关系是:

物理口 eno1 ─┐
              ├─ bond0(二层聚合,无标签逻辑)── bond0.10(VID 10,IP 入口)
物理口 eno2 ─┘                                       ├─ bond0.20(VID 20,IP 入口)
                                                      └─ bond0(本体,可理解为 untagged/native)

802.1Q 的 VLAN 子接口(bond0.10 这种)是 bond0 的上层虚拟设备:内核加载 8021q 模块后,ip link add link bond0 name bond0.10 type vlan id 10 会创建一个 net_device,它的收包路径负责剥离 VID 10 的标签,发包路径负责给帧打上 VID 10 的标签,其余逻辑(成员选择、主备切换)全部由下层 bond0 完成。所以:

  • 先有 bond,再有 VLAN:聚合是底层的,tag 是上层的;
  • IP 只配在 bond0.10 / bond0.20 上(或配在 bond0 本体作为 native VLAN 的管理地址),成员网卡永远只承载"聚合后的裸二层帧";
  • 一条物理聚合链路(两根线)因此可以同时承载多个 VLAN 的流量,这就是"bond 之上的 trunk"。

理解了这条因果链,配置就只是把上面的层次关系逐层落下来。

#操作层:完整可复现的配置

#前置条件与目标拓扑

下面以最常见的两种发行版家族为例:

  • RHEL 系(RHEL 8/9、Rocky Linux、AlmaLinux、Fedora):以 NetworkManager + nmcli 为主要方案,先确认工具存在(发行版未装时 dnf install NetworkManager 并 systemctl enable --now NetworkManager)。
  • Debian 系(Debian 11/12、Ubuntu 20.04/22.04/24.04):以 ifupdown(/etc/network/interfaces)为例;使用 bond 需要 ifenslave 包,使用 VLAN 需要 vlan 包(或直接使用 ifupdown2)。
  • 若用传统 ifcfg 方式(RHEL 8 及更早),单独给出一节;RHEL 9 已移除 network-scripts(ifcfg),只支持 NetworkManager,这一点请先确认再选方案。

目标拓扑(示例,全部使用 RFC 5737 文档地址,请替换为你的实际网段):

  • 两块物理网卡 eno1、eno2(名字以 ip link 实际输出为准);
  • 聚合为 bond0,模式按需求选 active-backup(1)或 802.3ad(4);
  • bond0 本体配管理地址 192.0.2.10/24(native,交换机放行 untagged);
  • bond0.10 配 198.51.100.5/24、bond0.20 配 203.0.113.5/24(tagged,交换机 trunk 放行 VID 10、20);
  • 交换机把接 eno1/eno2 的两个口做成聚合口,mode 4 时启用 LACP,并把聚合口配为 trunk。

#方案 A:RHEL 系 + NetworkManager(nmcli)

在 NetworkManager 里,bond 与 slave 都是独立的连接(connection),用 master 关联。一步步来。

1) 确认环境与网卡名

nmcli -v
ip link show
ip -br link

2) 创建 bond(以 802.3ad / mode 4 + layer3+4 哈希为例)

nmcli con add type bond con-name bond0 ifname bond0 mode 802.3ad miimon 100
nmcli con mod bond0 +bond.options "xmit_hash_policy=layer3+4,updelay=0,downdelay=0"
nmcli con add type ethernet con-name bond0-port1 ifname eno1 master bond0
nmcli con add type ethernet con-name bond0-port2 ifname eno2 master bond0
nmcli con up bond0

说明:con-name 是 NetworkManager 连接名,ifname 是内核设备名;slave 连接用 master bond0 挂接。此时 bond0 还没有 IP。

3) 给 bond0 配 IP(native/管理地址)

nmcli con mod bond0 ipv4.addresses 192.0.2.10/24
nmcli con mod bond0 ipv4.gateway 192.0.2.1
nmcli con mod bond0 ipv4.method manual
nmcli con up bond0

4) 在 bond 上创建 VLAN 子接口并配 IP

nmcli con add type vlan con-name bond0.10 ifname bond0.10 dev bond0 id 10
nmcli con mod bond0.10 ipv4.addresses 198.51.100.5/24
nmcli con mod bond0.10 ipv4.method manual
nmcli con up bond0.10

nmcli con add type vlan con-name bond0.20 ifname bond0.20 dev bond0 id 20
nmcli con mod bond0.20 ipv4.addresses 203.0.113.5/24
nmcli con mod bond0.20 ipv4.method manual
nmcli con up bond0.20

5) 只要冗余、不要带宽:把模式改成 active-backup(mode 1)

上面的流程只需要把第一步的 mode 802.3ad 换成 mode active-backup,其余不变:

nmcli con delete bond0 bond0-port1 bond0-port2   # 若已按 mode 4 创建过,先清掉重来
nmcli con add type bond con-name bond0 ifname bond0 mode active-backup miimon 100
nmcli con add type ethernet con-name bond0-port1 ifname eno1 master bond0
nmcli con add type ethernet con-name bond0-port2 ifname eno2 master bond0
nmcli con mod bond0 ipv4.addresses 192.0.2.10/24
nmcli con mod bond0 ipv4.method manual
nmcli con up bond0 bond0-port1 bond0-port2

mode 1 也可以指定首选成员和 ARP 探测(注意与 miimon 互斥):

nmcli con mod bond0 +bond.options "primary=eno1"
# 若改用 ARP 探测(同时去掉 miimon):
nmcli con mod bond0 bond.options "mode=active-backup,primary=eno1,arp_interval=1000,arp_ip_target=192.0.2.1"
nmcli con down bond0 && nmcli con up bond0

6) 清理

删除连接即删除设备:

nmcli con delete bond0.20 bond0.10 bond0-port2 bond0-port1 bond0

#方案 B:Debian / Ubuntu(/etc/network/interfaces)

先装依赖包:

apt-get update && apt-get install -y ifenslave vlan
modprobe bonding
modprobe 8021q
grep -q bonding /etc/modules || echo bonding >> /etc/modules
grep -q 8021q /etc/modules || echo 8021q >> /etc/modules

编辑 /etc/network/interfaces(以 mode 4 + 一个 VLAN 子接口为例):

# 成员网卡:只声明属于 bond0,不配 IP
auto eno1
iface eno1 inet manual
    bond-master bond0

auto eno2
iface eno2 inet manual
    bond-master bond0

# bond 本体:native(untagged)管理地址
auto bond0
iface bond0 inet static
    address 192.0.2.10
    netmask 255.255.255.0
    gateway 192.0.2.1
    bond-slaves eno1 eno2
    bond-mode 802.3ad
    bond-miimon 100
    bond-xmit-hash-policy layer3+4
    bond-updelay 0
    bond-downdelay 0

# VLAN 子接口:VID 10,tagged
auto bond0.10
iface bond0.10 inet static
    address 198.51.100.5
    netmask 255.255.255.0
    vlan-raw-device bond0

说明:bond-mode 也可写数字(bond-mode 4);mode 1 时写 bond-mode active-backup 并去掉 bond-xmit-hash-policy。ifupdown 会自动按依赖顺序先起 bond0 再起 bond0.10。如果使用 netplan(Ubuntu 22.04+ 部分环境),等价的声明是给两块物理网卡配 bond 引用、给 bond0 配 addresses,再在其上声明 vlan-id——这里以 ifupdown 为准,netplan 请按你安装版本的参考文档核对。

应用配置:

systemctl restart networking    # 或 ifdown -a && ifup -a

#方案 C:RHEL 系传统 ifcfg(RHEL 8 及更早)

RHEL 8 及更早年份的网络脚本(network-scripts)用 ifcfg 文件描述接口。先确认在跑传统网络服务而非 NetworkManager,然后写四个文件:

/etc/sysconfig/network-scripts/ifcfg-bond0:

DEVICE=bond0
NAME=bond0
TYPE=Bond
BONDING_MASTER=yes
ONBOOT=yes
BOOTPROTO=none
IPADDR=192.0.2.10
PREFIX=24
GATEWAY=192.0.2.1
BONDING_OPTS="mode=802.3ad miimon=100 xmit_hash_policy=layer3+4"

/etc/sysconfig/network-scripts/ifcfg-eno1:

DEVICE=eno1
NAME=eno1
TYPE=Ethernet
ONBOOT=yes
BOOTPROTO=none
MASTER=bond0
SLAVE=yes

/etc/sysconfig/network-scripts/ifcfg-eno2:同上,仅把 DEVICE/NAME 换成 eno2。

/etc/sysconfig/network-scripts/ifcfg-bond0.10:

DEVICE=bond0.10
NAME=bond0.10
VLAN=yes
ONBOOT=yes
BOOTPROTO=none
IPADDR=198.51.100.5
PREFIX=24

应用:

systemctl restart network

重申:RHEL 9 已经移除 network-scripts,此方案只适用于 RHEL 8 及更早;在这些系统上,也需要先 modprobe bonding 确认模块可用。

#交换机侧的配合

Linux 侧配完只是半边,交换机侧必须与 bond 模式对齐。分三种情况:

mode 1(active-backup): 交换机不需要任何聚合配置,只要让两个口都 access 到同一个 VLAN 即可。这是因为任一时刻只有一个成员在转发,交换机只会从其中一个口学到 MAC,不存在"两个口同时转发同一 MAC"的问题。

mode 4(802.3ad): 必须预先在两个口上建立 LACP 聚合口,且 trunk 配置(放行哪些 VLAN)要配在聚合口上,而不是分别配在每个物理口上。以 Cisco IOS 为例:

interface Port-channel1
 switchport mode trunk
 switchport trunk allowed vlan add 10,20
!
interface GigabitEthernet0/1
 channel-group 1 mode active
!
interface GigabitEthernet0/2
 channel-group 1 mode active

以华为 VRP 系交换机(S 系列/CE 系列命令行风格)为例:

interface Eth-Trunk1
 mode lacp-static
 port link-type trunk
 port trunk allow-pass vlan 10 20
#
interface GigabitEthernet0/0/1
 eth-trunk 1
#
interface GigabitEthernet0/0/2
 eth-trunk 1

(具体语法以你设备版本的官方文档为准;关键语义是:LACP 模式对应 Linux 的 802.3ad,聚合口上放行带 tag 的 VLAN。)

mode 0/2(balance-rr / balance-xor,不协商 LACP): 为避免回程集中在单口导致性能与乱序问题,可以把交换机两个口做成静态聚合(Cisco channel-group 1 mode on;华为 Eth-Trunk 默认手工模式),此时两端不做 LACP 协商也能聚合转发,但链路状态变化时交换机的收敛依赖静态配置,不会自动重协商。

#team 作为另一种实现

如果并非必须用 bonding,RHEL 7/8 时代曾推荐 team(libteam/teamd)替代 bonding,原因是其 JSON 配置更易理解、含 lacp 等 runner。一张对照表:

需求bonding 模式team runner
主备1(active-backup)activebackup
轮询0(balance-rr)roundrobin
负载均衡(哈希)2(balance-xor)loadbalance
LACP4(802.3ad)lacp

NetworkManager 下创建 team 的等价命令(以 activebackup 为例):

nmcli con add type team con-name team0 ifname team0 config '{"runner":{"name":"activebackup"}}'
nmcli con add type team-slave con-name team0-port1 ifname eno1 master team0
nmcli con add type team-slave con-name team0-port2 ifname eno2 master team0
nmcli con up team0

查看 team 状态用 teamdctl team0 state view。但要注意:RHEL 9 已弃用 team,官方推荐回归 bonding;Debian 系仍可用 libteam。如果不是存量环境,新部署选择 bonding 更稳妥。

#验证层:怎么确认我做对了

配置完成后,必须用证据确认"聚合真的生效、tag 真的打对、切换真的够快",而不是看几行配置就宣布成功。

#cat /proc/net/bonding/bond0:读懂状态与切换账本

这是 bond 状态的第一手来源。mode 1 的典型输出节选:

Ethernet Channel Bonding Driver: v6.x

Bonding Mode: fault-tolerance (active-backup)
Primary Slave: None
Currently Active Slave: eno1
MII Status: up
MII Polling Interval (ms): 100
Up Delay (ms): 0
Down Delay (ms): 0

Slave Interface: eno1
MII Status: up
Speed: 1000 Mbps
Duplex: full
Link Failure Count: 0
Permanent HW addr: 52:54:00:aa:bb:01

Slave Interface: eno2
MII Status: up
Speed: 1000 Mbps
Duplex: full
Link Failure Count: 1
Permanent HW addr: 52:54:00:aa:bb:02

mode 4 的典型输出节选(802.3ad 段):

Bonding Mode: IEEE 802.3ad Dynamic link aggregation
Transmit Hash Policy: layer3+4 (1)
MII Status: up
MII Polling Interval (ms): 100

802.3ad info
LACP rate: slow
Aggregator selection policy (ad_select): stable
System priority: 65535
System MAC address: 52:54:00:aa:bb:01
Active Aggregator Info:
    Aggregator ID: 1
    Number of ports: 2
    Actor Key: 9
    Partner Key: 1
    Partner Mac Address: 00:1c:73:xx:xx:xx

Slave Interface: eno1
...
Aggregator ID: 1

判定标准:

  • Bonding Mode 必须是你配置的模式;
  • mode 1:Currently Active Slave 只有一块,切换后它会变;
  • mode 4:Aggregator ID 两块成员一致且非 0、Number of ports: 2、Partner Mac Address 是交换机端口的 MAC(不是空/全 0)——这三条同时成立,才说明 LACP 协商成功;
  • Link Failure Count 是故障账本,见上文。

用 ip -d(detail)可以直接看到设备的类型细节:

ip -d link show bond0
ip -d link show bond0.10

典型输出:

6: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:aa:bb:01 brd ff:ff:ff:ff:ff:ff
    bond mode 802.3ad miimon 100ms xmit_hash_policy layer3+4
    ...

7: bond0.10@bond0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:aa:bb:01 brd ff:ff:ff:ff:ff:ff
    vlan protocol 802.1Q id 10 <REORDER_HDR>
    ...

注意三点:bond0 显示 MASTER、成员网卡显示 SLAVE;bond0.10@bond0 的 @bond0 表示它的下层设备是 bond;vlan protocol 802.1Q id 10 确认标签类型和 VID。

VLAN 收发计数在 /proc/net/vlan/ 下:

cat /proc/net/vlan/bond0.10

它会显示 VID、以及收到的带标签帧总数与发送总数——发数在增长、收数在增长,说明 tag 在这条链路上真的来回走。

#ethtool 与计数器:诊断顺序从证据开始

网络诊断最常见的错误是从"猜"开始(换网线、换交换机端口),而不是从可观测证据开始。推荐的顺序是:

# 1) 物理层与协商(看起来 up 不等于协商正常)
ethtool eno1 | grep -E "Speed|Duplex|Link detected"

# 2) 错误计数器 —— 最直接的证据
ethtool -S eno1 | grep -Ei "err|drop|discard|crc|fifo"

# 3) 网卡与驱动
ethtool -i eno1        # 驱动与固件版本
dmesg | grep -i eno1   # 有没有 link down / reset 记录

# 4) 上层:路由、连接、抓包
ip -s link show eno1   # 内核视角的收发统计与错误
ss -s                  # 连接状态汇总

计数器的解读是这块最值钱的东西:crc_errors 涨说明物理层有问题(线、模块、端口);rx_missed / rx_dropped 涨更像主机侧处理不过来(ring buffer、中断、CPU);tx_dropped 可能指向队列或虚拟化层。计数器把"网络慢"分成了几个完全不同的方向,而猜是分不出来的。 配合 dmesg 里的 link down / link up 记录与 bond 的 Link Failure Count,往往能还原出"什么时候抖过"——这个时间线对判断故障是偶发还是趋势非常关键。

#连通性与故障演练:拔掉一根线,计时

配置验证的最终一步是主动制造故障,而不是等故障发生。

# 后台持续 ping 网关,统计丢包
ping -f 192.0.2.1 &
# 拔掉 eno1 的网线(或 ip link set eno1 down),观察:
#   ping 丢了几个包?中断了多少毫秒?
cat /proc/net/bonding/bond0 | grep -A2 "Slave Interface: eno1"
cat /proc/net/bonding/bond0 | grep "Currently Active Slave"
# 插回网线,观察切换恢复与 updelay 的作用

预期:mode 1 下,ip link set eno1 down 后 100ms 量级(miimon 决定)内 Currently Active Slave 切到 eno2,ping 丢包通常只有 0–2 个;Link Failure Count 对 eno1 加 1。如果丢包远超预期,去查 miimon 是否过大、交换机端口是否也做了聚合或 STP 收敛期过长。mode 4 下还要在交换机侧确认聚合口没有 down/重协商。

如果需要确认帧真的带了 tag,抓包看二层头:

tcpdump -i bond0.10 -nn -e icmp
# 报文里应看到 vlan 10(802.1Q, QOS 0x0, vlan 10)才能进入该子接口

注意:抓 bond0.10 时看到的是剥离后的帧;要看原始带 tag 帧,抓 bond0 本体:tcpdump -i bond0 -nn -e 'vlan 10'。

#边界与坑

#半边通:两端口都 up 但流量不正常

Bonding 出问题最麻烦的形态是"半边通":两张网卡都 up,但流量只走其中一张,或者某些目标通、某些不通。 典型原因有三个:

  1. LACP 静默模式不一致:一边期望协商、一边没配,最终可能只有一个成员在转发(/proc/net/bonding/bond0 里能看到成员没被聚合)。
  2. 哈希不一致:两端对流的哈希结果不同,导致回程走了另一条链路;中间再有有状态设备(如防火墙),直接不通。
  3. ARP/邻居表问题:切换后对端交换机仍缓存着旧端口的 MAC 表项(MAC 表项老化需要时间),表现为"切了但几分钟内不通"。

第三条常被忽略:网络设备的转发表是有记忆的。 二层的收敛时间不只看服务器侧,也要看交换机侧的老化时间。这也是为什么"同一网段切换很快、跨网段切换很慢"有时是真的——中间的设备越多,需要重新学习的表就越多。排障时把交换机侧的状态也纳入检查,不要只盯 Linux。

#负载均衡的边界:单流带宽不叠加

再次强调:802.3ad / balance-xor 按流哈希,单个 TCP 连接只走一条成员链路;想靠 Bonding 让一个 SCP 变快,基本不会成功。balance-rr 是例外(包级轮询),但乱序代价往往更糟。真要让大文件传输变快,思路应是"把传输拆成多个并发连接",或换用支持多路径的上层技术——bond 解决的是链路级可用性与多连接总吞吐,不解决单流带宽。

#链路抖动与参数取舍

miimon 太小会把交换机 STP 收敛期的短暂丢帧误判成链路故障,触发无谓切换;updelay 太短会让刚恢复的成员立刻被启用,形成 flapping。这类问题的典型语境是机房链路本身波动大:宁可把 miimon 放大(如 200–500ms)也别让它来回跳。这是个工程判断:这台机器更怕中断,还是更怕抖动。

#RAID 的同一个道理:沉默的降级与写缓存

RAID 和 Bonding 的相似性比看起来高,值得放在一起理解:

相似点BondingRAID
冗余对象链路磁盘
降级状态单链路运行降级(degraded)运行
重建是瓶颈不太涉及重建期是性能与风险双高期
缓存策略改变语义少见写缓存(Write Back)与丢电

最后一行是 RAID 独有的要害:开启写缓存能大幅提升写性能,但如果没有电池/超级电容保护(BBU/超级电容),掉电时缓存中的数据会丢。 这不是"风险有多大"的问题,而是"你在性能和一致性之间选了哪一边"的问题——而且很多 RAID 卡的默认策略并不保守,需要显式检查。与 Bonding 呼应的一点是:RAID 卡也会"沉默地降级",阵列从 RAID 10 掉一块盘之后通常还能跑,性能与冗余度却都下降了。状态要主动巡检,不要等它坏了再发现——那时已经晚了。Link Failure Count 与 RAID 告警日志,是同一类"沉默账本"。

#虚拟交换机的两套思维与 offload

Linux Bridge 和 Open vSwitch 都是"交换机",但思维不同:

Linux BridgeOpen vSwitch
定位内核自带的二层转发可编程的虚拟交换
配置方式ip / bridge 命令,持久化靠发行版数据库(ovsdb)+ 命令行
VLAN 处理支持,但表达力有限显式 tag/trunk 概念,更接近物理交换机
流表与 ACL基本靠 iptables/ebtables 配合原生流表,可做隧道与精细策略
适合简单网桥、KVM 默认需要多租户、隧道、策略的场景

与本文主题相关的坑是:虚拟交换机的性能和"卸载"能力与物理设备不同。 某些 offload 特性(如大包分段、校验和卸载)在经过虚拟交换机后行为会变化,抓包看到的和实际发出的可能不一致(经典的"抓到的包看着不对但通信正常")。抓包不一致时先怀疑卸载,而不是先怀疑网卡坏了。

#发行版差异与工具风险

几点需要明确限定的边界:

  • 发行版差异极大:RHEL 系、Debian 系、NetworkManager 与传统网络脚本的配置表达不同;RHEL 9 移除 network-scripts、弃用 team。抄一份模板到处用的误导性大于帮助,先确认发行版版本再选方案。
  • RAID 卡厂商工具的风险:storcli / perccli / MegaCli 版本语法差异明显,且用它们改配置是有风险的操作,请以对应工具文档为准,本文不展开。
  • 性能调优不脱离场景:中断绑定、RPS/RFS、ring buffer 大小都有效,但全部依赖具体负载与硬件,脱离场景给数值等于让读者替作者试错。
  • 再往上一层是 Kubernetes 网络:CNI 有自己的抽象(比如 Bonding 之上的多网卡、IPVLAN 等),但底层仍在用本文的概念——理解二层聚合与 VLAN 的层次,是理解容器网络的前提。

#收尾

冗余的价值不取决于"有备用",而取决于"多快发现主用坏了"。那个"多快"是可配的(miimon、updelay、downdelay、LACP 协商),也是该被计时验证的——配完 bond 和 trunk 之后,顺手拔一根网线,测一次。如果 Link Failure Count 从此以后安静地躺着不动,那说明过去的一段日子网络一直很健康;如果它在某天悄悄涨了一格,那段没人知道的短暂中断,正是这篇配置留下并被记录下来的意义。

相关阅读

Related

留言讨论

0 条

还没有留言,来说点什么吧。

Linux 网络高级配置实战:Bonding、RAID 卡、故障诊断与虚拟交换机 · LXH·BLOG