主题
OVS 的三个进程
上一章讲到 OVS 用"用户态决策 + 内核态缓存"的两级结构来兼顾灵活和性能。这一章把它落到具体的进程、套接字和命令上。
装完 OVS,机器上多了什么
在实验环境的交换机容器里看一眼进程(输出略作换行):
bash
docker exec clab-first-bridge-ovs1 ps -eo pid,args | grep ovstext
31 ovsdb-server /etc/openvswitch/conf.db
--remote=punix:/var/run/openvswitch/db.sock
--log-file=/var/log/openvswitch/ovsdb-server.log
--pidfile=/var/run/openvswitch/ovsdb-server.pid --detach
46 ovs-vswitchd unix:/var/run/openvswitch/db.sock
--mlockall
--log-file=/var/log/openvswitch/ovs-vswitchd.log
--pidfile=/var/run/openvswitch/ovs-vswitchd.pid --detach只有两个进程。第三个部分不是进程,是内核里的 openvswitch 模块。
bash
grep -w openvswitch /proc/modulestext
openvswitch 225280 0 - Live 0x0000000000000000于是完整的图是这样:
三者各管什么
ovsdb-server —— 配置数据库
一个小型的事务型数据库进程,存的是你希望这台交换机是什么样子:有哪些网桥、每个网桥上有哪些端口、端口是什么类型、隧道对端是谁、QoS 怎么配。
它有 20 张表:
bash
docker exec clab-first-bridge-ovs1 ovsdb-client list-tablestext
Open_vSwitch Bridge Port Interface Controller
Mirror NetFlow sFlow IPFIX Flow_Table
QoS Queue SSL Manager Datapath
CT_Zone CT_Timeout_Policy Flow_Sample_Collector_Set
AutoAttach最核心的是 Open_vSwitch → Bridge → Port → Interface 这条链,实验 2 会专门拆它。
关键点:配置写进数据库就落盘了。数据库文件在这里:
text
/etc/openvswitch/conf.db -> /var/lib/openvswitch/conf.db所以重启 ovs-vswitchd 甚至重启机器,你建的网桥和端口都还在。这和 ip link add 建出来的东西重启就没了是完全不同的模型。
流表不在数据库里
一个常见误解:既然配置都持久化,那流表也持久化吧?不是。
用 ovs-ofctl add-flow 加的流表项存在 ovs-vswitchd 的内存里,进程重启就没了。数据库里的 Flow_Table 表存的是流表的属性(比如最大条目数),不是流表项本身。
生产环境里流表由控制器在连接建立后重新下发,这是设计使然。
ovs-vswitchd —— 转发决策大脑
真正做转发决策的进程。它:
- 从
ovsdb-server读配置,据此创建网桥、把网口挂进 datapath - 持有完整的 OpenFlow 流表,实现 OpenFlow 协议
- 处理内核送上来的"这个包怎么办"(upcall),算出动作,并把结论装进内核缓存
- 做 MAC 学习(
NORMAL动作背后的 MAC 表在这里,不在内核) - 把运行时状态回写数据库(比如接口的实际 MAC、链路状态、统计)
它启动时的第一个参数就是数据库的 socket:unix:/var/run/openvswitch/db.sock。ovs-vswitchd 是 ovsdb-server 的客户端 —— 这个从属关系决定了启动顺序不能颠倒。
openvswitch 内核模块 —— datapath
只干一件事,但要干得极快:
- 从网口拿到包,提取 key(进入端口 + 各层协议字段)
- 拿 key 去查缓存
- 命中 → 直接执行缓存里的动作,包走掉,全程不出内核
- 未命中 → 把包交给
ovs-vswitchd问怎么办
注意它是缓存,不是流表。里面没有你写的 OpenFlow 规则,只有 ovs-vswitchd 算好的结论。
哪个命令跟谁说话
这张表建议记住。初学 OVS 时最常见的困惑就是"我该用 vsctl 还是 ofctl"。
| 命令 | 对话对象 | 通道 | 管什么 | 典型用法 |
|---|---|---|---|---|
ovs-vsctl | ovsdb-server | db.sock | 配置:网桥、端口、接口 | add-br / add-port / show |
ovs-ofctl | ovs-vswitchd | br0.mgmt(OpenFlow) | 流表 | dump-flows / add-flow |
ovs-appctl | ovs-vswitchd | ovs-vswitchd.PID.ctl | 运行时状态与调试 | fdb/show / ofproto/trace |
ovs-dpctl | 内核 datapath | netlink | 内核缓存 | show / dump-flows |
ovsdb-client | ovsdb-server | db.sock | 直接读写数据库 | list-tables / dump |
这些通道就是 /var/run/openvswitch/ 下面那堆 socket 文件:
bash
docker exec clab-first-bridge-ovs1 ls -l /var/run/openvswitch/text
srwxr-x--- br0.mgmt ← ovs-ofctl 走这个(每个网桥一个)
srwxr-x--- br0.snoop ← ovs-ofctl snoop 用,旁听 OpenFlow 消息
srwxr-x--- db.sock ← ovs-vsctl / ovsdb-client 走这个
srwxr-x--- ovs-vswitchd.46.ctl ← ovs-appctl 走这个(46 是进程号)
srwxr-x--- ovsdb-server.31.ctl ← ovs-appctl 也能管数据库进程
-rw-r--r-- ovs-vswitchd.pid
-rw-r--r-- ovsdb-server.pid一个实用推论
ovs-vsctl 卡住不返回,通常是 ovsdb-server 挂了或 db.sock 不在。 ovs-ofctl 报错连不上,通常是 ovs-vswitchd 挂了或网桥不存在。
从报错的命令就能定位是哪个进程出了问题 —— 这比盲目重启有效得多。
一个包的完整旅程
现在把两级结构走一遍。假设 h1 第一次 ping h2。
关键在于第一个包和后面的包走的是完全不同的路径。 这解释了两个常见现象:
- 第一个 ping 的 RTT 明显比后面高(要走用户态)
- 流表改了以后,已经建立的流不一定立刻生效(内核缓存还在)
你会亲手验证这件事
实验 1 里我们清空内核缓存后立刻 ping 5 个包,第一个包的 RTT 稳定地比后面四个高一截 —— 那就是它多跑了一趟用户态留下的痕迹。
内核缓存里存的不是"一条流一条记录"
如果内核缓存按"精确的五元组"存,那扫端口这种场景会瞬间塞满几万条。OVS 的解法是带掩码的缓存项(megaflow):只把这次决策真正用到的字段记进匹配条件,其余字段通配。
看实验里的真实输出:
text
recirc_id(0),in_port(2),eth(src=aa:c1:ab:2b:d1:01,dst=aa:c1:ab:28:bc:4c),
eth_type(0x0800),ipv4(frag=no), packets:4, bytes:392, actions:3这条缓存项匹配的是"从 2 口进、这一对 MAC、IPv4、未分片"。注意它没有记 IP 地址和端口号 —— 因为 NORMAL 只按 MAC 转发,压根没看那些字段。于是这两台主机之间所有的 IPv4 流量,无论多少条 TCP 连接,都由这一条缓存项覆盖。
反过来说,流表写得越细,缓存项就越细,缓存条数就越多。这是 OVS 性能调优里最核心的一条因果关系,第 6 部分会详细讲。
datapath 不一定在内核里
上面说的是最常见的 kernel datapath(system 类型),用内核 openvswitch 模块,也是本教程实验的默认形态。ovs-dpctl show 的第一行会告诉你类型:
text
system@ovs-system: ← system 表示内核 datapath另一种是 userspace datapath(netdev 类型),转发也在 ovs-vswitchd 进程里做,配合 DPDK 或 AF_XDP 绕过内核协议栈,用于追求极高吞吐的场景。架构上的差别只在最下面那一层,上面的数据库、流表、命令全都一样。这个留到第 5 部分。
小结
- OVS 有两个用户态进程加一个内核模块
ovsdb-server存配置,落盘持久化;ovs-vswitchd做转发决策,流表在内存里不持久化- 内核 datapath 是缓存,不是流表;它只执行
ovs-vswitchd算好的结论 - 四个命令对应四个不同的对话对象,报错时能直接指向出问题的组件
- 第一个包走慢路径(upcall),后续包走快路径(内核缓存命中)
- 缓存项是带掩码的,一条能覆盖一大类包
下一章:搭建实验环境,然后就可以动手了。