主题
实验 18:外部控制器
前面十七章里,流表都是我们手工敲进去的。这一章把决策权交出去 —— 让一个外部程序通过 OpenFlow 协议来管流表。
这就是 SDN 最初的核心主张:控制面和转发面分离。
起环境
bash
cd labs/learn-ovs/15-controller
./start.shtext
h1 ── ovs1 ── h2
│
OpenFlow (TCP 6653)
│
控制器控制器在哪
实验里控制器跑在 ovs1 容器内部,纯粹为了少搭一个节点。走的是完全相同的 TCP OpenFlow 连接,和跑在另一台机器上没有本质区别 —— 只是 remote_ip 换成了 127.0.0.1。
起一个控制器
OVS 自带一个最小实现:
bash
docker exec -d clab-controller-ovs1 \
sh -c 'ovs-testcontroller --detach --pidfile ptcp:6653'ptcp:6653 表示被动监听 TCP 6653(OpenFlow 的标准端口,早期是 6633)。
ovs-testcontroller 的行为是把交换机当学习型网桥:收到不认识的流量就下发一条转发规则。生产环境用的是 ONOS、OpenDaylight、Faucet、Ryu 这些完整控制器,但连接建立和消息交互的过程完全一样。
连上去
bash
docker exec clab-controller-ovs1 \
ovs-vsctl set-controller br0 tcp:127.0.0.1:6653确认连上了:
bash
docker exec clab-controller-ovs1 ovs-vsctl get Controller br0 is_connectedtext
true看完整状态:
bash
docker exec clab-controller-ovs1 ovs-vsctl list Controllertext
is_connected : true
role : other
status : {sec_since_connect="0", state=ACTIVE}
target : "tcp:127.0.0.1:6653"
inactivity_probe : []
max_backoff : []| 字段 | 含义 |
|---|---|
target | 控制器地址。tcp: 主动连、ptcp: 被动等、ssl: 加密 |
is_connected | 当前是否连着 |
role | OpenFlow 角色:other / master / slave(多控制器时用) |
status | 连接状态、上次连接/断开距今多久、最后的错误 |
inactivity_probe | 多久没消息就发探测,默认 5 秒 |
max_backoff | 重连退避上限,默认 8 秒 |
看控制器下发了什么
打点流量:
bash
docker exec clab-controller-h1 ping -c 3 192.168.10.2bash
docker exec clab-controller-ovs1 ovs-ofctl dump-flows br0text
idle_timeout=60, priority=1,arp,in_port=1,dl_src=aa:c1:ab:4b:16:f8,
dl_dst=aa:c1:ab:97:ee:67,arp_spa=192.168.10.1,arp_tpa=192.168.10.2,
arp_op=2 actions=output:2
idle_timeout=60, priority=1,icmp,in_port=2,dl_src=aa:c1:ab:97:ee:67,
dl_dst=aa:c1:ab:4b:16:f8,nw_src=192.168.10.2,nw_dst=192.168.10.1,
icmp_type=0,icmp_code=0 actions=output:1
idle_timeout=60, priority=1,icmp,in_port=1,...,icmp_type=8 actions=output:2
priority=0 actions=CONTROLLER:128最后那条是关键:priority=0 actions=CONTROLLER:128 —— 所有没被其它规则匹配的包,把前 128 字节送给控制器。这叫 table-miss 规则,是反应式(reactive)控制器的入口。
前面三条是控制器在收到 packet-in 之后动态下发的。注意它们的特点:
- 极其精确:匹配了 in_port、双向 MAC、IP、ICMP 类型
- 带
idle_timeout=60:60 秒没流量就自动删掉,避免流表无限增长
反应式模型的代价
这个模型有两个明显问题:
一、第一个包的延迟。 要跑一趟控制器,比实验 4 的 upcall 还慢一个数量级(那只到用户态,这要过网络)。
二、缓存爆炸。 每条流一条精确规则,正是实验 4警告过的情况 —— megaflow 被拆得极细,内核缓存条数随连接数线性增长。
别在高流量路径上用 CONTROLLER
actions=CONTROLLER 意味着每个匹配的包都要过一趟慢路径加网络往返。table-miss 规则用它是可以的(只有新流量会命中),但绝不能用在业务流量上。
生产环境的主流做法是主动式(proactive):控制器在启动时就把完整策略下发好,运行时不再有 packet-in。OVN 就是纯主动式的。
fail_mode:控制器挂了怎么办
这是生产环境最需要想清楚的配置。
bash
ovs-vsctl set-fail-mode br0 standalone # 或 secure| 模式 | 控制器断开后 | 适合 |
|---|---|---|
standalone(默认) | 回落成普通二层交换机,继续转发 | 可用性优先 |
secure | 保持现有流表,不新增任何规则;流表空就全丢 | 安全优先 |
实测 standalone
bash
ovs-vsctl set-fail-mode br0 standalone
pkill -f ovs-testcontroller等一会儿再 ping:
text
3 packets transmitted, 3 received, 0% packet loss还是通的。 但流表是空的:
bash
ovs-ofctl dump-flows br0text
NXST_FLOW reply (xid=0x4):一条都没有,却能转发?用 trace 看:
bash
ovs-appctl ofproto/trace br0 "in_port=1,dl_src=<h1MAC>,dl_dst=<h2MAC>"text
bridge("br0")
-------------
0. priority 15790320
NORMAL
-> forwarding to learned port
Datapath actions: 3一条看不见的规则
priority 15790320 —— 这是 OVS 在 standalone 回落时装的内部规则,优先级远高于任何用户规则(OpenFlow 优先级最大 65535,这个值超出了正常范围)。
它不出现在 ovs-ofctl dump-flows 里,因为那只显示 OpenFlow 可见的流表。只有 ofproto/trace 能看到它。
排查"流表是空的但还在转发"这类怪事时,记得用 trace 而不是 dump-flows。
回落不是立刻发生的
实测大约 15 秒。OVS 要等 inactivity_probe 超时、重连退避几轮,确认连接真的没了才回落。
所以 verify.sh 里用的是轮询而不是固定 sleep:
bash
for _ in $(seq 1 30); do
ovs-vsctl get Controller br0 is_connected | grep -q false \
&& ping -c 1 -W 1 192.168.10.2 >/dev/null 2>&1 && break
sleep 1
done测异步状态变化时,轮询到条件成立比猜一个 sleep 时间可靠得多。
实测 secure
bash
ovs-vsctl set-fail-mode br0 secure
ovs-ofctl del-flows br0text
2 packets transmitted, 0 received, 100% packet loss完全不通。 secure 模式下 OVS 不会自作主张加任何规则,流表空就是全丢。
怎么选
standalone:控制器挂了网络还能用,但策略全部失效 —— 原本被隔离的租户会突然互通。这在多租户环境里是严重的安全事故。
secure:控制器挂了网络就断,但不会泄露。配合流表持久化和控制器高可用使用。
多租户/有隔离要求的场景一律用 secure。 这也是 OVN 的选择。
多控制器与角色
可以配多个控制器做高可用:
bash
ovs-vsctl set-controller br0 tcp:10.0.0.10:6653 tcp:10.0.0.11:6653OpenFlow 1.3 定义了三种角色:
| 角色 | 权限 |
|---|---|
master | 全部权限,同一时刻只能有一个 |
slave | 只读,收不到 packet-in |
equal(OVS 里显示为 other) | 和 master 同等权限,可以有多个 |
角色由控制器自己声明,OVS 负责保证只有一个 master。
其它连接方式
bash
# 主动连出去(最常见)
ovs-vsctl set-controller br0 tcp:10.0.0.10:6653
# 被动等控制器连进来
ovs-vsctl set-controller br0 ptcp:6653
# 加密连接(生产环境应该用这个)
ovs-vsctl set-controller br0 ssl:10.0.0.10:6653
ovs-vsctl set-ssl <私钥> <证书> <CA证书>明文 OpenFlow 等于把交换机的控制权裸奔
tcp: 连接没有任何认证和加密。任何能连到 6653 端口的人都能改流表。
生产环境用 ssl:,或者至少把控制网络严格隔离。
常用的控制器
| 控制器 | 语言 | 特点 |
|---|---|---|
ovs-testcontroller | C | OVS 自带,只做学习型交换,仅供测试 |
| Faucet | Python | 轻量、面向生产、配置驱动 |
| ONOS | Java | 电信级,功能全,重 |
| OpenDaylight | Java | 生态大,重 |
| Ryu | Python | 写自定义控制器的经典框架,已停止维护 |
| OVN | C | 严格说不是 OpenFlow 控制器,见下一章 |
自动检查
bash
./verify.shtext
[通过] br0 配了控制器
[通过] 控制器连接已建立
[通过] 控制器接管后仍然连通
[通过] 流表里出现了控制器下发的规则
[通过] standalone 模式:控制器断了仍然转发
[通过] secure 模式:控制器断了就不转发自测清单
tcp:和ptcp:有什么区别?priority=0 actions=CONTROLLER:128这条规则的作用是什么?- 反应式模型有哪两个代价?
- standalone 模式下流表是空的,为什么还能转发?怎么看到那条规则?
- 控制器断开后多久才回落?为什么不能用固定 sleep 来测?
- 多租户环境该用哪种 fail_mode?为什么?
- 为什么生产环境不该用
tcp:?
常见问题
is_connected 一直是 false
bash
ovs-vsctl list Controller | grep statusstatus 里的 last_error 会告诉你原因:
Connection refused→ 控制器没起来,或端口不对No route to host→ 网络不通- 一直
state=BACKOFF→ 反复重连失败,检查防火墙
连上了但流量不通
看有没有 table-miss 规则:
bash
ovs-ofctl dump-flows br0 | grep CONTROLLER没有的话,控制器不会收到 packet-in,自然不会下发规则。有些控制器需要显式配置才会装这条。
控制器一断网络就全断
fail_mode 是 secure 且流表为空。这是预期行为。要么改成 standalone,要么保证控制器高可用。
流表条数暴涨
反应式控制器的典型问题。每条流一条规则,加上 idle_timeout 不够短的话会堆积。
bash
ovs-ofctl dump-flows br0 | wc -l
ovs-dpctl show | grep flows治本办法是改用主动式下发。
想看 OpenFlow 消息交互
bash
ovs-ofctl snoop br0这会连上实验 2 提到的 br0.snoop 套接字,实时打印交换机和控制器之间的所有 OpenFlow 消息。调试控制器时非常有用。
小结
- 控制器通过 OpenFlow over TCP 6653 管理流表,
set-controller一条命令接入 ptcp:被动监听,tcp:主动连出,生产环境用ssl:priority=0 actions=CONTROLLER:128是反应式模型的入口(table-miss)- 反应式的代价:第一个包延迟高 + 每条流一条精确规则导致缓存爆炸
- 生产环境主流是主动式:启动时下发完整策略,运行时无 packet-in
standalone回落成普通交换机(策略失效,有安全风险)secure保持流表不新增(流表空就全断)- 多租户环境必须用
secure - standalone 回落靠一条优先级 15790320 的隐藏 NORMAL 规则,
dump-flows看不到,只有ofproto/trace能看到 - 回落约需 15 秒,测异步状态要轮询而不是固定 sleep
ovs-ofctl snoop br0可以实时看 OpenFlow 消息
下一章:实验 19:用户态 datapath。