Skip to content

常见坑合集 ​

这一章把整本教程里踩过的坑集中列出来。每一条都是实验环境里真实遇到并验证过的,不是抄来的清单。

按"你会在什么时候踩到"分类。

概念混淆 ​

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 端口号
br0LOCAL(65534)1
eth112
eth223

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                   # 才看到 error
text
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。

隧道 ​

三件事必须先确认 ​

按这个顺序,不要跳:

  1. underlay 通吗 —— ping <对端 underlay 地址>
  2. 两端 VNI 一致吗 —— 不一致时能抓到包但业务不通
  3. 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_ratekbit/s
Queue.max-ratebit/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.txt

trace 字段没写全 ​

不写 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:$PATH

netlab 的锁文件路径不展开 ~ ​

defaults.multilab.lock 必须写绝对路径。netlab 拿到值后直接 Path().resolve(),不做 ~ 展开,写 ~/.netlab/lab.lock 会报:

text
flock: cannot open lock file .../~/.netlab/lab.lock: No such file or directory

netlab down 依赖快照文件 ​

netlab.snapshot.pickle 丢了以后 netlab down 会直接报错,连 --force 也救不了。

恢复办法:用 topology.yml 重新生成一份快照再拆:

bash
netlab create --defaults <defaults文件>
netlab down --cleanup

netlab 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 判断存在性 —— 那样会一直用旧镜像。

小结 ​

坑最集中的四个地方:

  1. add-port 不拉接口 —— "配置全对但不通"的头号原因
  2. 两套端口编号 —— ofctl 和 dpctl 的数字含义完全不同
  3. 匹配四层字段忘写协议前缀 —— 报错最多的语法问题
  4. 隧道 MTU —— "ping 通但传文件卡死"的唯一常见原因

外加一个特别难查的:两端都是 OVS 却用了 balance-slb —— 所有状态看着都正常,就是时通时不通。

排查时的三条铁律:

  • 看 ovs-vsctl show 里有没有 error:
  • ofproto/trace 正反两个方向都要跑
  • 修改 datapath 一律用 ovs-appctl dpctl/

下一章:性能观测与调优。