KubeVirt + Kube-OVN构建VPC 环境下的数据路径分析

7 阅读5分钟

KubeVirt + Kube-OVN VPC 环境下的数据路径分析(Opus 4.6)

问题: 帮我分析使用kubevirt 创建的虚拟机,使用kube-ovn实现vpc虚拟网络的情况下,虚拟机内应用访问外部服务涉及的数据复制和上下文切换情况

这是一个非常复杂的网络路径,涉及多层虚拟化叠加。让我逐层分析。

整体架构概览

VM内应用 → VM内核协议栈 → virtio-net → QEMU → tap设备 → OVS网桥 → Kube-OVN隧道封装 → 宿主机内核协议栈 → 物理网卡 → 外部服务

第一层:VM 内部(Guest)

1. 应用缓冲区 → Guest 内核协议栈

  • 复制第1次:用户空间 → Guest 内核 Socket 缓冲区
  • 上下文切换:2次(用户态 → 内核态 → 用户态)
  • Guest 内核完成 TCP/IP 封装

2. Guest 内核 → virtio-net 设备

  • 复制第2次:Guest 内核将数据放入 virtio ring buffer(vring)
  • 这是 Guest 与 Host 之间的共享内存区域
  • Guest 写入描述符后触发 VM Exit(通知 QEMU/KVM)

⚠️ VM Exit 是非常昂贵的操作,类似于上下文切换但开销更大,涉及 CPU 从 Guest 模式切换到 Host 模式。


第二层:QEMU/KVM 虚拟化层(Host 用户空间)

3. virtio ring buffer → QEMU 处理

  • 如果使用 vhost-net(内核态):数据不需要经过 QEMU 用户空间,直接在 Host 内核处理 ✅
  • 如果未使用 vhost-net:
    • 复制第3次:KVM 将控制权交给 QEMU 用户空间进程
    • 上下文切换:内核态 → 用户态(QEMU)
    • QEMU 读取 vring 数据并写入 tap 设备

使用 vhost-net 的情况(KubeVirt 默认启用)

  • vhost-net 在 Host 内核态直接从 vring 读取数据并注入 tap 设备
  • 减少了 QEMU 用户空间的数据复制和上下文切换
  • 复制第3次:vring → tap 设备的内核缓冲区(sk_buff)

第三层:Kube-OVN / OVS 数据平面

4. tap 设备 → OVS 内核数据路径

  • 数据从 tap 设备进入 OVS 内核模块(openvswitch.ko)
  • 通常无额外复制:OVS 内核态直接操作 sk_buff 指针
  • OVS 执行流表匹配:
    • VPC 路由判断
    • 安全组/ACL 规则
    • NAT(如果需要 SNAT 访问外部)

5. OVS 流表未命中 → ovs-vswitchd(用户空间)

  • 如果是首包或流表缓存 miss:
    • 复制第4次:内核 → ovs-vswitchd 用户空间(通过 Netlink upcall)
    • 上下文切换:2次
    • ovs-vswitchd 计算转发决策,下发流表到内核
    • 后续包直接走内核快速路径(无此开销)

6. Geneve/VXLAN 隧道封装

  • Kube-OVN 默认使用 Geneve 隧道实现 VPC 隔离
  • OVS 内核模块执行隧道封装:
    • 添加内层以太网帧头
    • 添加 Geneve/VXLAN 头
    • 添加外层 UDP 头
    • 添加外层 IP 头
    • 添加外层以太网帧头
  • 无额外复制:直接修改 sk_buff

第四层:宿主机内核协议栈 → 物理网卡

7. 封装后的数据包 → 宿主机内核路由

  • 经过宿主机 iptables/nftables(可能有 SNAT 规则)
  • 如果 VPC 需要访问外部,Kube-OVN 会配置:
    • VPC 网关 Pod
    • 或 EIP/SNAT 规则

8. 宿主机内核 → 物理网卡

  • 复制第5次:DMA 将数据从内核内存传输到网卡缓冲区
  • 网卡发送数据帧

完整数据复制与切换总结

数据复制次数

序号位置描述能否避免
1VM 内应用缓冲区 → Guest 内核
2VM 内Guest 内核 → virtio vring
3VM↔Hostvring → tap 设备 sk_buff(vhost-net)
4HostOVS upcall(仅首包)✅ 后续包走缓存
5Host内核 → 物理网卡 DMA

最优路径(非首包):约 4-5 次数据复制

上下文切换 / 模式切换

序号类型描述开销
1系统调用VM 内 send()
2VM Exitvirtio 通知 Host
3系统调用vhost-net 内核处理(如果是 QEMU 则多一次用户态切换)
4Netlink upcallOVS 首包上送(仅首包)
5软中断物理网卡发送完成

关键性能瓶颈分析

🔴 最大瓶颈:VM Exit

  • 每次 virtio 通知都可能触发 VM Exit
  • 单次 VM Exit 开销约 数百到数千个 CPU 周期
  • 优化:virtio 支持 batchnotification suppression

🟡 隧道封装开销

  • Geneve 封装增加约 50-58 字节头部
  • 导致有效 MTU 减小
  • 如果未调整 MTU,会触发分片,进一步增加开销

🟡 OVS 流表处理

  • 首包需要 upcall 到用户空间,延迟较高(ms 级)
  • 后续包走内核 datapath megaflow 缓存,延迟较低(μs 级)

🟢 VPC SNAT 开销

  • 如果 VM 在 VPC 内部,访问外部需要经过 SNAT
  • Kube-OVN 可能通过专门的 VPC NAT Gateway Pod 处理
  • 这意味着数据包可能还要额外经过一个 Pod 的网络命名空间,增加额外路径

与裸机对比

场景数据复制次数模式切换额外封装
裸机直连44次系统调用
容器 + Kube-OVN4-54-5次Geneve隧道
KubeVirt VM + Kube-OVN VPC5-65-7次 + VM ExitGeneve隧道

可能的优化方向

  • 启用 vhost-net(KubeVirt 默认启用):避免数据经过 QEMU 用户空间
  • 启用多队列 virtio-net:提高并发吞吐
  • 调整 MTU:设置 Jumbo Frame 减少分片
  • 使用 OVS-DPDK:将 OVS 数据面移到用户空间轮询模式,减少中断和上下文切换
  • SR-IOV 直通:完全绕过 OVS 和 tap 设备,但会失去 Kube-OVN VPC 的软件定义网络能力
  • vDPA:兼顾 SR-IOV 性能和软件定义网络灵活性