标签: Macvlan

  • 深入 K8S veth pair 丢包排查:高 PPS 触发的 SoftIRQ 单核瓶颈与 macvlan 卸载实战

    在 K8S 容器网络中,高并发(PPS > 30万)场景下 veth pair 极易因单队列架构触发宿主机单核 SoftIRQ (NET_RX) 100% 饱和,导致严重丢包与网络抖动。临时止血方案需在宿主机端开启 RPS(Receive Packet Steering)将软中断打散;而彻底解决该类 I/O 密集型业务瓶颈,应引入 macvlan 或 SR-IOV 进行网络栈卸载,直接旁路宿主机内核的复杂转发路径。

    故障现场:Redis 容器的神秘丢包与 99 线飙升

    近期排查了一起 K8S 集群内 Redis 响应毛刺问题。环境基础信息如下:

    • OS: Ubuntu 22.04 (Kernel 5.15.0-76-generic)

    • K8S 版本: v1.25.9

    • CNI: Calico v3.25.0 (BGP 路由模式)

    • 业务表现: 压测期间 Redis 实例的 QPS 达到 8 万时,p99 延迟从 2ms 突变至 150ms 以上,客户端频繁报 Read timed out

    首先登入 Redis 所在宿主机,直接通过 mpstat 查看中断分布:

    # 每秒输出所有 CPU 核状态
    mpstat -P ALL 1
    
    09:41:01 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
    09:41:02 AM  all    8.23    0.00    6.11    0.00    0.00   12.15    0.00    0.00    0.00   73.51
    09:41:02 AM    0    4.00    0.00    3.00    0.00    0.00    1.00    0.00    0.00    0.00   92.00
    ...
    09:41:02 AM   12    2.00    0.00   10.00    0.00    0.00  100.00    0.00    0.00    0.00    0.00
    

    如上所示,CPU 12 的 %soft 已经被彻底打满(100%)。进一步通过 /proc/softirqs 定位具体的中断类型:

    watch -d -n 1 "cat /proc/softirqs | grep NET_RX"
    

    确认是 NET_RX 软中断风暴。接着查看容器对应的宿主机端 veth 网卡(假设为 cali9a3b2c1)的丢包统计:

    # 确认网卡 rx_dropped 指标疯狂上涨
    ip -s link show cali9a3b2c1
    

    现象明确:宿主机单核处理软中断能力达到极限,导致网卡接收队列(Backlog)溢出,底层协议栈开始大面积丢弃数据包。

    为什么 veth pair 会成为高吞吐场景的性能毒药?

    要搞清楚这个问题,必须深入 veth pair 在内核中的数据流转机制。

    veth pair 是一对虚拟以太网设备。在 Calico 网络下,数据包从物理网卡(如 eth0)进入宿主机,经过内核路由判决后,发往对应的宿主机端 veth 设备(caliXXX),然后再进入容器的网络命名空间。

    对于物理网卡,现代网卡均支持多队列(RSS, Receive Side Scaling),可以通过 Hash 算法将不同数据流的硬件中断(HardIRQ)分发到多个 CPU 核上,进而触发多核并发处理 NET_RX 软中断。

    但 veth pair 是纯软件模拟的虚拟网卡,默认只有单队列(rx-0/tx-0)。 当数据包从物理网卡路由到 caliXXX 时,内核调用 dev_forward_skb,最终触发 netif_rxskb(套接字缓冲区)压入特定 CPU 的 softnet_data->input_pkt_queue 中。 由于 veth 没有硬件多队列支撑,所有发往该容器的数据包,其软中断处理逻辑通常只能由单核(通常是触发调用的源 CPU,或者被网卡中断绑定的固定 CPU)串行执行。当流量达到几十万 PPS 时,这个单 CPU 很快就会触及 100% 的瓶颈,导致后续包因为 Backlog 队满而被丢弃。

    实战破局:从软件调优到硬件卸载

    针对上述瓶颈,我们在实战中通常采用两个阶段的方案:快速止血与架构重构。

    第一阶段:软件层面开启 RPS 打散软中断

    RPS(Receive Packet Steering)是 RSS 的软件实现。它能在 netif_rx 接收到包后,利用四元组 Hash 软计算,将包投递到其他 CPU 的积压队列中,强制触发跨核的软中断处理。

    找到 Redis 对应的宿主机网卡 cali9a3b2c1,为其配置 RPS(假设宿主机为 16 核,我们将掩码设为 ffff,允许打散到所有核):

    # 将 16 进制掩码写入对应接收队列的 rps_cpus 中
    echo ffff > /sys/class/net/cali9a3b2c1/queues/rx-0/rps_cpus
    
    # 同步调大内核层面的 backlog 队列深度,防止缓冲击穿
    sysctl -w net.core.netdev_max_backlog=10000
    

    开启后,再次观察 mpstat,CPU 12 的 %soft 迅速下降至 30% 左右,其他 CPU 的 %soft 开始均衡上升,Redis 响应延迟立刻恢复到 2ms 的水平。

    注意: 这种方案有代价。RPS 带来了额外的 CPU 周期消耗(计算 Hash、跨核 Cache Miss),整体 CPU 负载(Load Average)会显著升高。这是典型的“空间换时间”策略。

    第二阶段:引入 macvlan / SR-IOV 卸载网络栈

    对于此类极致 I/O 的业务,经过多次踩坑,最终的防线必须是绕过复杂的宿主机网络栈。通过 Multus CNI 引入 macvlanSR-IOV,是当前主流的解法。

    macvlan 桥接模式为例,它的底层原理是直接在宿主机物理网卡(eth0)上虚拟出一个具有独立 MAC 地址的子接口。数据包到达物理网卡后,底层驱动通过匹配 MAC 地址,直接将包送入容器的 Network Namespace,彻底跳过了宿主机内核的路由查找、Netfilter (iptables/IPVS) 过滤以及 veth pair 的设备中转。 且 macvlan 继承了物理主网卡的 RSS 特性,天然支持多核并发接收。

    在 K8S 中配置 Multus 与 Macvlan 混合网络示例 (NetworkAttachmentDefinition):

    apiVersion: "k8s.cni.cncf.io/v1"
    kind: NetworkAttachmentDefinition
    metadata:
      name: macvlan-conf
      namespace: default
    spec:
      config: '{
          "cniVersion": "0.3.1",
          "type": "macvlan",
          "master": "eth0",
          "mode": "bridge",
          "ipam": {
            "type": "host-local",
            "subnet": "192.168.100.0/24",
            "rangeStart": "192.168.100.100",
            "rangeEnd": "192.168.100.200",
            "routes": [
              { "dst": "0.0.0.0/0" }
            ],
            "gateway": "192.168.100.1"
          }
        }'
    

    随后在 Redis Pod 中声明注解:

    metadata:
      annotations:
        k8s.v1.cni.cncf.io/networks: macvlan-conf
    

    改造后,Redis Pod 获得了直通物理网络的 eth1 网卡,单机压测极限 PPS 提升了近 3 倍,且宿主机的 CPU sys/soft 占用极低。

    常见问题 (FAQ)

    Q1:为什么使用 macvlan (bridge 模式) 后,宿主机反而 ping 不通该容器了? 这是 macvlan 驱动设计的经典防线。macvlan 拦截了进出物理网卡的流量,但根据 802.1q 规范,从物理网卡发出的包默认不会回流到自己。宿主机发送的报文直接从底层网卡出去了,无法通过 MAC 匹配路由回该网卡上的 macvlan 子接口。 解法: 在宿主机上再创建一个同网段的 macvlan 接口(例如叫 macvlan-host),将宿主机对该网段的路由指向 macvlan-host,利用 bridge 模式下的内部交换机制实现通信。

    Q2:SR-IOV 与 macvlan 相比优势在哪里,什么时候必须上 SR-IOV? macvlan 仍经过宿主机的物理网卡驱动和内核协议栈底层;而 SR-IOV(Single Root I/O Virtualization)是 PCIe 硬件级别的虚拟化。它通过 PF(Physical Function)虚拟出多个 VF(Virtual Function),VF 直接映射给容器。 如果是搞 DPDK 等用户态网络协议栈,或者极低延迟(微秒级)的 HFT (高频交易) 场景,必须用 SR-IOV 彻底 Bypass 内核。普通的高性能 Redis/MySQL,macvlan 已经足够。

    Q3:开启了 RPS,但有些网卡的 rps_cpus 修改后提示 “Permission denied” 或无效? 如果是针对容器内的 veth 设备修改,受限于 NetNS 权限,需在宿主机端的对端网卡(如 Calico 的 calixxx、Flannel 的 vethxxx)操作。另外,务必确保宿主机系统服务(如 irqbalance)不要与你手动的 RPS 掩码逻辑发生冲突,排查过程中发现两者打架是常态,针对极端优化的节点,通常建议关闭 irqbalance 并手动绑核。

  • 深入 Macvlan 广播风暴排查:CAM 表溢出引发的未知单播泛洪与 ksoftirqd 软中断打满实战

    某次接手一个号称为了“极致网络性能”而将 K8S CNI 从 VXLAN 模式改为纯 Macvlan 的生产集群。业务上线后,节点负载出现间歇性雪崩,核心接口 P99 延迟从正常的 20ms 飙升至 3s 甚至超时,节点 Load Average 狂飙。排查到底层的结论很直接:高密度 Pod 产生的离散 MAC 地址不仅打穿了单机网卡的硬件过滤表,更是直接打爆了 ToR 交换机的 CAM(MAC 地址表)。交换机被迫退化成 Hub,引发全网段“未知单播泛洪”(Unknown Unicast Flooding)。所有节点的 ksoftirqd 进程因处理海量非本机的垃圾报文将 CPU 软中断打满。盲目追求扁平网络而不评估物理网络和硬件容量,纯属给自己挖坑。

    案发现场与指标表现

    报警爆发时,业务端反馈连接池超时,但在容器内 ping 网关却时通时断。登录其中一台高负载节点排查,执行 mpstat -P ALL 1,发现部分 CPU 核心的 %soft(软中断)指标死死顶在 100%:

    10:14:01 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %idle
    10:14:02 AM    2    1.00    0.00    3.00    0.00    0.00  100.00    0.00    0.00   0.00
    10:14:02 AM    5    0.50    0.00    2.50    0.00    0.00  100.00    0.00    0.00   0.00
    

    查看 /proc/softirqsNET_RX(网络接收软中断)的计数值在特定核上正在以每秒数十万的速率疯狂拉升。

    直接抓取网卡吞吐,sar -n DEV 1 显示物理网卡 eth0rxpck/s(每秒接收包数)高达 40w+,但节点实际的业务 QPS 根本没有这么大,且大部分包被内核默默丢弃了(rxdrop/s 同样极高)。

    剥丝抽茧:谁在塞满接收队列?

    既然有海量不明报文涌入,直接祭出 tcpdump 在宿主机物理网卡上抓包分析。为了过滤掉正常的本机流量,明确指定抓取目的 MAC 不是本机的报文:

    tcpdump -i eth0 -n -e -c 1000 not ether dst $(cat /sys/class/net/eth0/address)
    

    抓包结果让人大跌眼镜:屏幕上疯狂滚动着目的 MAC 地址属于其他节点上运行的 Pod 的单播报文。

    正常情况下,交换机通过 CAM 表记录 MAC 地址与物理端口的映射,单播包应该精确转发到对应端口,为什么这些单播包会被广播到所有节点?

    结合 Macvlan 的底层机制,问题的技术逻辑链条浮出水面: Macvlan 的核心原理是为宿主机网卡创建多个子接口,每个子接口(即每个 Pod)分配一个独立的、真实的 MAC 地址。

    # Macvlan 底层创建逻辑示例
    ip link add link eth0 name macvlan0 type macvlan mode bridge
    

    第一层雪崩:物理网卡 UC Filter 被迫降级 普通的物理网卡(如 Intel X710 等)硬件支持的单播 MAC 地址过滤表(Unicast Filter Table)容量极其有限(通常在 128 到 512 个之间)。当单台宿主机上调度的 Pod 数量超过网卡硬件限制时,网卡驱动为了保证网络连通性,会直接放弃硬件过滤,强制将网卡设置为混杂模式(Promiscuous Mode)。 通过 dmesg | grep promiscuous 确认了这一点,系统日志中赫然躺着: eth0: entered promiscuous mode 这意味着网卡会将网络上收到的所有报文全部通过 DMA 拷贝到内存,并触发中断交由内核 ksoftirqd 处理。

    第二层雪崩:ToR 交换机 CAM 表溢出 集群规模约 100 台,每台节点平均 150 个 Pod,总计 15000+ 个 MAC 地址。而机架顶部的 ToR 交换机的 CAM 表容量上限仅为 8192。 当交换机学习到的 MAC 地址超过 8192 时,新来的 MAC 无法被记录,或者旧的活跃 MAC 被挤出。当交换机收到目的地址不在 CAM 表中的单播报文时,它的处理机制是:将该报文向 VLAN 内除源端口外的所有端口泛洪(Flooding)

    两层雪崩叠加,灾难诞生了:交换机将海量的单播报文当成广播往全网段狂塞,而所有宿主机的物理网卡均因超过硬件过滤上限处于混杂模式,全盘接收这些垃圾报文。内核网络栈被迫对这每秒数十万的包进行解析、路由判断并最终丢弃,直接耗尽了 CPU 的软中断处理能力,导致正常的业务报文排队超时,业务被一波带走。

    破局与架构避坑

    不要一听到 VXLAN 的封包解包开销,就急着上 Macvlan。扁平网络带来的不仅是性能,还有对二层物理网络的巨大冲击。

    对于这种问题,除了临时扩容交换机 CAM 表(如果硬件支持的话)或降低 Pod 密度外,根本的技术解法是抛弃 Macvlan,转向 IPVLAN (L2 模式)

    IPVLAN 与 Macvlan 类似,都能提供直接接入 Underlay 网络的低损耗,但 IPVLAN 的核心区别在于:所有 Pod 共享宿主机物理网卡的 MAC 地址

    # IPVLAN 底层创建逻辑示例
    ip link add link eth0 name ipvlan0 type ipvlan mode l2
    

    使用 IPVLAN 后:

    1. ToR 交换机解脱:无论节点上跑 10 个还是 1000 个 Pod,交换机在对应端口上只看到 1 个宿主机的 MAC,彻底根绝 CAM 表溢出风险。

    2. 物理网卡解脱:无需占用网卡硬件 MAC 过滤表,网卡无需开启混杂模式,异常泛洪报文在网卡硬件层即被丢弃。

    3. 内核分发:报文到达内核后,IPVLAN 驱动根据网络层的 IP 地址(而不是 MAC)将流量精准分发到对应的 Pod 网络命名空间。

    同类问题速查排查清单

    1. NET_RX 软中断飙升定性:遇到网络高延迟,第一时间 mpstat -P ALL 1 查看 %soft,并用 cat /proc/softirqs 确认是否为 NET_RX 引起。若单核被打满,往往伴随网卡多队列未开启或哈希不均(RSS 配置问题)。

    2. 未知单播泛洪检测:使用 tcpdump -i eth0 -n -e not ether dst <本机MAC> 抓包。如果抓到大量不属于本机且非广播/多播的报文,立即检查交换机 MAC 学习表是否溢出或未学习到对应路由。

    3. 网卡混杂模式暗坑排查:高密度容器场景下,通过 dmesg | grep promiscuousip link show 检查物理网卡是否处于 PROMISC 状态。如果是且非主动开启(如抓包),需警惕硬件 MAC 过滤表已满。

    4. Macvlan 宿主机互通死角:如果开发反馈 Macvlan 模式下 Pod 无法 ping 通所在的宿主机(常导致 Kubelet 健康检查失败),这是 Macvlan 规范限制。必须在宿主机额外创建一个 Macvlan 虚接口,并将宿主机 IP 移至该虚接口并配置对应路由才能绕过此隔离限制。