VXLAN 封装
课程(6 节,按顺序学)
第 1 课 · VXLAN 到底解决了什么问题
入门 · 约 8 分钟
本课目标:两件事:VLAN 只有 4094 个不够用;更难的那件是——要让被三层网络隔开的两台主机同处一个二层域。
12 bit 的天花板
VLAN ID 在 802.1Q 标签里只有 12 bit,去掉保留的 0 和 4095,实际可用的是 1 到 4094。在一个企业园区里这个数字绰绰有余;但在一个多租户的云环境里,让每个租户随手申请三五个网段,一千个租户就把整个空间用光了。
更麻烦的是:这 4094 个是整个二层域共享的一个命名空间——不是每台交换机各有 4094 个。同一个 fabric 上两个互不相干的租户都想用 VLAN 100,你就得在中间某处做 VLAN 翻译,或者干脆把他们隔到不同的物理网络里去。★ 规模一上来,VLAN ID 就从「一个配置项」变成了一种需要集中分配、集中记账的稀缺资源。
VXLAN 的第一层答案很直接:把段标识符从 12 bit 换成 24 bit 的 VNI,一下变成 1600 万个。这个数字大到可以不用记账了——直接从租户 ID、业务 ID 映射出来就行。
真正难的那件事:三层网络上要二层可达
但只是「段号不够用」,不足以让整个行业换一套封装。真正的驱动力是另一件事:现代数据中心的 fabric 是纯三层的 spine-leaf——每条 leaf 上行链路都是一个路由接口,靠 ECMP 把流量摊到所有 spine 上。为什么必须这么设计,本产品的 DC Fabric 模块讲得很细:只有三层才有 ECMP,二层网络得靠 STP 把冗余链路阻塞掉。
可业务侧的需求恰好相反:虚拟机在机架之间迁移时 IP 不能变;集群心跳、VRRP、以及不少老应用的广播式发现,都要求成员在同一个网段里。IP 不变、同网段,本质上都是在要求这两台主机之间存在二层邻接。
★ 于是矛盾非常锋利:底下这张网必须是三层的(为了 ECMP 和规模),上面跑的业务却要二层(为了迁移和同网段)。VXLAN 就是把二层帧整个塞进三层报文里,让这两个要求同时成立。
overlay 与 underlay:两张网叠在一起
这么一分层,就有了两张逻辑上独立的网。underlay(承载网)是那张真实的三层 fabric,它只需要做好一件事——让每台 leaf 的 VTEP 地址(通常是一个 loopback /32)之间互相可路由。overlay(叠加网)是 VXLAN 帮租户造出来的那些二层域,一个域一个 VNI。
这个分层最值钱的地方在于:故障域和变化频率被彻底分开了。underlay 的路由表里只有几百条 loopback 和互联网段,建完之后几年不动;而 overlay 上租户天天新建、删除网段,虚机天天迁移——这些动作一条 underlay 路由都不用改。
★ 反过来说,排障时一旦把两张网混在一起看,就会非常痛苦。要养成的习惯是先问一句:这是 underlay 的问题还是 overlay 的问题?两端 VTEP 地址 ping 得通吗?通了,就说明 underlay 没事,问题在封装、VNI 或者 MAC 学习上。
实验重点
- VLAN ID 只有 12 bit,可用 1-4094,而且是整个二层域共享的一个命名空间;VNI 是 24 bit,1600 万个
- VXLAN 真正解决的是「在三层 fabric 上提供二层邻接」,不只是段号不够用
- underlay 只管 VTEP 之间可路由,overlay 才是租户看到的二层域;排障第一步永远是先分清这是哪一层的问题
第 2 课 · ★★★ 封装栈:那 50 个字节里装了什么
入门 · 约 14 分钟
本课目标:本模块最核心的一课——把「原始帧 → VXLAN → UDP → 外层 IP → 外层以太网」这 50 个字节逐层讲透,每一层为什么必须在。
四层新头,一层一个职责
一台虚机发出来的还是一个普普通通的以太网帧:目的 MAC、源 MAC、可能带一个 802.1Q 标签、然后是 IP 报文。VTEP 拿到它之后一个字节都不改,只是在前面依次贴上四层新的头:
[外层以太网 14B][外层 IP 20B][UDP 8B][VXLAN 8B][ 原始以太网帧:目的MAC 源MAC (802.1Q) IP ... ]
新增合计 = 14 + 20 + 8 + 8 = 50 字节
这个 50 字节要背下来,第 5 课整节课都在算它。顺便澄清一个常见的混淆:口语里说的「VXLAN 头 8 字节」,指的只是最里面那一层;工程上说的 50 字节,是这四层的总和。
★ 这一课真正要建立的认知是:这四层里没有一层是可选的装饰——每一层都在回答一个具体的、绕不过去的问题。下面逐层说它为什么必须在那里。
每一层为什么必须在
VXLAN 头(8 字节)回答的是「这个帧属于哪个二层域」。里面真正承载信息的只有 24 bit 的 VNI 和一个必须置 1 的 I 标志位,其余都是保留位。远端 VTEP 解封装之后,完全靠这个 VNI 决定把内层帧交给哪个二层域——VNI 是 overlay 唯一的身份标识。
UDP 头(8 字节)回答的是「怎么让这个包在现成的三层网络里畅通无阻」。用 UDP、而不是自己申请一个 IP 协议号,是个非常务实的选择:所有的路由器、防火墙、网卡 offload、ECMP 哈希实现,都已经认识 UDP 了。目的端口 4789 是 IANA 分给 VXLAN 的。★ 注意有个经典的坑:Linux 早期实现和一些老设备默认用 8472,两端不一致的话什么日志都不报,就是不通。
外层 IP 头(20 字节)回答的是「这个包在 underlay 里从哪到哪」。源地址是入口 VTEP,目的地址是出口 VTEP,两个都是 leaf 的 loopback。underlay 的路由器看到的,就是一个普通的 loopback 到 loopback 的单播 IP 包,和它平时转发的任何流量没有任何区别。
外层以太网头(14 字节)就是最普通的逐跳二层头,每一跳都会被重写。它和内层那个被完整保留下来的以太网头毫无关系——这也是新手最容易看晕的地方:一个 VXLAN 报文里有两个以太网头,外层每跳都变,内层从头到尾一个比特都不动。
UDP 源端口:它不是端口,是熵
唯一一个「看着是端口、其实不是端口」的字段,是 UDP 源端口。VTEP 从不用它标识任何会话——它是把内层报文的五元组做哈希算出来的一个值(通常落在 49152-65535 这个动态端口范围里),收端根本不看它。
为什么要这么设计?因为 underlay 的 ECMP 哈希只看得到外层头。如果所有 VXLAN 报文的外层五元组都一样(同一对 VTEP IP、同样的 UDP 4789、源端口固定不变),那么两台 leaf 之间的全部流量都会哈希到同一条上行链路上——你买了四条 100G 上行,实际只用得上一条。
★ 把内层的流量差异「翻译」到外层唯一一个允许变化的字段上,让 underlay 在完全不理解 VXLAN 的前提下也能把流量摊开——这就是 UDP 源端口的全部意义,也是整个封装设计里最聪明的一笔。
由此还能推出一个很实用的排障结论:两台主机之间如果只有一条大象流(一条 TCP 连接),它的内层五元组是固定的,算出来的源端口也就固定,那么它只会走一条上行链路。VXLAN 救不了大象流——这和 DC Fabric 模块讲的是同一个问题,只是多套了一层壳。
实验重点
- 50 字节 = VXLAN 8 + UDP 8 + 外层 IP 20 + 外层以太网 14,内层帧一个字节都不改
- VNI 决定内层帧属于哪个二层域;选 UDP/4789 是为了复用「现网所有设备都已经认识 UDP」这件事
- UDP 源端口是内层五元组的哈希,唯一目的是给 underlay 的 ECMP 提供熵——但单条大流依然只走一条链路
第 3 课 · VTEP 与 VNI:谁负责封装,谁完全不知情
入门 · 约 10 分钟
本课目标:状态全都压在 leaf 上;spine 只按外层 IP 转发,从不知道 VNI 和内层 MAC。这个不对称,就是 underlay 能做得又简单又大的原因。
VTEP 是一个角色,不是一台设备
VTEP(VXLAN Tunnel End Point)指的是「做封装和解封装的那个点」。在数据中心里它通常是 leaf 交换机上的一个逻辑接口,源地址用一个 loopback /32;但它也完全可以是服务器里的 vSwitch(软件 VTEP)、一台防火墙、或者一台负载均衡。★ 判断一台设备是不是 VTEP,标准只有一条:它身上有没有发生封装/解封装。
VTEP 干的活其实就两件。入方向(从服务器来):从接入口收到一个帧,先查这个口/VLAN 对应哪个 VNI,再查内层目的 MAC 在哪台远端 VTEP 后面,然后按上一课那四层头封装出去。出方向(从 fabric 来):收到一个发给自己 VTEP 地址的 UDP 4789 报文,剥掉外层,用 VNI 找到本地对应的二层域,把内层帧按普通二层转发送出去。
所以一台 VTEP 上必须有两张表:VNI 到本地 VLAN/接口的映射(这是本地配的),和内层 MAC 到远端 VTEP IP 的映射(要么靠泛洪学,要么靠 EVPN 通告)。后面那张表怎么来的,正是第 6 课和整个 EVPN 模块的主题。
spine 完全不知情——这不是巧合,是设计目标
中间那台 spine 收到的是什么?一个源和目的都是 loopback 地址的普通单播 IP 包。它查自己的路由表,挑一个下一跳,转发出去,结束。★ 它从不解析 UDP 载荷,所以它既不知道 VNI 是多少,也不知道内层 MAC 和内层 IP 是什么,更不知道这个 fabric 上有几个租户。
这份「不知情」是有实打实工程价值的。spine 的 MAC 表里只有 underlay 的邻居,不会因为租户虚机数量增长而膨胀;租户新建一个网段、迁一台虚机,spine 的配置和表项一条都不用动;spine 的角色因此收敛成一件事——一台高性能的三层转发盒子,于是你可以挑便宜、端口密度高、表项规格不必很大的型号。
镜像地看,所有复杂度都被收敛到了 leaf 上:VNI 映射、MAC 学习、BUM 处理、ARP 抑制,全在 leaf。这是个刻意的取舍:leaf 数量多但每台管的租户少,spine 数量少但每个包都要过——把状态放在 leaf 上,整个设计才扩得开。
两端 VNI 必须一致,而且没人会提醒你
一个 overlay 二层域能不能通,前提是两端 VTEP 上配的 VNI 完全一致。如果 leaf-1 把服务器口映射成 VNI 10010,而 leaf-3 映射成了 10011,那么 leaf-1 封装出来的报文到了 leaf-3,leaf-3 会拿 10011 去找本地二层域——找不到就直接丢掉,或者更糟:正好命中了另一个租户的域。
这个错误的可怕之处在于它长得和健康的 fabric 一模一样:underlay 路由没问题,两端 VTEP 地址 ping 得通,所有接口都是 up,日志干干净净。你只能靠对比两端配置、或者去查 VNI 表才能发现它。★ 这就是当前加载的 vni-mismatch 场景要教的事——能 ping 通远端 VTEP,不等于 overlay 通了。
可以带着这个场景去拓扑页看一眼,再切回 baseline 看一眼:两张图画出来是一样的,因为在 underlay 这一层它们确实一样。本模块所有陷阱场景都是这个形状:真正坏掉的那个东西,在图上看不见。
实验重点
- VTEP 是「发生封装/解封装的那个点」,通常是 leaf 上的一个 loopback——spine 不是 VTEP
- spine 只读外层 IP,从不知道 VNI、内层 MAC、租户数量,所以它的表项不随租户规模膨胀
- 两端 VNI 必须一致;配错了拓扑正常、接口 up、VTEP 也 ping 得通,只能靠对比配置或查 VNI 表发现
第 4 课 · BUM 流量:组播组,还是头端复制
进阶 · 约 12 分钟
本课目标:overlay 里没有「泛洪」这个原生动作,复制这件事总得有人干。两种做法,两张完全不同的账单。
为什么 BUM 要单独设计
BUM 是三类流量的合称:Broadcast(广播,比如 ARP 请求、DHCP Discover)、Unknown unicast(目的 MAC 还没学到的单播)、Multicast(组播)。在一台普通的二层交换机上,这三类的处理方式完全一样——从属于该 VLAN 的所有口(除了入口)发出去,也就是泛洪。
但 VXLAN 的封装是点到点的单播隧道:外层 IP 头里只有一个位置能填目的 VTEP 地址。★ 换句话说,overlay 里根本没有「泛洪」这个原生动作——一个需要送到该 VNI 里所有远端 VTEP 的帧,要么由入口 VTEP 自己复制成多份,要么请 underlay 帮忙复制。
这就是 BUM 必须单独设计的原因,也是两条路线的分水岭:复制这件事,交给 underlay 做(组播),还是交给入口 leaf 做(头端复制)。
方案一:每个 VNI 一个组播组
做法是给每个 VNI 分配一个 underlay 的组播组地址,比如 VNI 10010 对应 239.1.1.10。所有配了这个 VNI 的 VTEP 都加入这个组;入口 VTEP 遇到 BUM 帧时,外层目的 IP 直接填这个组播地址,只发一份。
复制的活交给 underlay 的组播树去做,报文只在分叉点被复制,所以任何一条链路上都不会出现重复流量。这是带宽上最优的答案:不管 VNI 里有多少台 VTEP,入口 leaf 的上行链路上永远只有一份。
代价是你的 underlay 从此要跑组播:PIM、RP(汇聚点)、组播路由状态、每个 VNI 一个组的地址规划,以及组播的排障。★ 现网大面积放弃这个方案的原因非常朴素——为了一个 fabric 内部的辅助功能,去维护一整套组播控制平面,运维复杂度和收益完全不成比例,而且组播故障的排查难度比单播高一个量级。
方案二:头端复制,以及这笔账怎么算
头端复制(ingress replication,也叫 head-end replication)的做法是:入口 VTEP 自己把这一个 BUM 帧复制成 N-1 份(N 是该 VNI 里 VTEP 的总数),每一份封上一个不同的目的 VTEP 地址,当成普通单播依次发出去。underlay 完全不需要知道组播是什么东西。
这笔账要老老实实算。假设一个 VNI 里有 8 台 VTEP,一台主机发一个 ARP 广播:入口 leaf 要生成 7 份报文,它的上行链路上就出现 7 倍于这一份的流量。VNI 里 VTEP 越多,倍数越大。★ 头端复制的成本是 O(N),而且这笔钱全部由入口那一台 leaf 的上行带宽和转发芯片来付。
那现网为什么还是选它?两个原因。第一,underlay 只跑单播,配置简单、故障好查。第二——也是本模块最想让你记住的因果——★ 一旦上了 EVPN,BUM 里最大的那一块(未知单播)基本就消失了:MAC 在流量发生之前就已经被 BGP 通告好了,没有「未知」;ARP 还能由 leaf 本地代答(ARP 抑制)。剩下的真广播量很小,7 倍也就无所谓了。
所以这两个方案的取舍,从来不是「哪个技术更先进」,而是「你愿意在 underlay 上背组播,还是在控制平面上背 EVPN」。行业给的答案是后者。
实验重点
- BUM = 广播 + 未知单播 + 组播;VXLAN 是点到点单播隧道,overlay 里没有原生的泛洪动作
- 组播组方案带宽最优,但要在 underlay 上跑 PIM/RP;头端复制不需要组播,但入口 VTEP 要自己造 N-1 份
- 头端复制的成本是 O(N),而且全压在入口那一台 leaf 上——之所以还能接受,是因为 EVPN 把未知单播这一大块消掉了
第 5 课 · ★★★ 排障:ping 通了,传文件卡死
排障 · 约 14 分钟
本课目标:「部分能通」是 MTU 问题的招牌症状。学会从症状直接推到「underlay MTU 该配多少」这个数字上。
症状:为什么恰好是「小包通、大包不通」
现场的描述通常是这样的:两台虚机之间 ping 完全正常,ssh 登上去敲命令也很顺,但只要 scp 一个大文件就卡住不动;或者网页打开一半白屏;或者数据库连上了,一执行大查询就超时。
这种「部分成功」几乎可以直接锁定 MTU,原因是报文大小和业务类型强相关:ping 默认只有 64 字节,ssh 的交互按键是几十字节的小包,TCP 三次握手也是小包——这些都远小于任何 MTU,一路顺畅。而文件传输、大查询的响应会把 MSS 填满,一上来就是满长度的报文。
★ 所以要把这个反射建立起来:只要出现「连通性正常、但大流量业务不通」,第一反应就是查 MTU,而不是去翻路由和 ACL。反过来同样有用:如果连 ping 都不通,那基本就不是 MTU 的问题。
下面还有一层更隐蔽的机制:TCP 握手时协商的 MSS 是按主机自己网卡的 MTU 算的(1500 - 40 = 1460),主机完全不知道路上还要被套 50 个字节。于是它理直气壮地发出一个 1500 字节的帧,封装后变成 1550——underlay 接口的 MTU 是 1500,这个包被丢掉了。
算术:underlay MTU 到底该配多少
公式就是第 2 课那 50 个字节:underlay 接口 MTU >= 内层帧最大长度 + 50。内层是标准的 1500 字节 IP MTU 时,underlay 至少要 1550。如果内层帧还带一个 802.1Q 标签(4 字节),就是 +54,也就是 1554。
这里有个特别容易翻车的地方:不同厂商对「接口 MTU」统计的范围并不一致——有的算三层载荷(IP MTU),有的算包含以太网头的整帧(有时叫 baby giant 或 frame size)。★ 所以不要去背 1550 这个数字,要背 加 50 这个关系,然后按你手上设备的定义换算成配置值,配完再实测验证。
实测的办法是发一个带 DF 位、指定长度的 ping:从主机发 1472 字节负载(1472 + 20 IP + 8 ICMP = 1500)能通、1473 不通,说明路径 MTU 就是 1500。从这里二分下去,就能把真正卡住的那一跳定位出来。
为什么标准解法是给 underlay 开巨帧
理论上有三种解法。① 把所有主机的 MTU 从 1500 降到 1450:技术上成立,但你要去改成千上万台虚机、容器、裸金属的网卡配置,还得保证以后新建的一台都不会漏——运维上根本不成立。② 让 VTEP 对 overlay 报文做分片重组:性能代价极大,很多硬件压根不支持,而且分片本身就是麻烦的源头。③ 把 underlay 的 MTU 抬上去。
★ 行业的标准答案是 ③:在 underlay 的每一跳上统一开巨帧(jumbo frame),通常直接配到 9000 或 9216。为什么配这么大、而不是刚好 1554?因为这样一劳永逸——以后再叠 QinQ、再叠 MPLS、换一种更长的封装、内层想跑 9000 的巨帧,都不用回来重新改一遍。配置的动作一样多,余量白送。
关键在于「每一跳」这三个字。underlay 路径上任何一个接口漏配,症状就完整地出现——但只对经过那一跳的流量出现。而 fabric 本来就是 ECMP 多路径,于是你会看到一个非常诡异的现象:同一对主机之间,有的连接好、有的连接坏,取决于哈希挑中了哪条上行链路。这种「时好时坏」就是漏配单个接口最典型的指纹。
实验重点
- ping 通但大文件卡死 = MTU 问题的招牌症状:小包躲过了 MTU,满长度的包躲不过
- underlay MTU 至少要 内层 + 50(内层再带一个 802.1Q 标签就是 +54);别死记 1550,记住「加 50」这个关系
- 标准解法是 underlay 每一跳统一开 9000/9216 巨帧;漏配一跳的指纹是「同一对主机,有的连接好、有的坏」
第 6 课 · 排障:泛洪学习的天花板(也是 EVPN 模块的入口)
排障 · 约 12 分钟
本课目标:只有 VXLAN 数据平面时,MAC 位置是从流量里猜出来的。它能跑通,但撑不大——这正是 EVPN 模块存在的理由。
没有控制平面时,MAC 表是从哪来的
前面几课一直绕开一个问题:入口 VTEP 凭什么知道「内层这个目的 MAC 在哪台远端 VTEP 后面」?如果只有 VXLAN 数据平面,答案是——它一开始并不知道,只能靠泛洪问出来,再靠偷看学回来。
完整过程是这样的。主机 A 要发给主机 B,leaf-1 查不到 B 的 MAC,这就是一个未知单播,于是按第 4 课的方式泛洪出去(头端复制成 N-1 份,或者送进组播组),送到该 VNI 里所有远端 VTEP。每台 VTEP 解封装后往本地口泛洪,只有 B 所在的 leaf-3 那一边有人应答。
关键在于学习就发生在解封装那一刻:leaf-3 收到这个报文时,同时看到了外层的源 VTEP IP 和内层的源 MAC,于是它记下「MAC-A 在 VTEP-1 后面」。等 B 回包,leaf-3 已经知道该封装给谁;leaf-1 收到回包,也照样学到「MAC-B 在 VTEP-3 后面」。★ 这就是 flood-and-learn(泛洪学习):数据平面自己充当了控制平面——没有任何协议通告过任何东西,所有表项都是从真实业务流量里推断出来的。
三个撑不大的地方
① 每一个新目的都要付一次泛洪的代价。 一台新虚机上线、一对此前没通信过的服务,第一个包必然是未知单播,必然被复制 N-1 份送到全 VNI。在 VNI 里 VTEP 多、虚机启停频繁的环境里,这个背景噪声是持续存在的。
② 老化让这笔代价周期性地重来。 MAC 表项有老化时间(常见 300 秒)。一对主机只要沉默超过这个时间,表项就被删掉;下次再通信,又是一次完整的泛洪学习。所以这不是「开机抖一下就好了」,而是稳态下持续的泛洪。
③ 你只能被动等,不能主动查。 一台 VTEP 的表里只有它「见过」的流量对应的条目——同一个 VNI 里另外两台主机之间聊得再热闹,本地 VTEP 也一无所知。★ 这带来两个很实际的后果:整张网里没有任何一个地方能看到这个二层域的全貌(排障时你查每台 leaf,看到的都只是局部真相);而且虚机迁移之后,旧位置的表项要等到老化、或者被新流量覆盖才更新——在那之前,流量一直在往错的 VTEP 发。
还有一层:ARP 请求是广播,在泛洪学习模式下每一个 ARP 都要走一遍全 VNI 的复制。一个几千台虚机的二层域,光 ARP 就能造出可观的背景流量。
动手:看清一次未知单播的真实代价
当前加载的 flood-and-learn 场景,把所有 VTEP 的 MAC 表都清空了——模拟的正是老化之后的状态。去 BUM 页发一个到未知 MAC 的单播,数一数它被复制成了几份,再看每台 VTEP 从这一个包里分别学到了什么。
然后把完全一样的事再做一遍。第二次不会再泛洪了,因为表项已经学到了。★ 请特别留意这两次的差距有多大:同一对主机、同一个业务,第一个包的代价和后面所有包的代价完全不在一个量级上——而老化时间一到,你又回到第一次。
这也解释了一个很常见的现场困惑:某个业务「刚开始有点慢,之后就好了」,抓包时又看到 fabric 上有一阵一阵的广播尖峰。这两件事是同一个机制的两个侧面。
交接:这正是 EVPN 要解决的问题
把上面三条并在一起看,结论很干净:泛洪学习是一个能工作、但不该被规模化的方案。它的每一个缺点都来自同一个根因——MAC 的位置只能从数据平面事后推断,没有人提前告诉你。
★ 所以缺的东西不是更好的封装,而是一个控制平面:一个能在流量发生之前,就把「哪个 MAC 挂在哪台 VTEP 后面」主动通告给所有相关 VTEP 的协议。这正是本类别里另一个模块 EVPN 的全部主题——用 MP-BGP 的 L2VPN EVPN 地址族分发 MAC 以及 MAC/IP 绑定,于是未知单播基本消失、ARP 可以由 leaf 本地代答、虚机迁移可以主动撤销再重新通告。
建议的学习顺序就是你现在正在走的这个:先在 VXLAN 这边把数据平面——封装、VTEP、VNI、BUM、MTU——弄扎实,再去 EVPN 模块看控制平面是怎么把泛洪这件事整个消掉的。两个模块讲的是同一张网的两半,单看任何一半都会觉得少了点什么。
实验重点
- 泛洪学习 = 数据平面自己当控制平面:未知单播先泛洪出去,解封装时从「外层源 VTEP + 内层源 MAC」学回来
- 三个天花板:每个新目的都要泛洪一次、老化让这笔代价周期性重来、任何一台 VTEP 都只有局部视图
- 根因是 MAC 位置只能事后推断——补上一个「提前通告 MAC」的控制平面,就是 EVPN 模块的全部主题