本文延续前一篇《网络协议栈与路由机制》的脉络,从物理网络延伸到虚拟化场景下的覆盖网络。阅读本文需要具备基本的 IP 路由和以太网概念。

核心概念

后续章节会频繁引用以下术语。

术语定义
覆盖网络(Overlay Network)在已有物理网络之上,通过封装技术构建的虚拟网络
Underlay承载覆盖网络流量的物理网络基础设施
广播域(Broadcast Domain)一个二层广播帧(目标 MAC 为 FF:FF:FF:FF:FF:FF)能到达的所有设备的集合,三层转发边界(如路由器)是其边界
VLAN(Virtual Local Area Network)通过 VLAN ID 将交换端口划入不同广播域的技术,ID 为 12 位,上限 4096 个
VNI(VXLAN Network Identifier)VXLAN 的网络标识,24 位,用于区分不同的虚拟网络
OVS(Open vSwitch)Linux 上的软件交换机,支持 OpenFlow 流表
流表(Flow Table)OVS 中”匹配条件→动作”的规则列表,控制包的转发与策略
SDN 控制器集中管理网络设备的控制程序,负责向交换机下发流表

为什么需要覆盖网络

虚拟化环境中,一台物理机上可能运行数十甚至数百个虚拟机(VM)或容器。这些租户的 IP 地址由虚拟化平台分配,对底层物理网络完全透明。物理交换机不感知这些虚拟 IP 的存在,自然无法直接转发。

VLAN 可以在同一台交换机上划分不同的广播域,但其 ID 只有 12 位,上限 4096 个,且受限于二层网络无法跨越路由器。VXLAN(Virtual eXtensible LAN,RFC 7348)用封装技术解决了这两个限制:

  • VNI 为 24 位,支持约 1677 万个虚拟网络
  • 通过在 UDP 包中封装二层帧,可以跨越三层网络传输

VXLAN 封装原理

数据包结构

一个虚拟机发出的原始以太网帧与普通帧无异。VXLAN 在其外层依次封装四个报文头:

Outer Ethernet Header (物理 MAC)
  Outer IP Header      (物理 IP)
    Outer UDP Header    (目标端口 4789)
      VXLAN Header      (8 bytes, 含 VNI)
        Original Frame   (原始以太网帧,一字不改)

展开来看:

┌──────────────────────────────────────────────────────────┐
│ Outer Ethernet: SrcHost MAC → DstHost MAC                │
│ Outer IP:       SrcHost IP  → DstHost IP                 │
│ Outer UDP:      Random Port → Dst Port 4789              │
│ VXLAN Header:   Flags=0x08, VNI=0x0000C8, Reserved=0     │
│                                                          │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Original Ethernet: VM-A MAC → VM-B MAC               │ │
│ │ Original IP:        VM-A IP  → VM-B IP               │ │
│ │ Payload                                              │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘

外层报文头使用物理网络的地址,交换机和路由器按常规方式转发。到达目标物理机后,内核识别 UDP 端口 4789,剥离外层,将原始帧交给本地虚拟交换机处理。

VNI 的作用

VNI 的定义见上文「核心概念」表格,本节展开其两个核心职责:

  1. 接收端解复用:远端物理机解封装后,根据 VNI 决定把原始帧交给哪个虚拟网桥处理。在端到端流程的示例中,接收端 OVS 用 VNI=200 + 目标 IP 两个维度定位目标端口。
  2. 二层广播域隔离:同一个 VNI 下的虚拟机属于同一个广播域,不同 VNI 的流量在隧道中完全隔离,即使共享同一套物理网络。

VNI 的本质是二层广播域的标识,而不是租户标识。一个租户往往对应多个 VNI,原因如下:

  • 租户内部的网络拓扑需要分段(如 DMZ、应用层、数据层),各段之间要求二层隔离
  • 合规要求某些子网之间不能直接二层通信
  • 一个租户跨越多个 VNI 后,跨 VNI 通信需要经过三层网关(在广播域之间做 IP 路由转发的设备),可以在网关上叠加路由策略和访问控制,比纯二层更安全

不同平台的映射粒度各不相同。OpenStack Neutron 默认一个网络(network)对应一个 VNI;部分 Kubernetes CNI 插件(如 Calico VXLAN 模式)也按 IP 池(IPPool)粒度分配不同的 VNI。

封装和解封装的执行者

整个过程在内核态完成,没有用户态进程参与:

步骤执行者
虚拟机发出原始帧虚拟机的内核网络栈
虚拟交换机查表决策OVS 内核模块(openvswitch.ko)
封装 VXLAN 外层OVS 内核模块(自行管理隧道时)或 Linux 内核 VXLAN 模块(vxlan.ko)
物理网卡发送网卡驱动
解封装 VXLAN 外层OVS 内核模块或 Linux 内核 VXLAN 模块
虚拟交换机投递到目标虚拟机OVS 内核模块

OVS:软件交换机

基本结构

OVS 在 Linux 上实现了一台可编程的虚拟交换机。虚拟机或容器通过虚拟网络设备连接到 OVS 的网桥(bridge)上——KVM 虚拟化场景通常使用 TAP 设备,容器场景通常使用 veth pair:

OVS Bridge (br0)
  |-- vnet0   <--->  VM-1
  |-- vnet1   <--->  VM-2
  |-- vnet2   <--->  VM-3
  |-- ...
  |-- vxlan0  <--->  VXLAN tunnel to Host-A
  |-- vxlan1  <--->  VXLAN tunnel to Host-B
  +-- ...

虚拟机发出的包到达 OVS 后,OVS 查询流表决定如何处理。

流表的结构

每条流表规则包含两个部分:匹配条件动作

匹配条件:  目标 IP = 10.10.1.5, TCP 端口 = 80
动作:      封装 VNI=200, 从 vxlan0 发出

匹配条件可以基于包头的几乎任何字段(MAC、IP、端口、VNI 等),动作包括转发到指定端口、丢弃、修改包头字段、或者提交到下一个流表继续处理。

流表的流水线

OVS 将流表组织为多级流水线,包按序经过每个 table 处理,类似后端开发中的中间件链。不同 SDN 控制器的 table 编号各不相同,下面展示一种常见的编排方式:

packet
  |
  table 0:   classify (which port, tag metadata)
  |
  table 20:  filter (IP? ARP? multicast?)
  |
  table 45:  security group / policy check
  |           allow -> resubmit to next
  |           deny  -> drop
  |
  table 60:  forwarding (which port for dst logical IP)
  |
  output to target port

resubmit(,45) 相当于中间件中的 next(),将包交给下一级处理——各 stage 职责分离,新增功能只需插入新的 table,不影响已有规则。任何一级返回 drop 就终止流水线。

动态映射表

核心问题

虚拟机或容器会在物理节点之间迁移。当 VM-A 从 Host-1 迁移到 Host-2 时,所有节点都需要更新”VM-A 的逻辑 IP 现在在 Host-2 上”这一映射关系。

两类控制模式

集中式:一个 SDN 控制器掌握全局信息,主动向每个节点下发流表。

         ┌──────────────┐
         │ SDN Controller│
         └──┬───┬───┬───┘
            │   │   │
        ┌───┘   │   └───┐
        ▼       ▼       ▼
    Host-1   Host-2   Host-3
    flow-A   flow-B   flow-C

分布式:每个节点自行维护本地映射,通过协议互相通告。

Host-1 ←── BGP/gossip ──→ Host-2 ←── BGP/gossip ──→ Host-3

两种模式同步的内容相同:映射关系(逻辑 IP 在哪台物理机上)和网络策略(谁和谁能通信)。

不同实现中的名字

“动态映射表”是一个通用概念,在不同实现中名称不同。Kubernetes 生态中的 CNI(Container Network Interface)插件负责容器网络的映射与策略,Calico 使用 Linux 路由表加 iptables 或 eBPF(一种内核可编程技术,可用于高性能的网络包处理)实现,Flannel 使用 VXLAN 封装配合 Linux 转发表(FDB),Cilium 基于 eBPF 实现。公有云 VPC 的映射则由云厂商的 SDN 控制面管理。

环境动态映射表管理者
OVS + SDN 控制器OVS 流表SDN 控制器
Calico (K8s CNI)Linux 路由表 + iptables/eBPFcalico-node (BIRD)
Flannel VXLANLinux FDB + 路由表flanneld
CiliumeBPF mapcilium-agent
公有云 VPC交换机上的流表云厂商 SDN 控制面

映射与策略

映射是最简策略

“逻辑 IP 到物理位置的映射”是转发决策的最低要求 — 告诉交换机包往哪发。在此基础上,可以叠加更精细的访问策略:

层级功能类比
映射怎么到DNS:告诉你服务器在哪
映射 + 安全组能不能到DNS + 防火墙:能不能访问看规则
映射 + 安全组 + QoS到了之后跑多快DNS + 防火墙 + 限速

策略在流表中的实现

访问策略不是独立于流表的机制,而是流表中的特定规则。如「核心概念」中所定义,每条规则包含匹配条件和动作;转发规则的动作是 output,策略规则的动作是 drop,它们共享同一个流表匹配引擎:

规则1: 目标=10.10.1.5, TCP 80    → output: vxlan0    ← 允许并转发
规则2: 目标=10.10.1.5, TCP 22    → drop               ← 拒绝
规则3: 目标=10.10.1.5            → output: vnet3      ← 映射到本地 VM
规则4: 其他                      → drop               ← 默认拒绝

在 OVS/OpenFlow 中,规则按优先级从高到低匹配,先命中哪条就执行哪条。精细规则(指定端口)优先级高于粗粒度规则(仅指定 IP),因此规则 1 命中 TCP 80 时允许通过,规则 2 命中 TCP 22 时拒绝,而规则 3 作为兜底的映射规则不会被上述端口命中。

在多级流表流水线中,策略检查和映射查找通常位于不同的 table:策略检查在前(table 45),决定”该不该发”;映射查找在后(table 60),决定”往哪发”。二者各司其职,共同完成从逻辑地址到物理位置的完整转发决策。

完整端到端流程

同 VNI 通信(二层转发)

追踪一个虚拟机发包到同一 VNI 内、另一台物理机上的虚拟机的完整路径:

[VM-A 发包]  VNI=100, IP=10.10.1.3
  目标 10.10.1.5 与本机同网段 → 直接 ARP 查询 VM-B 的 MAC
  原始帧: 源 MAC=VM-A, 目标 MAC=VM-B, 源 IP=10.10.1.3, 目标 IP=10.10.1.5

[本地 OVS]
  查流表 → 目标 10.10.1.5 不在本机 → 远端物理机 172.16.0.2
  封装: 外层 IP=172.16.0.1→172.16.0.2, UDP 4789, VNI=100

[物理网络]
  交换机/路由器只看外层地址,正常转发

[远端物理机]
  内核识别 UDP 4789 → 剥离外层 → 得到原始帧 + VNI=100
  OVS 查流表: VNI=100, 目标 10.10.1.5 → 本地 vnet5

[VM-B 收包]
  收到原始帧,完全不知道中间经历了封装和解封装

同 VNI 内的通信是纯二层行为:OVS 根据 MAC 地址做转发决策,虚拟机看到的始终是一个平坦的二层网络。

跨 VNI 通信(三层路由)

当 VM-A(VNI=100, 10.10.1.3)要访问 VM-C(VNI=200, 10.10.2.5)时,二者不在同一个广播域,必须经过三层网关:

[VM-A 发包]  VNI=100, IP=10.10.1.3
  目标 10.10.2.5 不在本机网段 10.10.1.0/24
  → 查路由表: 默认网关 10.10.1.1
  → ARP 查询网关 MAC
  原始帧: 源 MAC=VM-A, 目标 MAC=Gateway, 源 IP=10.10.1.3, 目标 IP=10.10.2.5

[本地 OVS (Host-1)]
  查流表: 目标 MAC=Gateway → 送给网关
  封装: VNI=100, 外层 IP=172.16.0.1→172.16.0.10 (网关物理 IP)

[三层网关]
  解封装 → VNI=100 + 原始 IP 包
  查路由表 + VTEP 解析: 10.10.2.0/24 → VNI=200, 下一跳=Host-2 (172.16.0.3)
  MAC 重写: 源 MAC=Gateway, 目标 MAC=VM-C
  重新封装: VNI=200, 外层 IP=172.16.0.10→172.16.0.3

[物理网络]
  交换机/路由器只看外层地址,正常转发

[远端 OVS (Host-2)]
  解封装 → VNI=200 + 原始帧
  查流表: VNI=200, 目标 MAC=VM-C → 本地 vnet7

[VM-C 收包]
  源 MAC=Gateway(网关做了 MAC 重写), 源 IP=10.10.1.3(IP 层不变)

跨 VNI 通信与同 VNI 通信的关键区别:

维度同 VNI跨 VNI
转发依据目标 MAC(OVS 二层)目标 IP(三层网关)
决策者OVS 二层流表三层网关路由表
MAC 是否重写否(端到端不变)是(网关替换源/目标 MAC)
封装 VNI始终不变网关从 VNI=100 重封装为 VNI=200
物理网络感知无(只看外层地址)无(只看外层地址)

可以看到,物理网络在整个过程中只看外层 IP 地址做转发,完全不知道 VNI 和虚拟网络的存在。封装和解封装将物理网络的拓扑与虚拟网络的逻辑完全解耦。