☰
裸Linux手工搭建VxLAN:MTU、FDB与多租户隔离
2026/10/26 5:23:04 网站建设 项目流程

同一个机房里两台机器要二层互通,中间隔了台三层设备,业务又改不动地址,这种场景我在几个项目里都遇到过。最早的解决办法是把线拉过去,后来是打 VLAN 标签跨设备透传,再后来机架一多、租户一多,4094 个标签根本不够分,跨机房更是不可能。这时候多数人会想到 VxLAN,但一搜资料全是"某某控制器一键下发""某某编排平台自动组网",仿佛不装一套管理平面就没法做事。实际上,Linux 内核从 3.7 起就自带 VxLAN 支持,iproute2一条ip link add type vxlan就能把隧道立起来,三台机器以内、拓扑相对稳定的场景,纯手工配置完全够用,而且能把底层发生了什么看得一清二楚。这篇就把我在裸 Linux 主机上手动搭 VxLAN 的全过程拆开讲:封装原理、参数含义、MTU 怎么算、FDB 怎么填、多租户怎么隔离、踩过的坑怎么排。适合手里有几台机器、需要跨网段打通二层、又不想引入重型方案的人参考,也适合想搞明白 VxLAN 到底干了什么的人从零复现。

1. 先把 VxLAN 的账算明白:它到底替你省掉了什么

1.1 从 VLAN 的 4094 个标签说起

二层隔离这件事,传统做法是 VLAN。协议里 VID 字段 12 位,除掉 0 和 4095 两个保留值,理论上 4094 个可用标签。单机房单业务的时候绰绰有余,问题出在两个方向:一是多租户环境下每个租户要占几个 VLAN,几百个租户就把池子吃干净了;二是 VLAN 标签必须在整条路径的每一台交换设备上都配置,中间任何一跳没放行,包就断在那儿,跨机房基本没法玩。

VxLAN 的思路很直接:既然二层标签不够用、又必须全网设备配合,那就把整个二层帧当成货物,打个包裹,走三层网络送过去。VNI 是 24 位,理论上 1600 万个隔离域;而中间网络只需要支持普通 IP 路由转发,不需要认识 VxLAN,也不需要知道里面装的是什么。对中间设备来说,这只是一堆源端口 4789 的 UDP 包。

这个转变的意义在于隔离域的数量级和跨域能力同时被解锁。以前两个机房之间要做二层延伸,得协调运营商拉专线、配透传;现在只要两边主机三层可达,隧道就通了。代价当然也有,后面会算。

1.2 VxLAN 封装长什么样:50 字节的代价

要理解后面所有的 MTU 问题,先得把封装结构记在脑子里。一个内层以太帧从本机 vxlan 接口发出去,经过网卡之前会被加上这么几层:

层次长度内容
外层以太头14 字节源/目的 Underlay MAC
外层 IP 头20 字节源/目的 Underlay IP(无选项)
UDP 头8 字节源端口随机,目的端口默认 4789
VxLAN 头8 字节Flags + VNI + 保留字段
内层以太帧变长原始二层帧,含内层 MAC 和载荷

加起来固定开销 50 字节。VxLAN 头本身的结构是 1 字节 Flags(其中 I 位置 1 表示 VNI 有效,值为 0x08)、3 字节保留、4 字节的 VNI 字段——实际 VNI 只占 24 位,剩下 8 位保留。也就是说 VNI 最大 16777215,日常规划用几百几千完全够了。

记住 50 这个数字,第二章算 MTU 全靠它。另外注意,外层 UDP 的源端口是按内层帧的哈希算出来的,这是为了让中间网络能做 ECMP 负载均衡——同一条流始终落到同一条路径上,避免乱序。这个设计细节在后面调性能的时候会提到。

1.3 手动搭 vs 上控制器:什么规模适合自己动手

我不否认控制器方案的价值,几百台节点、租户频繁增删的环境,手工维护 FDB 表项会疯掉。但判断标准其实很清晰:

  • 节点数在 10 台以内、拓扑变更不频繁:手工配置完全可行,写个脚本批量下发,成本远低于搭一套管理平面。
  • 需要精细控制、出了问题要能一眼看到底层:手工方式的排错链路是透明的,ip -d link、bridge fdb show、tcpdump三板斧下去,问题基本就定位了。
  • 节点数上百、FDB 需要动态学习或需要跨集群:这时候手工维护静态表项开始变成负担,多播模式又受限于底层网络是否放行组播,该考虑自动化方案了。

还有一类特殊场景适合手工:临时打通、故障演练、验证某个网络行为。建一个 vxlan 接口只要一条命令,删掉也只要一条,比申请配置变更快得多。我自己在做跨机房故障复现的时候,经常用这种方式在几台机器上快速搭出一个隔离环境。

2. 动手前必须确认的四件事:内核、MTU、Underlay 与地址规划

2.1 内核支持与 iproute2 版本差异

第一件事是确认内核带了 vxlan 模块。绝大多数发行版默认内核都编译了,但要确认:

modprobe vxlan lsmod | grep vxlan

如果没有输出,说明模块没加载成功,可能是内核裁剪过。检查CONFIG_VXLAN是否等于m或y:

grep CONFIG_VXLAN /boot/config-$(uname -r)

第二件事是iproute2的版本。这个坑我踩过不止一次:不同版本的ip命令对 vxlan 参数写法要求不一样。老版本认nolearning,新版本要用no learning或者干脆换成learning off,写成前者会直接报Error: unknown command。

我的做法是统一用最保守的写法,创建接口时只用最基础的参数,剩下的属性后续用bridge link set单独调整:

ip -V ip link add vxlan100 type vxlan id 100 dev eth0 dstport 4789 bridge link set dev vxlan100 learning off

这样写兼容性好,ip -V输出类似ip utility, iproute2-6.1.0这种版本信息,5.x 以上基本都能跑。另外建议安装bridge命令所属的包(通常和iproute2一起),后面查 FDB 表全靠它。

2.2 MTU 这笔账:为什么 1450 是关键数字

Underlay 网卡是标准 1500 的话,内层可用 MTU 就是:

1500 - 50 = 1450

所以 vxlan 接口的 MTU 要设成 1450,接 vxlan 的网桥也要设成 1450,挂在网桥上的业务接口同样得是 1450。如果业务侧还是 1500,发出去的包在 vxlan 接口上会被分片或者直接丢弃(取决于是否允许分片),表现就是"小包能通、大包死活不通"。

如果 Underlay 支持巨型帧(比如 9000),那内层可以用到 8950,这时候就不用纠结了。但现实是很多环境里中间路径的 MTU 并不统一,你以为链路是 9000,中间某台设备只有 1500,照样出问题。我一般保守按 1500 算,除非确认整条路径都放行了巨型帧。

配置方式:

ip link set dev vxlan100 mtu 1450 ip link set dev br100 mtu 1450

这里有个细节值得说:网桥的 MTU 取的是所有从属接口里的最小值,但 vxlan 接口创建时默认 MTU 还是 1500,所以顺序很重要——先把 vxlan 接口 MTU 改成 1450,再挂进网桥,否则网桥的 MTU 会被拉高。更稳妥的做法是每次都显式设置一遍,不要依赖自动继承。

2.3 Underlay 连通性与地址规划表

上 vxlan 之前,先确认两台机器的 Underlay 地址能直接 ping 通。VxLAN 隧道不负责底层连通性,它只是把二层帧塞进 UDP 里,UDP 走不通什么都没用。

ping -c 3 192.168.1.2

顺手确认一下 4789 端口没被占:默认的 VxLAN 目的端口是 4789(老的 IANA 分配是 8472,某些方案还在用)。如果机器上装了其他隧道组件,可能已经占用了这个端口,那就换个dstport,两端必须一致。

地址规划我习惯列个表,尤其是节点超过两台的时候,不写下来一定会乱:

主机Underlay IPVNIOverlay IPvxlan 接口名
主机A192.168.1.110010.10.100.1/24vxlan100
主机B192.168.1.210010.10.100.2/24vxlan100
主机C192.168.1.310010.10.100.3/24vxlan100

Overlay 网段和 Underlay 网段千万别重叠,这是新手最常见的错误之一。如果 Underlay 是 10.0.0.0/8,Overlay 就别用 10.x,换成 172.16.x 或者 192.168.x 里的小段。重叠之后路由表会认错方向,表现为"明明配了地址就是不通",而且极难排查。

3. 两台主机点对点打通:从建接口到 ping 通

3.1 建 vxlan 接口的完整命令与参数逐个拆解

先看最简形式的点对点配置。在主机A上:

ip link add vxlan100 type vxlan \ id 100 \ remote 192.168.1.2 \ local 192.168.1.1 \ dev eth0 \ dstport 4789 \ learning off

每个参数都得说清楚,因为漏一个就出问题:

  • id 100:VNI 编号,两端必须一致。不一致的话包能到对端,但对端内核发现 VNI 不匹配会直接丢掉,抓包能看到包进来,但接口上什么都没有。
  • remote 192.168.1.2:对端的 Underlay 地址。点对点模式下所有出向包都发给这个地址。
  • local 192.168.1.1:本机的 Underlay 源地址。这个参数在多网卡机器上必须显式指定,否则内核可能选错出接口和源地址,导致对端回包回不来。
  • dev eth0:出接口。配合local一起确认路径。
  • dstport 4789:目的端口,两端一致。
  • learning off:关闭自学习。手动模式下必须关,否则内核会尝试自己学习对端 MAC,反而产生不可控的表项。

建好之后看一眼详情,确认参数生效:

ip -d link show vxlan100

输出里会带一行vxlan id 100 remote 192.168.1.2 local 192.168.1.1 dev eth0 srcport 0 0 dstport 4789 ...之类的内容,重点核对 id、remote、local、dstport 四个值。这一步别省,我遇到过参数被静默忽略的情况(老版本对某些参数组合不生效),只看命令没报错不算数。

接着把接口拉起来:

ip link set dev vxlan100 up

3.2 网桥怎么挂:vxlan 口和业务口的关系

只建 vxlan 接口是没用的,它得和业务流量接上。有两种接法:

第一种,直接把 Overlay IP 配在 vxlan 接口上。适合只需要主机间三层互通的场景,配置最简单:

ip addr add 10.10.100.1/24 dev vxlan100

第二种,建一个网桥,把 vxlan 接口和业务网卡都挂进去。适合需要二层延伸的场景——比如虚拟机、容器要认为自己在一个二层网络里,或者业务侧要跑 ARP、DHCP 这类依赖广播的协议:

ip link add br100 type bridge ip link set dev br100 mtu 1450 ip link set dev vxlan100 master br100 ip link set dev eth1 master br100 ip link set dev br100 up

这里eth1是业务侧的物理或虚拟网卡。注意挂了同一个网桥的接口之间会互相转发,如果 eth1 又接到了一台干活儿的交换机,很容易形成环路。所以业务侧的接口最好是独立的,不要和主网卡混在一个网桥里。

还有个容易被忽略的点:vxlan 接口进网桥之前,要把它的 MAC 固定下来。

ip link set dev vxlan100 address 00:11:22:33:44:55

默认情况下内核会给 vxlan 接口生成一个随机 MAC,主机重启后会变。在点对点场景影响不大,但在多节点场景里,如果对端的静态 FDB 表项里写的是老 MAC,重连就断了。固定 MAC 是省事的好习惯,同主机多个 vxlan 接口要注意 MAC 不能重复。

3.3 静态 FDB 表项的写法与验证

learning off之后,转发靠的是 FDB 表。需要给 vxlan 接口加一条"泛洪"表项,告诉内核:所有未知的单播、广播、多播帧,都往这个目的地址送。

bridge fdb append 00:00:00:00:00:00 dev vxlan100 dst 192.168.1.2

00:00:00:00:00:00这个全零 MAC 是约定俗成的"未知目标"占位符,加了这个表项,BUM(Broadcast、Unknown unicast、Multicast)流量就会发往192.168.1.2。

如果想让某个具体 MAC 走指定隧道,加具体表项:

bridge fdb append aa:bb:cc:dd:ee:01 dev vxlan100 dst 192.168.1.2

查一下确认写入成功:

bridge fdb show dev vxlan100

正常应该看到类似这样的输出:

00:00:00:00:00:00 dst 192.168.1.2 self permanent 00:11:22:33:44:55 dev vxlan100 self permanent

第二行是本接口自己的 MAC,自动加的。self表示这条表项属于这个设备,permanent表示不会老化。

注意:bridge fdb append是追加操作,重复执行不会去重(有些版本会去重,有些不会)。写自动化脚本的时候用replace更稳妥,bridge fdb replace在表项存在时会覆盖。

主机B上做对称配置,只是remote换成 192.168.1.1,Overlay IP 换成 10.10.100.2。两边都建好之后,ping 一下 Overlay 地址:

ping -c 3 10.10.100.2

3.4 三层验证法:从 FDB 到 tcpdump

ping 不通的时候不要瞎猜,按层往下查效率最高。我总结了一个三层验证顺序:

第一层,查 FDB 表。bridge fdb show dev vxlan100里有没有那条全零表项?没有的话,包根本发不出去。如果有但dst地址写错了,也是白搭。

第二层,查 Underlay 抓包。在主机A的 eth0 上:

tcpdump -i eth0 -nn udp port 4789 -c 10

如果一条都抓不到,说明出向根本没走到物理网卡——可能是路由问题,或者local/dev参数指错了接口。如果抓得到包但看不到回包,问题在对端,去主机B上抓。

第三层,查内层抓包。在 vxlan 接口上抓:

tcpdump -i vxlan100 -nn icmp

这一层能看到的话,说明解封装是成功的,问题在接口之后的转发(比如网桥、路由表)。看不到就是封装/解封装阶段出了问题。

这三层走一遍,问题基本能锁死在某一环,比漫无目的地改配置快得多。我一般还会加一个 ARP 表检查:

ip neigh show dev vxlan100

如果 ARP 一直是INCOMPLETE状态,说明广播包没送到对端,直接回去查第一层的 FDB 表。

4. 从两台到多台:FDB 规模、多播组与多租户 VNI

4.1 静态 FDB 的规模边界与批量写入

三台以上的全互联拓扑,需要给每台主机的 vxlan 接口加上到其他所有节点的表项。节点数为 N 时,每台机器需要 N-1 条表项。五台机器就是每台四条,手工敲还能忍;二十台就是每台十九条,人工维护开始变得痛苦;五十台以上基本不可能靠手工跟上变更。

批量写入用脚本很简单,把节点列表存成一个文件:

#!/bin/bash # nodes.txt 每行一个 Underlay IP DEV="vxlan100" while read -r ip; do [ -z "$ip" ] && continue bridge fdb replace 00:00:00:00:00:00 dev "$DEV" dst "$ip" done < nodes.txt

用replace而不是append,重复执行不会产生重复表项,可以放心放进幂等脚本里。

这里有个规模上的经验值:单接口 FDB 表项在几百条以内性能没有明显变化,因为内核用的是哈希表。真正的问题不是性能,而是变更管理——增加一个节点要在所有存量节点上补一条,删除节点要回收所有相关表项,漏一个就会出现"部分节点通、部分节点不通"这种最难查的故障。所以我在节点数超过 10 台之后,基本都会把这份配置写进一个中心的配置源,由脚本统一渲染下发,而不是手工改。

4.2 多播组模式:能省事,但有两个硬限制

VxLAN 标准里定义了多播模式:不再逐条指定remote,而是加入一个组播组,BUM 流量由底层网络的多播转发给所有成员。

ip link add vxlan100 type vxlan \ id 100 \ group 239.1.1.1 \ dev eth0 \ dstport 4789 \ learning off

这样只写一条命令,所有加入 239.1.1.1 组的主机自动能收到广播。听起来很美,但实际有两个硬限制:

限制一,Underlay 必须完整支持组播。包括主机所在的接入交换机、中间所有路由设备、目的主机的接入设备,都要配置 PIM 之类的组播路由。很多环境出于安全或运维复杂度考虑,默认是把组播关掉的,一关就全断了。

限制二,组播是广播域的延伸,规模一大就是灾难。一个组播组里的主机越多,BUM 流量的复制份数越多,每一份都要占用带宽。几十台以内还能接受,上百台之后,一个 ARP 请求就要复制上百份,网络直接被自己的控制流量压垮。

所以多播模式我只在小规模、且确认底层网络放行组播的场景下用。跨机房的基本别想,公网更不可能。绝大多数实际部署,还是走静态 FDB 或者由控制平面下发 FDB 的模式。

另外补充一点:还有一种external模式,接口创建时指定external,表示 FDB 完全由外部程序(比如桥接插件)管理,内核不自动学习也不自动添加表项。这个模式适合有自研控制平面的场景,纯手工用不上,但看到别人配置里出现external时知道是什么意思就行。

4.3 同主机多 VNI:网桥、MAC 与命名规范

一台主机上跑多个租户的时候,需要多个 vxlan 接口,每个对应一个 VNI。典型配置是这样:

# 租户一 ip link add vxlan100 type vxlan id 100 remote 192.168.1.2 local 192.168.1.1 dev eth0 dstport 4789 learning off ip link add br100 type bridge ip link set dev vxlan100 master br100 ip link set br100 up # 租户二 ip link add vxlan200 type vxlan id 200 remote 192.168.1.2 local 192.168.1.1 dev eth0 dstport 4789 learning off ip link add br200 type bridge ip link set dev vxlan200 master br200 ip link set br200 up

关键点有几个:

网桥必须分开。同一个网桥里的接口属于同一个广播域,把 vxlan100 和 vxlan200 挂进同一个网桥,等于把两个 VNI 的流量在本地桥接了,隔离直接失效。

MAC 必须唯一。同主机上每个 vxlan 接口都要固定一个不同的 MAC,而且不能和网桥里其他接口冲突。我习惯按 VNI 编号生成,比如 VNI 100 对应00:11:22:00:01:00,一眼能看出归属。

命名要有规范。接口名vxlan{VNI}、网桥名br{VNI},这样ip link show一眼扫过去就知道有哪些租户在跑,出问题定位也快。Linux 接口名最长 15 个字符,vxlan16777215正好卡在边界上,实际规划 VNI 的时候留点余量。

还要提一个容易忽略的点:多个 vxlan 接口共用同一个 Underlay 出口时,源端口是各自独立算的,所以不会互相干扰。但如果 Underlay 出口带宽有限,多租户同时大流量传输会互相抢带宽,这时候需要在 Underlay 上做 QoS,而不是在 overlay 层想办法。

5. 五个真实踩过的坑:现象、排查链路与修复

5.1 接口全 up、FDB 也有,就是 ping 不通

这是最让人抓狂的一类问题。所有状态看起来都对:ip link显示state UNKNOWN(vxlan 接口正常状态就是 UNKNOWN,不是 UP,这点要知道,别误判),FDB 表项在,Underlay 能 ping 通,就是 overlay 不通。

排查链路我一般这么走:

第一步,在对端抓 Underlay 的 UDP 包:

tcpdump -i eth0 -nn udp port 4789 -c 5

如果对端收不到,问题在本端出向。回到本端确认ip -d link show vxlan100里的local地址是不是真的属于dev指定的那块网卡:

ip addr show eth0

这一步我遇到过两次,都是机器有多块网卡,local填了一个在另一块网卡上的地址,结果包从错误的接口发出去,对端收到后回包的路由对不上。

第二步,如果对端能收到包但不回,检查 VNI 是否一致。VNI 不一致时内核会静默丢弃,抓包能看到包进来,但没有任何错误日志。ip -d link show两边的输出对一遍,一分钟就能确认。

第三步,检查防火墙。很多发行版默认开着 firewalld 或 nftables,4789/UDP 被挡住是很常见的。临时关掉验证一下:

iptables -L -n | grep 4789 nft list ruleset | grep 4789

确认是防火墙问题的话,加放行规则而不是直接关服务:

iptables -I INPUT -p udp --dport 4789 -j ACCEPT

5.2 小包通大包不通:分片与 MTU 的连锁反应

这个坑的典型表现是:ping -c 3通,ping -s 1400不通,SSH 能连上但传文件卡死,HTTP 小请求正常大响应超时。本质就是 MTU 没对齐。

验证方法很直接,用-M do禁止分片,一点点加 payload 找出临界值:

ping -M do -s 1422 10.10.100.2

为什么是 1422?因为1422 + 8(ICMP 头)+ 20(IP 头)= 1450,正好是内层 MTU。如果这个通,说明内层 MTU 是 1450;如果 1423 不通而 1422 通,就确认了。

找到临界值之后,回头检查三处 MTU:

ip link show dev vxlan100 | grep mtu ip link show dev br100 | grep mtu ip link show dev eth1 | grep mtu

三处都应该是 1450(或者更小)。改的时候按顺序来:先改 vxlan,再改网桥,最后改业务接口。改完不需要重启接口,但已有的 TCP 连接不会自动重新协商 MSS,需要断开重连,或者依赖 PMTUD(路径 MTU 发现)慢慢收敛。有些环境里 ICMP 的fragmentation needed被防火墙挡了,PMTUD 失效,那就只能靠调小 MSS 来兜底:

iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

这条规则会把经过的 TCP SYN 包里的 MSS 改成路径 MTU 减 40,相当于在链路上强制对齐。我在几个客户环境里用过,比手工算 MSS 靠谱。

5.3 learning off 之后 FDB 表项不再自增

刚接触的时候我犯过一个错:建接口时加了learning off,然后以为 FDB 会自动填充,等了半天表里还是空的,ping 也不通。

原因很简单:learning off就是明确告诉内核"不要自己学",那 FDB 里就只有你手动加的那几条。这不是 bug,是设计。手动模式下,你必须保证两件事:

  • 至少有一条00:00:00:00:00:00的泛洪表项指向远端。
  • 如果要做单播优化,得把对端的 MAC 和 IP 对应关系也写进去。

反过来,如果用了learning on(默认值),FDB 会随流量自动增长。听起来方便,但在手工维护拓扑的环境里反而危险——学到错误的表项之后,流量会走错路径,而且这些动态表项会在老化后消失,导致行为不可预测。我现在的做法是一律learning off,全部静态管理,行为确定,出问题好查。

查看动态表项的残留:

bridge fdb show dev vxlan100 | grep -v permanent

有输出说明还有动态表项,可以考虑加bridge link set dev vxlan100 learning off再手动清一遍。

5.4 重启后全部消失:落地配置的三种做法

ip link add建的接口、bridge fdb加的表项,重启全没了。这是内存态配置,必须做持久化。三种做法,各有适用场景。

做法一:systemd-networkd 的 .netdev + .network 文件。适合用 networkd 管理网络的机器。/etc/systemd/network/10-vxlan100.netdev:

[NetDev] Name=vxlan100 Kind=vxlan MTUBytes=1450 [VXLAN] Id=100 Local=192.168.1.1 Remote=192.168.1.2 DestinationPort=4789

再配一个10-br100.netdev定义网桥,10-br100.network把接口挂进去。FDB 表项的持久化 networkd 支持得不太完整,需要配合ExecStartPost或者一个 oneshot 服务来做,所以我一般会在网桥起来之后跑一个脚本补 FDB。

做法二:NetworkManager 的 nmcli。适合桌面环境或使用 NM 的服务器:

nmcli con add type vxlan con-name vxlan100 ifname vxlan100 \ id 100 remote 192.168.1.2 local 192.168.1.1 dev eth0 \ dstport 4789 nmcli con modify vxlan100 vxlan.learning off nmcli con up vxlan100

NM 的好处是所有配置都在一个地方,nmcli con show一目了然,坏处是 FDB 表项同样得靠脚本。

做法三:自己写一个 systemd service 跑初始化脚本。最灵活,也最适合手工模式。脚本里把建接口、挂网桥、加 FDB 全部写全,用Type=oneshot+RemainAfterExit=yes挂在network-online.target之后。我在多节点环境里统一用这种方式,因为可以把节点列表从一个配置文件读进来批量渲染 FDB。

不管哪种做法,都要验证一遍:配置写完systemctl restart systemd-networkd或者 reboot 一次,然后跑一遍完整的验证清单(接口在、IP 在、FDB 在、能 ping 通)。不做这一步,等于没做持久化。

5.5 校验和 offload 造成的"假丢包"

这个坑最阴,因为现象是"抓包看到包了,但对端应用收不到",而且只在特定机器上出现。

根因在网卡的校验和卸载。VxLAN 封装之后,内层帧的校验和由硬件计算,抓包工具在网卡层面抓到的包,校验和字段是"占位"的无效值(通常显示incorrect)。更麻烦的是某些网卡驱动在 VxLAN 场景下的 offload 行为不一致,包在发出时校验和没算对,对端直接丢了,而你在本机抓包看到的是完整的包,完全看不出问题。

排查方法是在对端抓包,并用-vv看校验和:

tcpdump -i eth0 -nn -vv udp port 4789 -c 5

如果看到bad udp cksum或者incorrect,基本就是这个问题。验证性修复是关掉相关 offload:

ethtool -K eth0 tx off ethtool -K eth0 rx off

或者只关校验和相关的:

ethtool -K eth0 tx-checksum-ip-generic off

关了之后如果通了,说明确实卡在 offload 上。但不要长期关着,关掉之后校验和靠 CPU 算,吞吐会明显下降。正确做法是升级网卡驱动或固件,或者调整 VxLAN 接口的tx校验和设置:

ip link set dev vxlan100 tx checksum off

具体哪个选项有效,取决于网卡型号和驱动版本,得实测。我在两台不同代的网卡上遇到过这个问题,其中一台升级驱动就解决了,另一台只能在内核启动参数里加网卡相关的 quirk。这类问题没有通用解,只能一层层试。

6. 把手动方案压到极致:性能调优与基线测试

6.1 GSO/GRO 与 tx-checksumming 的实际影响

VxLAN 的性能瓶颈主要在封装和解封装上。每发一个包,内核都要加 50 字节的头、算哈希决定源端口,这些是纯 CPU 开销。现代网卡支持 VxLAN 相关的 offload,可以把一部分工作交给硬件。

先看当前状态:

ethtool -k eth0 | grep -E 'tx-checksum|generic-receive-offload|tcp-segmentation-offload|tx-udp-segmentation'

重点关注tx-udp-segmentation(UDP 分段卸载,VxLAN 场景下非常关键)和tx-checksum-ip-generic。这两个开着的话,大包的封装和校验和都由硬件处理,CPU 占用能降一大截。

如果没有硬件支持,可以退而求其次,调整 vxlan 接口的 GSO 参数:

ip link set dev vxlan100 gso_max_size 65536 ip link set dev vxlan100 gro_max_size 65536

这两个参数控制的是单个 skb 能聚合的最大尺寸,调大之后可以减少包数量,降低 per-packet 开销。实测在纯软件路径下,调到 64K 能提升 15%~30% 的吞吐,具体幅度看 CPU 和包大小。

还有txqueuelen。vxlan 接口默认队列长度不大,高吞吐场景下容易丢包:

ip link set dev vxlan100 txqueuelen 5000

这个值不是越大越好,太大反而增加延迟。我的经验是先调到 5000 观察,如果ip -s link show vxlan100里还有 dropped,再往上加。

6.2 用 iperf3 和 ping -M do 建基线

调优之前先建基线,不然改了什么都不知道有没有效果。两个工具就够了。

第一个是ping -M do确认 MTU 边界,前面说过,不再重复。

第二个是iperf3测吞吐。在对端起服务端:

iperf3 -s

本端跑测试:

iperf3 -c 10.10.100.2 -t 30 -P 4

-P 4起 4 条并行流,单流容易被 TCP 窗口限制,测不出真实带宽。记录下初始的 Gbits/sec,然后依次开启 offload、调整 GSO、调 txqueuelen,每改一项测一次,做成表格对比。

我自己的实测数据(两台同代服务器、万兆网卡、1500 MTU):

配置吞吐CPU 单核占用
默认2.1 Gbps85%
开 tx-udp-segmentation6.8 Gbps45%
再加 gso_max_size=655367.4 Gbps38%
再加 txqueuelen=50007.5 Gbps36%

可以看到最大的一跳是 UDP 分段卸载,开了之后提升三倍多。这也是为什么我一直强调先看ethtool -k的输出,硬件能力决定了上限。

还有一点:测试要用大包。iperf3默认的缓冲区大小在小包场景下测不出封装开销的影响。加-l 1400或者-M 1400用接近 MTU 的包长,更贴近真实业务。

6.3 手动维护的成本曲线:什么时候该换方案

最后说说我的判断标准。手动方案的成本不是线性的,它在某个点上会突然变陡:

  • 1~5 台:手工配置半小时搞定,改一次五分钟,完全没问题。
  • 6~15 台:需要脚本辅助,FDB 批量下发,配置纳入版本管理。运维成本每周几十分钟量级。
  • 16~50 台:需要配置中心渲染,变更要有人审核,故障排查需要拓扑图辅助。开始出现"改了 A 忘了 B"的事故。
  • 50 台以上:静态 FDB 的维护成本超过收益,该上控制平面了。这时候的问题不是技术难度,是人脑记不住这么多状态。

还有一个隐藏成本:故障定位。手工方案的好处是每一层都看得见,坏处是没有统一视图,出问题要一台台机器登上去看。节点少的时候这是优势,节点多了就变成负担。我在 20 台左右的环境里就开始用脚本定期采集所有节点的ip -d link show、bridge fdb show、ip neigh show存成快照,出问题时对比前后差异,比一台台登上去快得多。

另外一个容易忽略的点是变更窗口。手工改配置没有事务性,改到一半失败了,网络就处于半通状态。我的做法是把每一步都写成幂等的脚本,先在新节点上完整跑一遍,确认没问题再推到存量节点,出问题可以立即回滚——回滚也是跑一遍脚本,只是参数换回旧值。

我在几个小规模环境里用纯手工 VxLAN 跑了两年多,中间经历过节点增删、VNI 扩容、底层网络换设备,总体还算稳。真正让我觉得难受的不是配置本身,而是排查跨节点问题时那种"要一台台去看"的无力感。后来我们加了定时快照采集,才把这块补上。如果你也打算手工搭,建议一开始就把节点清单和配置模板管起来,别等到十几台的时候再回头补,那时候补的成本是刚开始的好几倍。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询