主题
常见坑合集
这一章把整本教程里踩过的坑集中列出来。每一条都是实验环境里真实遇到并验证过的,不是抄来的清单。
按"你会在什么时候踩到"分类。
概念混淆
ovs-system 不是网桥
add-br 之后 ip link 里出现的 ovs-system 是内核 datapath 的宿主设备,不对应任何网桥。
整台机器上所有 OVS 网桥共用一个 ovs-system。 建 10 个网桥也只有一个。
永远不要动它
bash
ip link set ovs-system down # 会影响整机所有 OVS 网桥详见实验 1。
OVS 网桥不是 Linux bridge
bash
brctl show # 看不到 OVS 网桥
bridge link # 也看不到内核里 OVS 是 datapath + vport,不注册成 bridge 设备。
反过来 ip link 能看到 br0,那是因为 internal 端口确实是个 netdev。能被 ip link 看到不代表它是 Linux bridge。
两套端口编号
| 接口 | OpenFlow 端口号 | datapath 端口号 |
|---|---|---|
br0 | LOCAL(65534) | 1 |
eth1 | 1 | 2 |
eth2 | 2 | 3 |
ovs-ofctl 里的 output:2 是 eth2,ovs-dpctl 里的 actions:2 是 eth1。同一个数字,完全不同的意思。
记法:ofctl 的数字是 OpenFlow 编号,dpctl 的数字是 datapath 编号。 有疑问就查:
bash
ovs-ofctl dump-ports-desc br0 # OpenFlow 编号
ovs-dpctl show | grep port # datapath 编号详见实验 1。
内核 datapath 里的流表不是你的流表
ovs-dpctl dump-flows 显示的是缓存,是 ovs-vswitchd 算好的结论,里面没有你写的 OpenFlow 规则。
改流表要用 ovs-ofctl,看流表也要用 ovs-ofctl。
配置操作
add-port 不会拉起接口
bash
ovs-vsctl add-port br0 eth1
# 还需要
ip link set eth1 up这是"配置看起来全对但不通"的头号原因。
add-port 一个不存在的设备不会立刻报错
bash
ovs-vsctl add-port br0 eth9 # 命令成功返回
ovs-vsctl show # 才看到 errortext
Port eth9
Interface eth9
error: "could not open network device eth9 (No such device)"排查时一定要看 ovs-vsctl show 里有没有 error:。
VLAN 配在 Port 表,不是 Interface 表
bash
ovs-vsctl set Port eth1 tag=100 # 对
ovs-vsctl set Interface eth1 tag=100 # 错,不生效同理 bond_mode、qos、trunks 都在 Port 层。端口类型、隧道参数在 Interface 层。
详见实验 2。
patch 端口必须成对,且必须在一个事务里建
bash
# 正确
ovs-vsctl add-port br-int patch0 \
-- set Interface patch0 type=patch options:peer=patch1
# 错误:分两条命令
ovs-vsctl add-port br-int patch0 # 这一瞬间它是普通端口
ovs-vsctl set Interface patch0 type=patch options:peer=patch1分开写的话,中间那一瞬间 OVS 会去找一块叫 patch0 的真网卡然后报错。
@ 符号必须先定义后使用
配 Mirror、QoS 时:
bash
# 正确:先 get 拿 UUID,再 create 去引用
ovs-vsctl \
-- --id=@p get Port eth1 \
-- --id=@m create Mirror name=m0 select-src-port=@p ...
# 错误:顺序反了
ovs-vsctl \
-- --id=@m create Mirror name=m0 select-src-port=@p ... \
-- --id=@p get Port eth1报错很明确:
text
row id "@p" specified on "get" command was used before it was defined详见实验 12。
tag 和 trunks 不要同时设
改 access 口成 trunk 前先清掉 tag:
bash
ovs-vsctl clear Port eth4 tag
ovs-vsctl set Port eth4 trunks=100,200同时设了两个,行为取决于 vlan_mode,容易配出意外结果。
流表
流表为空 = 全部丢弃
bash
ovs-ofctl del-flows br0 # 不带参数,连默认的 NORMAL 一起删OpenFlow 是默认拒绝。生产环境上这条命令等于断网。
补回来:
bash
ovs-ofctl add-flow br0 "priority=0,actions=NORMAL"匹配四层字段必须先限定协议
bash
tp_dst=80 # 无效
tcp,tp_dst=80 # 正确
nw_dst=1.2.3.4 # 无效
ip,nw_dst=1.2.3.4 # 正确这是新手最常见的报错来源。
actions=drop 不能和其它动作并列
drop 其实是"动作列表为空"的语法糖,这两条等价:
text
actions=drop
actions=所以 actions=drop,output:2 会报错。
MAC 学习是 NORMAL 的一部分
删掉 actions=NORMAL,MAC 表就不再增长了。转发完全由你写的规则决定。
删掉之后旧的 MAC 表条目还会留 300 秒才老化,验证时先清一下:
bash
ovs-appctl fdb/flush br0详见实验 6。
goto_table 必须是最后一条指令
bash
actions=goto_table:1,output:3 # 报错text
invalid instruction ordering: apply_actions must appear before goto_table需要"跳过去还能回来"就用 resubmit(实验 7)。
多级流表要先启用 OpenFlow 1.3
bash
ovs-vsctl set Bridge br0 protocols=OpenFlow10,OpenFlow13
ovs-ofctl -O OpenFlow13 add-flow br0 "table=0,actions=goto_table:1"命令行也要带 -O OpenFlow13,两处都不能漏。
strip_vlan 在 OF1.3 下显示成 pop_vlan
bash
ovs-ofctl -O OpenFlow13 add-flow br0 "...,actions=strip_vlan,output:1"
ovs-ofctl -O OpenFlow13 dump-flows br0 | grep strip_vlan # 找不到
ovs-ofctl -O OpenFlow13 dump-flows br0 | grep pop_vlan # 在这里同理 mod_vlan_vid:100 显示成 set_field:4196->vlan_vid(0x1000 是 VLAN present 位,低 12 位才是 ID)。
push_vlan 之后必须跟 mod_vlan_vid
push_vlan 只插一个空标签,ID 是 0。漏了 mod_vlan_vid,后面按 dl_vlan=100 匹配的规则一条都命中不了。
output 到入口端口会被自动跳过
text
output:1
>> skipping output to input port
output:2这是 OpenFlow 标准语义。写泛洪规则时可以把所有成员口都列上,不用为每个入口单独写一条。
真要原路发回用 output:IN_PORT。
流表不持久化
ovs-ofctl add-flow 加的规则在 ovs-vswitchd 内存里,进程重启就没了。数据库里的网桥、端口配置会保留。
详见实验 2。
Port.tag 在 NORMAL 内部生效,流表看不到它
bash
ovs-appctl ofproto/trace br-int "in_port=1,..."text
Flow: in_port=1,vlan_tci=0x0000, ... ← tag=100 的口,但这里是 0所以在同一个网桥里写 dl_vlan=100 的规则永远匹配不上。
这是 OpenStack 必须把 br-int 和 br-tun 拆成两个网桥的根本原因 —— 帧穿过 patch 之后才带着真实的 VLAN 标签。
详见实验 15。
两端都是 OVS 时不能用 balance-slb
balance-slb 专为"对端交换机不做聚合"的场景设计,完全不和对端协商。
bond 的广播和未知单播只从一个成员(active member)发出,接收端也只接受来自对应成员的广播。两端各自独立挑 active member,挑的结果可能对不上 —— ARP 被丢弃。
症状很有迷惑性:MAC 表两边都学对了、bond/show 一切正常、断掉一条线反而通了,但两条线都在时时通时不通。
bash
# 两端都要改
ovs-vsctl set Port bond0 bond_mode=balance-tcp lacp=active
ovs-appctl lacp/show bond0 | grep synchronized # 确认协商成功详见实验 11。
隧道
三件事必须先确认
按这个顺序,不要跳:
- underlay 通吗 ——
ping <对端 underlay 地址> - 两端 VNI 一致吗 —— 不一致时能抓到包但业务不通
remote_ip互指了吗
underlay 地址不能接进网桥
underlay 要用 Linux 的 IP 路由。接进 OVS 网桥就变成二层端口了,协议栈拿不到它。
分工:underlay 归 Linux 协议栈,overlay 归 OVS。
MTU:小包通、大包死
隧道封装吃掉 50 字节(VXLAN)。虚拟机还按 1500 发,封装后 1550 超过 underlay MTU,加上 OVS 默认设 DF 位 → 直接丢弃。
确诊:
bash
ping -c 2 -M do -s 1400 <对端> # 通
ping -c 2 -M do -s 1472 <对端> # 不通 → 确诊解法:underlay 上巨帧(推荐),或 overlay MTU 调到 1450。
抓包要抓 underlay 接口
bash
tcpdump -i <underlay接口> -n -l udp port 4789 # 能看到封装
tcpdump -i vxlan_sys_4789 ... # 只能看到解封装后的内层帧所有 VXLAN 共用一个 datapath 设备
ovs-dpctl show 里只有一个 vxlan_sys_4789,不管你建了多少隧道端口。区分靠包携带的隧道元数据。
有状态与 QoS
ct_state 必须写 trk
text
ct_state=+est # 报错
ct_state=+est+trk # 正确text
invalid connection state: If "trk" is unset, no other flags are set用了 conntrack 必须单独放行 ARP
ARP 不是 IP 协议,连接跟踪不处理它。漏了这条,所有东西都不通,而且症状看起来像防火墙规则写错。
bash
ovs-ofctl add-flow br0 "table=0,priority=200,arp,actions=NORMAL"漏了 commit,出站连接建立不起来
只有 ct(commit) 过的连接,回包才会被识别成 +est。漏了它回包一直是 +new,然后被入站规则挡掉。
QoS 两处单位不一样
| 字段 | 单位 |
|---|---|
ingress_policing_rate | kbit/s |
Queue.max-rate | bit/s |
ingress_policing_rate=20000 是 20 Mbit/s;max-rate=20000000 才是 20 Mbit/s。
policing 下 TCP 跑不满配置值
实测配 20 Mbit/s 只跑到 11.9 Mbit/s。因为 policing 是丢包,TCP 会退避。
要给业务做带宽保证用 QoS + Queue(shaping),实测 50M 能跑到 49.7M。
详见实验 17。
配好队列还要用 set_queue 指过去
bash
ovs-ofctl add-flow br0 "priority=100,ip,actions=set_queue:1,NORMAL"不设的话流量全走 0 号(Default)队列。排查时看:
bash
ovs-appctl qos/show <端口>tx_packets 全在 Default 就说明规则没命中。
clear Port qos 会留下孤儿记录
bash
ovs-vsctl clear Port eth2 qos # 只解除引用
ovs-vsctl list QoS # 记录还在
ovs-vsctl -- --all destroy QoS -- --all destroy Queue # 彻底清工具使用
一律用 ovs-appctl dpctl/,不要用 ovs-dpctl 做修改
ovs-dpctl 直接走 netlink 操作内核,绕过 ovs-vswitchd,会造成两边状态不一致,出现反复 upcall。
用户态 datapath 下 ovs-dpctl 完全看不到东西。
只读查看(ovs-dpctl show)没问题。
脚本里 tcpdump 必须加 -l
块缓冲会导致被 timeout 杀掉时缓冲区内容全丢,文件里只剩开头两行提示 —— 看起来像"什么都没抓到"。
bash
timeout 8 tcpdump -i eth1 -n -l icmp > /tmp/cap.txttrace 字段没写全
不写 dl_type 它就是 0,ip 规则永远匹配不上。用十六进制报文最保险:
bash
ovs-appctl ofproto/trace br0 in_port=1 <报文十六进制>只 trace 单向
"能 ping 通"需要来回都有规则。反向也要 trace。
patch 端口抓不了包
它不是 Linux 设备。用端口镜像,或临时换成 veth 对。
流表是空的但还在转发
fail_mode=standalone + 控制器断开时,OVS 装了一条优先级 15790320 的隐藏 NORMAL 规则。dump-flows 看不到,只有 ofproto/trace 能看到。
控制器断开后回落不是立刻的
实测约 15 秒(要等探测超时和重连退避)。测异步状态变化要轮询,不要猜一个 sleep 时间。
实验环境相关
这几条是搭本教程实验环境时踩到的,用 netlab 的话会遇到。
netlab 不在 PATH 里
它装在 Python 虚拟环境里:
bash
export PATH=$HOME/.venvs/netlab/bin:$PATHnetlab 的锁文件路径不展开 ~
defaults.multilab.lock 必须写绝对路径。netlab 拿到值后直接 Path().resolve(),不做 ~ 展开,写 ~/.netlab/lab.lock 会报:
text
flock: cannot open lock file .../~/.netlab/lab.lock: No such file or directorynetlab down 依赖快照文件
netlab.snapshot.pickle 丢了以后 netlab down 会直接报错,连 --force 也救不了。
恢复办法:用 topology.yml 重新生成一份快照再拆:
bash
netlab create --defaults <defaults文件>
netlab down --cleanupnetlab status --cleanup 会交互询问
没有 --force 之类的开关,脚本里要喂一个 y:
bash
printf 'y\n' | netlab status -i 47 --cleanup同一台机器跑多个 netlab 实验会冲突
默认所有实验都登记为 default 实例,且共用管理网段。用 multilab 插件分开(见 labs/common/netlab-defaults.yml)。
改了 Dockerfile 但镜像没重建
本教程的 ovslab_build_images 按构建上下文的内容哈希打标签,改了 Dockerfile 会自动重建。如果你自己写脚本,注意别只用 :latest 判断存在性 —— 那样会一直用旧镜像。
小结
坑最集中的四个地方:
add-port不拉接口 —— "配置全对但不通"的头号原因- 两套端口编号 ——
ofctl和dpctl的数字含义完全不同 - 匹配四层字段忘写协议前缀 —— 报错最多的语法问题
- 隧道 MTU —— "ping 通但传文件卡死"的唯一常见原因
外加一个特别难查的:两端都是 OVS 却用了 balance-slb —— 所有状态看着都正常,就是时通时不通。
排查时的三条铁律:
- 看
ovs-vsctl show里有没有error: ofproto/trace正反两个方向都要跑- 修改 datapath 一律用
ovs-appctl dpctl/
下一章:性能观测与调优。