Skip to content

双网卡 Bond 容灾

01 前置理论与核心机制

1. 物理层面的单点隐患

  • 物理链路脆弱性:生产环境中,若服务器仅连接单根网线,一旦上游交换机端口发生硬件故障、光模块损坏或机房维护时误触网线,服务器将立即发生网络中断。为消除物理链路层的单点故障隐患,生产物理机通常标配双网卡(分别接入不同交换机)并配置 Bond(网卡绑定)。
  • Bond 的底层本质:利用 Linux 内核的 bonding 驱动,将多块物理网卡(如 ens38、ens39)在数据链路层虚拟合并为一张逻辑网卡(如 bond0)。对外仅暴露 bond0 上的单一业务 IP,底层物理网卡发生主备切换时,上层应用连接完全无感知。

2. Bond 的主流工作模式 (Mode)

  • active-backup (主备模式 / Mode 1):生产环境最常用的高可用模式。正常状态下仅有一块主网卡(Active)承载流量,备用网卡(Backup)处于待命状态。当主网卡链路断开时,内核会在毫秒级时间内将流量无缝切换至备用网卡。该模式的优势在于无需上游交换机配置任何特殊聚合协议,支持跨交换机冗余连接。
  • balance-rr (轮询模式 / Mode 0):按数据包顺序在多块网卡间轮流发送,虽能叠加带宽,但易引发 TCP 数据包乱序导致重传率上升。
  • 802.3ad (LACP 动态链路聚合 / Mode 4):兼顾带宽叠加与故障容错,将多条物理链路聚合为一条高带宽逻辑链路。但严格要求上游交换机支持并同步配置 LACP 协议,否则链路协商将失败。

02 故障排查与恢复闭环 SOP

在 Ubuntu 等采用 Netplan 管理网络的 Linux 发行版中,按以下标准步骤完成双网卡主备绑定与容灾切换验证:

步骤一:识别待绑定的物理网卡状态

查看操作系统当前识别的底层物理网卡名称与链路状态:

ip link show
  • 观测点:确认待绑定的两块物理网卡(如 ens38 和 ens39)已被系统正常识别,且具备物理载波信号(或处于待激活状态)。

步骤二:编写 Netplan 声明式配置文件

创建或编辑 Netplan 配置文件,将两块物理网卡纳入逻辑网卡 bond0 中,并在 bond0 上配置业务 IP 与路由:

       no

       no

         ens38
         ens39

         192.168.0.189/24
       192.168.0.1

         activebackup
         ens38

步骤三:收敛文件权限与安全加载 (防失联核心操作)

配置文件编写完成后,必须收敛文件读写权限,否则新版 Netplan 会输出安全警告或拒绝加载:

# 将配置文件权限限制为仅 root 可读写
chmod 600 /etc/netplan/99-bond-config.yaml
远程操作安全规范:在生产环境通过 SSH 远程修改网络配置时,切勿直接执行 netplan apply。
# 标准操作:带自动回滚机制的配置加载
netplan try

  • 自动回滚机制:执行 netplan try 后,系统会应用新网络配置并启动 120 秒倒计时。若因 YAML 配置错误(如网关写错或子网掩码异常)导致远程 SSH 连接中断,管理员将无法按下回车键确认;倒计时结束后,Netplan 会自动回滚至修改前的正常网络配置,规避远程服务器彻底失联的风险。验证网络连通正常后,按 ENTER 键确认即可永久生效。

步骤四:链路故障模拟与切换验证

配置生效后,开启两个终端窗口验证主备切换功能是否正常: 终端一 (客户端):从同网段另一台机器持续 Ping 该服务器的业务 IP:

ping 192.168.0.189

终端二 (服务端):实时观察内核 bond0 的底层状态文件:

watch -n 1 cat /proc/net/bonding/bond0

观测点:确认 Currently Active Slave 当前显示为主网卡 ens38。

模拟物理链路中断:在服务端人为关闭主网卡接口:

ip link set ens38 down

切换效果确认:

  • 服务端状态:Currently Active Slave 在 100 毫秒内自动切换为备用网卡 ens39。
  • 客户端连通性:Ping 测试持续正常响应,未发生连接中断,验证主备容灾机制生效。测试完毕后执行 ip link set ens38 up 恢复原网卡状态。

03 真实生产故障现象与案例库

案例 1:单网卡物理连线松动引发核心节点离线

  • 故障现象:某承载核心缓存业务的物理服务器突发网络中断,导致上游微服务大面积报连接超时错误。
  • 排查与处置:带外管理(IPMI)登录查看系统运行正常,但业务网卡显示 NO-CARRIER。机房现场排查发现,机柜理线维护时误触导致该服务器唯一的网线接口松动。重新插紧网线后业务恢复。
  • 复盘整改:核心物理节点严禁采用单网线接入。事后对机房所有单网卡物理服务器补充铺设第二条物理链路(接入机柜内另一台堆叠/上联交换机),统一改造为 active-backup 双网卡绑定架构。

案例 2:远程直接执行 netplan apply 导致服务器失联

  • 故障现象:运维人员远程调整生产服务器 IP 与路由配置,直接执行 netplan apply 后 SSH 终端立即卡死,服务器从内网彻底失联。
  • 排查与处置:配置 YAML 时误将子网掩码 /24 写成了 /32,导致网关不可达。由于直接使用了 apply 而非 try,系统无法自动回滚,最终只能通过机房 KVM/IPMI 控制台登录本地终端手动修正配置。事后将 netplan try 写入网络变更强制操作规范。

案例 3:未协同交换机配置盲目启用 802.3ad (LACP) 导致断网

  • 故障现象:为提升网络吞吐量,运维人员单方面在服务器 Netplan 中将 Bond 模式由 active-backup 修改为 802.3ad,应用配置后服务器网络完全中断。
  • 根因与处置:802.3ad(LACP)属于动态链路聚合协议,要求服务器两端连接的交换机端口必须同步建立 Port-Channel / Eth-Trunk 聚合组并开启 LACP 协商。服务器单方面开启会导致链路层协商失败而无法转发报文。在不具备交换机改造条件或跨独立交换机接入时,应统一使用对交换机透明的 active-backup 模式。

04 生产排障速查表(网络高可用与 Bond)

操作环节执行命令 / 观测点预期现象与目的操作注意事项01 设备识别ip link show确认待绑定物理网卡名称 (如 ens38/39)核对网线已插好且物理层有载波信号02 权限收敛chmod 600 收紧配置文件读写权限避免 Netplan 因文件权限过宽产生安全告警03 安全加载netplan try开启 120 秒超时自动回滚保护远程修改网络配置的标准操作方式04 状态观测cat /proc/net/bonding/bond0查看当前工作模式、主用卡与 MII 状态确认两块子网卡的 MII Status 均为 up05 故障演练ip link set ens38 down模拟主链路断开,触发 MII 监控切换验证流量是否无缝切换至备用网卡06 链路恢复ip link set ens38 up恢复原物理网卡状态根据抢占策略恢复主备状态,确保冗余完好

05 面试应答参考话术

面试官问:“物理服务器如果只插一根网线存在单点故障风险。如果让你在新机器上配置双网卡做高可用,你会怎么操作?有哪些需要注意的细节?”

可以这样回答:为了消除物理网络层的单点故障,我会在操作系统层面将两块物理网卡绑定为一个逻辑网卡(Bond),并分别连接到机柜内两台不同的交换机上。在模式选择上,针对高可用场景我通常会选用 active-backup(Mode 1 主备模式),平时主卡承载流量,备卡待命。这种模式最大的优势是对上游交换机完全透明,不需要网络团队配合修改交换机配置就能直接实现毫秒级容灾切换。在 Ubuntu 等系统下使用 Netplan 落地配置时,有三个关键细节需要严格把控:第一是配置文件的权限收敛。

编写完 /etc/netplan/ 下的 YAML 文件后,需要执行 chmod 600 收紧文件权限,避免新版 Netplan 触发权限过宽的安全限制。第二是远程操作必须使用 netplan try。

只要是通过 SSH 远程修改网络配置,绝对不能直接执行 netplan apply。使用 netplan try 会在应用新配置的同时启动 120 秒倒计时。如果因为掩码或网关写错导致网络中断,只要不按回车确认,倒计时结束后系统会自动回滚到原来的正常配置,彻底避免服务器断网失联。第三是上线前必须执行故障切换演练。

配置生效后,我会一边在外部持续 Ping 业务 IP,一边在服务端查看 /proc/net/bonding/bond0 状态,并手动执行 ip link set <主网卡> down 模拟拔掉网线。确认流量能无损切换到备用网卡且不丢包后,再恢复主网卡并正式交付上线。

06 面试高频追问与应答预案

追问 1:在主备模式下,如果故障的主网卡(ens38)恢复了,流量会自动切回 ens38 吗?

  • 回答要点:
  • 取决于是否配置了 primary 及 primary-reselect-policy(或内核的 primary_reselect)参数。
  • 如果配置文件中没有指定 primary 主网卡,当原网卡恢复后,它只会作为备用卡(Backup)待命,流量继续由当前活跃的 ens39 承载,不会发生回切。
  • 如果显式指定了 primary: ens38(如前文配置),Linux bonding 驱动的 primary_reselect 默认值为 always (0),意味着一旦 ens38 恢复 UP 状态,就会立即触发抢占切回主卡。在生产环境中,为防止物理端口接触不良(Flapping)引发流量在主备卡之间高频来回震荡,通常建议不配置固定 primary,或将抢占策略配置为 primary-reselect-policy: failure(仅当当前活动网卡也故障时才重新选择)。

追问 2:配置中的 mii-monitor-interval: 100 代表什么含义?如果交换机上联链路断了它能检测出来吗?

  • 回答要点:
  • 参数含义:代表启用 MII(Media Independent Interface)硬件载波链路监测,内核每 100 毫秒检查一次本地物理网卡的链路状态(Carrier Status)。100ms 是在 CPU 开销与故障切换速度之间取得平衡的通用推荐值。
  • MII 监测的局限性:MII 仅能检测服务器网卡到直连交换机端口这一段物理网线是否连通。如果直连交换机端口没断(灯还亮着),但交换机的上联核心路由断了,MII 会认为链路依然是 up 而不会触发切换。若需防范此类黑洞故障,需改用 ARP 探测机制(配置 arp-interval 和 arp-ip-targets 指向网关 IP),通过三层 ARP 响应来判断真实网络连通性。

07 Bond 网卡高可用状态自动巡检脚本 (check_bond_health.sh)

该脚本用于自动扫描宿主机底层所有 Bond 逻辑网卡的运行模式、主备状态及下属物理子网卡的链路完整性,提前发现单边链路断开导致的冗余降级隐患。

#!/usr/bin/env bash
# ==============================================================================
# 脚本名称: check_bond_health.sh
# 功能描述: 自动扫描系统底层 Bond 聚合网卡状态,核查物理成员链路冗余度 (纯只读)
# 使用方式: 在物理宿主机以 root 权限执行
# ==============================================================================

set -o pipefail

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
PLAIN='\033[0m'

echo -e "${BLUE}================================================================${PLAIN}"
echo -e "${BLUE}           系统底层 Bond 逻辑网卡高可用状态自动化巡检           ${PLAIN}"
echo -e "${BLUE}================================================================${PLAIN}"

# 1. 检测系统是否存在 Bond 网卡
if [ ! -d /proc/net/bonding ]; then
    echo -e "\n${YELLOW}[INFO] 当前系统未加载 bonding 内核模块,未配置双网卡绑定。${PLAIN}"
    exit 0
fi

BOND_FILES=$(ls /proc/net/bonding/ 2>/dev/null)
if [ -z "$BOND_FILES" ]; then
    echo -e "\n${RED}[WARN] /proc/net/bonding/ 下无活跃接口,当前服务器存在物理网卡单点风险。${PLAIN}"
    exit 0
fi

for bond in $BOND_FILES; do
    echo -e "\n${YELLOW}>>> 正在检查逻辑接口: ${bond}${PLAIN}"
    BOND_PATH="/proc/net/bonding/${bond}"

    # 2. 提取核心配置与全局状态
    BOND_MODE=$(grep "Bonding Mode:" "$BOND_PATH" | cut -d':' -f2 | sed 's/^[ \t]*//')
    MII_STATUS=$(grep -m 1 "MII Status:" "$BOND_PATH" | cut -d':' -f2 | sed 's/^[ \t]*//')
    MII_POLLING=$(grep "MII Polling Interval (ms):" "$BOND_PATH" | cut -d':' -f2 | sed 's/^[ \t]*//')
    ACTIVE_SLAVE=$(grep "Currently Active Slave:" "$BOND_PATH" | cut -d':' -f2 | sed 's/^[ \t]*//')

    echo -e "  -> 绑定工作模式: ${GREEN}${BOND_MODE}${PLAIN}"
    echo -e "  -> MII 探测周期: ${MII_POLLING} ms"

    if [ "$MII_STATUS" == "up" ]; then
        echo -e "  -> 逻辑接口状态: ${GREEN}UP (正常)${PLAIN}"
    else
        echo -e "  -> 逻辑接口状态: ${RED}DOWN (严重异常:逻辑网卡已中断)${PLAIN}"
    fi

    if [ -n "$ACTIVE_SLAVE" ]; then
        echo -e "  -> 当前承载主卡: ${GREEN}${ACTIVE_SLAVE}${PLAIN}"
    fi

    # 3. 遍历底层物理子网卡 (Slave Interfaces)
    echo -e "\n  -> [ 底层物理成员网卡状态 ]"
    SLAVES=$(grep -E "^Slave Interface:" "$BOND_PATH" | cut -d':' -f2 | sed 's/^[ \t]*//')

    UP_COUNT=0
    TOTAL_COUNT=0
    for slave in $SLAVES; do
        ((TOTAL_COUNT++))
        # 提取对应子网卡块内的 MII Status 与 Link Failure Count
        SLAVE_BLOCK=$(awk "/^Slave Interface: ${slave}$/,/^$/" "$BOND_PATH")
        SLAVE_STATUS=$(echo "$SLAVE_BLOCK" | grep -m 1 "MII Status:" | cut -d':' -f2 | sed 's/^[ \t]*//')
        FAIL_COUNT=$(echo "$SLAVE_BLOCK" | grep -m 1 "Link Failure Count:" | cut -d':' -f2 | sed 's/^[ \t]*//')

        if [ "$SLAVE_STATUS" == "up" ]; then
            echo -e "     - 网卡 ${slave} :${GREEN}UP (链路正常)${PLAIN} | 历史闪断次数: ${FAIL_COUNT:-0}"
            ((UP_COUNT++))
        else
            echo -e "     - 网卡 ${slave} :${RED}DOWN (物理链路断开!请检查网线或对端交换机端口)${PLAIN} | 历史闪断次数: ${FAIL_COUNT:-0}"
        fi
    done

    # 4. 冗余健康度综合评估
    if [ "$TOTAL_COUNT" -lt 2 ]; then
        echo -e "\n  -> ${RED}[ALERT]${bond} 仅包含 ${TOTAL_COUNT} 块物理网卡,不具备物理冗余容灾能力!${PLAIN}"
    elif [ "$UP_COUNT" -lt "$TOTAL_COUNT" ]; then
        echo -e "\n  -> ${RED}[WARN] 检测到备用物理链路处于 DOWN 状态!当前已降级为单点运行,失去容灾保护,请尽快报修物理链路。${PLAIN}"
    else
        echo -e "\n  -> ${GREEN}[OK] ${bond} 所有底层物理成员链路均处于 UP 状态,高可用冗余正常。${PLAIN}"
    fi
done

echo -e "\n${BLUE}================================================================${PLAIN}"