Skip to content

OVS 的三个进程

上一章讲到 OVS 用"用户态决策 + 内核态缓存"的两级结构来兼顾灵活和性能。这一章把它落到具体的进程、套接字和命令上。

装完 OVS,机器上多了什么

在实验环境的交换机容器里看一眼进程(输出略作换行):

bash
docker exec clab-first-bridge-ovs1 ps -eo pid,args | grep ovs
text
 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/modules
text
openvswitch 225280 0 - Live 0x0000000000000000

于是完整的图是这样:

三者各管什么

ovsdb-server —— 配置数据库

一个小型的事务型数据库进程,存的是你希望这台交换机是什么样子:有哪些网桥、每个网桥上有哪些端口、端口是什么类型、隧道对端是谁、QoS 怎么配。

它有 20 张表:

bash
docker exec clab-first-bridge-ovs1 ovsdb-client list-tables
text
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.sockovs-vswitchdovsdb-server 的客户端 —— 这个从属关系决定了启动顺序不能颠倒。

openvswitch 内核模块 —— datapath

只干一件事,但要干得极快:

  1. 从网口拿到包,提取 key(进入端口 + 各层协议字段)
  2. 拿 key 去查缓存
  3. 命中 → 直接执行缓存里的动作,包走掉,全程不出内核
  4. 未命中 → 把包交给 ovs-vswitchd 问怎么办

注意它是缓存,不是流表。里面没有你写的 OpenFlow 规则,只有 ovs-vswitchd 算好的结论。

哪个命令跟谁说话

这张表建议记住。初学 OVS 时最常见的困惑就是"我该用 vsctl 还是 ofctl"。

命令对话对象通道管什么典型用法
ovs-vsctlovsdb-serverdb.sock配置:网桥、端口、接口add-br / add-port / show
ovs-ofctlovs-vswitchdbr0.mgmt(OpenFlow)流表dump-flows / add-flow
ovs-appctlovs-vswitchdovs-vswitchd.PID.ctl运行时状态与调试fdb/show / ofproto/trace
ovs-dpctl内核 datapathnetlink内核缓存show / dump-flows
ovsdb-clientovsdb-serverdb.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 datapathsystem 类型),用内核 openvswitch 模块,也是本教程实验的默认形态。ovs-dpctl show 的第一行会告诉你类型:

text
system@ovs-system:      ← system 表示内核 datapath

另一种是 userspace datapathnetdev 类型),转发也在 ovs-vswitchd 进程里做,配合 DPDK 或 AF_XDP 绕过内核协议栈,用于追求极高吞吐的场景。架构上的差别只在最下面那一层,上面的数据库、流表、命令全都一样。这个留到第 5 部分。

小结

  • OVS 有两个用户态进程一个内核模块
  • ovsdb-server 存配置,落盘持久化ovs-vswitchd 做转发决策,流表在内存里不持久化
  • 内核 datapath 是缓存,不是流表;它只执行 ovs-vswitchd 算好的结论
  • 四个命令对应四个不同的对话对象,报错时能直接指向出问题的组件
  • 第一个包走慢路径(upcall),后续包走快路径(内核缓存命中)
  • 缓存项是带掩码的,一条能覆盖一大类包

下一章:搭建实验环境,然后就可以动手了。