尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

RYU+Mininet搭建SDN实验环境:从零实现学习交换机全流程

RYU+Mininet搭建SDN实验环境:从零实现学习交换机全流程 1. 先想清楚为什么选RYU配Mininet1.1 RYU和Mininet分别解决什么问题做SDN相关的实验、毕设或者业务原型时RYU和Mininet几乎是绕不开的一对组合。先说Mininet它解决的是没有真实交换机也能搭网络的问题。一台普通电脑上就能模拟出几台甚至几十台虚拟交换机、虚拟主机而且它底层调用的是Linux内核的network namespace和Open vSwitch也就是说你敲的每一个命令、跑通的一条链路在真实网络环境里是同样成立的。对学习、验证、演示来说这是性价比极高的仿真平台。RYU则是控制器层面的东西。SDN的核心思想是转发与控制分离Open vSwitch这类交换机只负责按规则转发规则从哪里来正是从RYU这样的控制器下发。RYU是日本NTT实验室开源的控制器纯Python实现代码结构轻量上手门槛比OpenDaylight低一大截。它负责通过OpenFlow协议和交换机通信监听交换机发来的消息然后下发转发规则。两者配合起来你就拥有了一套完整的SDN闭环实验环境Mininet负责造网RYU负责管网。流表的下发、数据包的转发策略、拓扑感知、流量统计全部可以在你手写的Python代码里一点点实现出来。我在很多课程设计和技术分享里见过这个组合它的价值不在于跑通一个demo而在于让你真正看懂SDN控制器和转发设备之间的每一次握手。适合谁参考这篇内容两类人。第一类是刚开始搞SDN方向的学生想在毕业论文或课程项目里有一个能跑通、能讲清楚原理的东西第二类是网络工程师或者后端开发想快速验证一个网络策略、学习OpenFlow协议机制。接下来我按自己做过的项目流程把从安装到写第一个应用、再排掉一堆坑的完整过程捋一遍。1.2 为什么不是ONOS、OpenDaylight或Floodlight每次我给别人推荐这套组合总有人问社区里不是还有OpenDaylight和ONOS吗为什么偏要选RYU没有哪个控制器是万能的但RYU在学习代价和工程落地之间的平衡点非常好。OpenDaylight的功能确实强大它自带集群、自带GUI、支持南向协议也极多但代价是架构重、内存吃紧、模块体系复杂。我第一次装OpenDaylight的时候光是等Karaf容器启动就等了两分钟如果只是做一个两三个交换机的实验这就是纯粹的资源浪费。ONOS的定位更偏向运营商级网络场景它强调高可用和性能但对新手来说里面大量的概念——atomix集群、分布式存储、意图网络没点基础根本转不动调试起来容易怀疑人生。Floodlight虽然比前两者轻但Java版本迭代较慢社区活跃度在近几年明显下降遇到问题能找到的资料也比较陈旧。回到RYU它是用Python写的而Python本身就是个能用最短代码把逻辑表达清楚的语言。写一个最简单的Hub转发应用一百来行代码就能跑通写一个带MAC学习、最多加个环路避免的L2交换机也就二百多行。对需要反复改逻辑、反复跑拓扑验证的人来说这个迭代速度极为舒服。而且RYU源码就摆在那里Python的阅读门槛低打开源码逐行读一遍整个控制器的工作机制——事件循环、协议解析、应用调度——都能看明白。从实验和教学角度看RYU的日志输出友好ryu-manager启动后可以直接看到控制器和交换机的握手过程、收到什么包、下发了什么规则不需要像OpenDaylight那样去翻复杂的日志系统。这套项目做下来你的核心收获不是我会用RYU了而是我搞懂了OpenFlow的机制换到其他控制器也只是API重新熟悉一遍的问题。2. 环境准备Mininet的安装与全流程验证2.1 安装前的四个准备项很多人装Mininet直接卡在第一步原因往往不是命令错了而是前置环境没想清楚。先说系统选择Mininet在Ubuntu系下支持最好这一点不需要钻牛角尖建议直接用Ubuntu 18.04或20.04都是稳定踩过无数坑的版本。用Arch或CentOS也能跑但你要有自己修依赖的耐心我试过一次在CentOS源码编译Mininet网上资料零零散散整整多花了一天不太建议新手上来就玩Hard模式。内核方面Mininet的默认交换机是Open vSwitch它会用到内核模块所以最好检查一下内核版本建议4.4以上。在终端里执行uname -r就可以看到内核版本一般的Ubuntu桌面版和服务器版都满足。这里有个容易忽略的点如果你用的是虚拟机注意虚拟化嵌套Nested Virtualization的问题。具体表现是Mininet在内核模块加载时报各种奇怪的错排查到最后发现是物理机的虚拟化层没把内核模块透传进去如果是VMware跑记得把处理器设置里的虚拟化Intel VT-x/EPT或AMD-V/RVI勾上。内存分配不低于2GB建议4GB更稳。原因是在一个典型实验里每台虚拟主机都要消耗一个独立的network namespace每台虚拟交换机又是一套独立的命名空间和OVS实例加上控制器进程资源吃紧时拓扑稍微大一点就直接卡死。磁盘空间至少留出5GB安装过程产生的编译缓存和日志比你想的占地方。还有一个可能被忽略的问题系统里的其他网络工具不要和Mininet抢占环境。比如你在系统里手动启过NetworkManager托管网桥、或者装过Open vSwitch并手动创建过网桥Mininet启动时会对已有网桥冲突报警。最干净的环境是裸机Ubuntu装完系统先做快照再开始折腾这一点对虚拟机用户尤其管用。2.2 三种安装方式怎么选Mininet官方提供了三种安装方式我全部试过结论是按你的使用场景来选不用盲目追求源码安装。第一种是apt安装命令就一行sudo apt update sudo apt install mininet优点是有Ubuntu软件源帮你处理依赖、卸载干净、装完就能用缺点是版本可能偏旧。旧版本不是不能做实验但当你后面想用一些新拓扑特性或和最新RYU配合时可能遇到兼容问题。如果只是跑跑简单拓扑、学学基本命令apt安装完全够用。装完后记得顺手安装依赖工具sudo apt install openvswitch-switch wireshark后面排查流表时用得着。第二种是源码安装这是最推荐的完整方式。先把Mininet仓库存下来git clone https://github.com/mininet/mininet.git cd mininet这里要注意不要直接git clone主分支就算了建议先看一下Release Tag列表切到比较稳定的版本比如2.3.0或者2.3.1d4。然后执行./util/install.sh -ainstall.sh脚本会自动帮你装Open vSwitch、辅助工具、甚至Wireshark插件整个过程会比较长需要耐心等待。这里有个小技巧很多人装到一半网络断了或者某个源超时报错不要一股脑重新执行install.sh先单独看一下是哪个组件失败单独补齐那个组件再重跑安装脚本效率高很多。第三种是直接下载官方虚拟机镜像如果你用的是Windows或者macOS这是最省心的方式。Mininet官方维护了基于Ubuntu的OVA镜像里面预装好了Mininet、OVS甚至一些示例代码用VirtualBox或VMware直接导入就能用。这种方式我一般在给学生上课或做演示时用好处是环境绝对干净坏处是镜像动辄几个GB而且官方镜像自带的控制器版本较旧你要是想用最新RYU还是得自己装一遍。2.3 安装完成后必做的三个验证装完别急着写控制器先把Mininet环境本身验证清楚。第一项验证是基础命令执行sudo mn --version这时能看到版本号。如果提示找不到命令大概率是PATH没配上重新登录一下会话或者手动把/usr/local/bin加入PATH即可。第二项验证跑了才算数bash sudo mn --test pingall这条命令会默认建立一个最小的拓扑一台交换机挂两台主机自动执行一次主机间互通测试。输出里如果显示0% dropped说明Mininet核心组件工作正常。如果这里就开始报丢包那不是Mininet的问题就是OVS内核模块没加载运行sudo ovs-vsctl show看看OVS是否正常响应。遇到过OVS服务没有自启的情况手动执行sudo systemctl restart openvswitch-switch之后就好了。 第三项验证是看看默认交换机和命名空间是否正常 bash sudo ovs-vsctl show正常情况你能看到名为s1的网桥再执行sudo ip netns list应该有h1和h2两个命名空间。这些验证做完才真正说明Mininet环境OK——不然你后面发现连不上控制器根本不知道是网络拓扑没起来还是控制器那边的问题。3. RYU控制器的安装与第一轮连通测试3.1 安装RYU的完整操作安装RYU推荐用pip方式因为这是官方支持、升级最快的方式。新建一个虚拟环境是推荐做法避免污染系统Python环境python3 -m venv ~/ryu-env source ~/ryu-env/bin/activate pip install ryu如果下载速度慢可以使用国内PyPI镜像比如pip install ryu -i https://pypi.tuna.tsinghua.edu.cn/simple。安装过程会自动拉取eventlet、routes、msgpack等依赖这些都不用你手动处理。安装完跑一下版本验证ryu-manager --version能看到版本号就说明Python环境没问题。有个小坑如果你系统里同时装过多个Python版本要确认pip和python是同一个版本避免出现Python 2的老代码与依赖冲突。RYU现在完全支持Python 3直接用python3和pip3来操作即可。有些场景下pip安装的RYU版本可能滞后如果你需要自己改源码调试就改用git安装git clone https://github.com/faucetsdn/ryu.git cd ryu pip install -e .这种可编辑安装方式的好处是源码和安装文件软链接在一起你改源代码后无需重新安装即可生效调试自己的控制器应用时非常方便。我后来一直在用这种方式改一个方法重启一下ryu-manager就能看到变化省掉了反复重装的时间。3.2 先别写代码用内置应用体验第一轮握手安装完RYU之后建议先不要直接写自己的应用先用RYU自带的一个最简单应用体验一遍控制器连接上交换机的感觉。这个应用就是simple_switch_1.0虽然文件名暗示OpenFlow 1.0版本但它的MAC学习逻辑和应用示例意义重大。开启第一个终端执行ryu-manager ryu.app.simple_switch_1.0看到日志输出loading app ryu.app.simple_switch_1.0和instantiating app ryu.app.simple_switch_1.0并且最后处于监听状态默认端口6633或6653说明控制器已经就绪。在第二个终端启动Mininetsudo mn --topotree,2,3 --controllerremote,ip127.0.0.1,port6653 --switchovsk,protocolsOpenFlow13--topotree,2,3表示深度2、每层3个下级设备的树形拓扑--controllerremote,ip127.0.0.1,port6653告诉Mininet用远程控制器并且指定IP和端口--switchovsk,protocolsOpenFlow13则让交换机使用OpenFlow 1.3协议。这里必须注意协议版本的匹配RYU的simple_switch_1.0监听的是1.0版本而simple_switch_13监听的是1.3版本如果协议版本不匹配交换机和控制器不握手流表永远下发不下去。启动后回到控制器终端的日志如果看到类似EVENT ofp_event-EventOFPSwitchFeatures之类的握手消息说明交换机已经成功连上控制器。在Mininet里执行pingall跑一遍全互联测试如果依然有丢包那就要看看是不是协议版本的问题把mininet启动命令里的protocols换成OpenFlow10再试或者直接用simple_switch_13来控制协议版本的匹配关系。pingall命令输出0% dropped的那一瞬间你的SDN环境就等于彻底打通了。这一步虽然用的是RYU自带应用但你已经完整走过了交换机启动-发现控制器-建立OpenFlow连接-控制器下发转发规则-数据包转发成功的整个链路后面的开发工作全是在这个框架里加自己的业务逻辑。3.3 顺手把RYU的REST API打开我劝大家在一块学RYU时就养成使用REST API的习惯。RYU自带了一个北向接口应用启动时额外加载它就行ryu-manager ryu.app.simple_switch_13 ryu.app.rest_conf_switch ryu.app.ofctl_rest这样你的控制器不仅能处理OpenFlow南向协议还额外暴露了一套REST接口。你可以用curl直接查看当前连接的交换机信息curl -X GET http://127.0.0.1:8080/v1.0/topology/switches这是理解控制平面和数据平面分离最直观的一道口子底层OVS转发行为一直在变但上层你只需要通过HTTP接口查询状态、下发策略完全不用登录每一台设备操作。这个架构思想和你以后用企业级SDN控制器是一样的只是在RYU里用几百行Python就能实现。4. 核心项目实践写一个能看懂流表的学习交换机4.1 代码结构逐段拆解这一节是整个项目的核心——我们不只是跑通一个现成的应用而是亲手写一个带MAC学习功能的二层交换机。它的业务逻辑和传统二层交换机完全一样当收到一个数据包时学习源MAC地址与端口的对应关系再根据目的MAC地址决定转发到哪个端口。知识迁移过来非常自然你不会面对一个完全陌生的算法。先搭出Controller类的骨架from ryu.base import app_manager from ryu.controller import ofp_event from ryu.controller.handler import MAIN_DISPATCHER, DEAD_DISPATCHER, CONFIG_DISPATCHER from ryu.controller.handler import set_ev_cls from ryu.ofproto import ofproto_v1_3 from ryu.lib.packet import packet from ryu.lib.packet import ethernet class LearningSwitch13(app_manager.RyuApp): OFP_VERSIONS [ofproto_v1_3.OFP_VERSION] def __init__(self, *args, **kwargs): super(LearningSwitch13, self).__init__(*args, **kwargs) self.mac_to_port {}OFP_VERSIONS指定了控制器只处理OpenFlow 1.3协议的消息这个和Mininet端启动时的protocolsOpenFlow13对应协议版本不一致必然握手失败。mac_to_port字典就是MAC地址和交换机端口的映射表MAC地址作为键、端口号作为值收到一个数据包就更新这张表。接下来是处理交换机特性上报---握手成功后控制器做的第一件正事是向交换机下发一条缺省流表项。如果没有这条表项所有匹配不到规则的数据包都会被直接丢弃加了它之后所有没识别出来的数据包会被封装成Packet-In消息转发给控制器处理控制器才有机会看到并学习这个包set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): datapath ev.msg.datapath ofproto datapath.ofproto parser datapath.ofproto_parser match parser.OFPMatch() actions [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] self.add_flow(datapath, 0, match, actions) def add_flow(self, datapath, priority, match, actions, buffer_idNone): ofproto datapath.ofproto parser datapath.ofproto_parser inst [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)] if buffer_id: mod parser.OFPFlowMod(datapathdatapath, buffer_idbuffer_id, prioritypriority, matchmatch, instructionsinst) else: mod parser.OFPFlowMod(datapathdatapath, prioritypriority, matchmatch, instructionsinst) datapath.send_msg(mod)注意这里OFPCML_NO_BUFFER的使用。在OpenFlow 1.3里OPFFLOW_MOD命令可以带上一个buffer_id告诉交换机把数据缓存在交换机侧但这样做的条件是交换机的Buffer资源足够用。考虑到OVS实现细节和实验环境的不确定性设计上直接要求不缓存、完整包上送逻辑上最简单、行为最可控。等控制器学到转发规则后再以普通流表项方式下发后续同一条流的包理论上直接命中流表转发不再打扰控制器。从工程角度看这种先全量上送控制器、后按流缓存的策略在流量不大时完全够用而且减小了因为buffer管理不当导致的丢包风险。4.2 Packet-In处理与MAC学习主逻辑Packt-In事件是整个应用最核心的入口。交换机遇到没有流表项匹配的数据包会把包封装成Packet-In发给控制器。而MAC地址学习和转发决策就在这个处理函数里一步步完成set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def packet_in_handler(self, ev): msg ev.msg datapath msg.datapath ofproto datapath.ofproto parser datapath.ofproto_parser in_port msg.match[in_port] pkt packet.Packet(msg.data) eth pkt.get_protocol(ethernet.ethernet) if eth is None: return dst eth.dst src eth.src dpid datapath.id self.mac_to_port.setdefault(dpid, {}) self.logger.info(packet in from %s: %s - %s at port %s, dpid, src, dst, in_port) self.mac_to_port[dpid][src] in_port if dst in self.mac_to_port[dpid]: out_port self.mac_to_port[dpid][dst] else: out_port ofproto.OFPP_FLOOD actions [parser.OFPActionOutput(out_port)] if out_port ! ofproto.OFPP_FLOOD: match parser.OFPMatch(in_portin_port, eth_dstdst, eth_srcsrc) self.add_flow(datapath, 1, match, actions, msg.buffer_id) data None if msg.buffer_id ofproto.OFP_NO_BUFFER: data msg.data out parser.OFPPacketOut(datapathdatapath, buffer_idmsg.buffer_id, in_portin_port, actionsactions, datadata) datapath.send_msg(out)这个逻辑有个值得加深理解的地方self.mac_to_port[dpid][src] in_port这一行是整个L2自学习机制的核心。它做的事情是当从某个交换机dpid的某个端口in_port收到一个源MAC为src的包时记住该MAC与端口的对应关系。从此以后凡是目的地址为这个MAC的包控制器就知道该从哪个端口送出去这就是学习二字的来源与真实交换机MAC地址表的构建如出一辙。代码里还有一个多数资料不会明说的复用逻辑当目的MAC已经被学习到时if out_port ! ofproto.OFPP_FLOOD:这个判断会把一个精确匹配的流表项下发到交换机里。下发之后同一对通信主机的后续数据包就直接在交换机内按流表转发不再经过控制器。这正是SDN一个有趣的特点首包慢路径走控制器处理之后的快路径由交换机本地转发完成。msg.buffer_id的处理也值得一路跟踪。如果控制器的add_flow下发了包含buffer_id的流表修改消息交换机会匹配后续数据包并直接根据该buffer_id处理无需控制器把原始数据包重新发送出去。但很多实验场合下交换机报告OFP_NO_BUFFER即没有缓存能力此时必须把msg.data完整带回OFPPacketOut消息中否则交换机没有包可转。这套兼容逻辑几乎是所有RYU控制器应用的固定写法把它理解透以后再看其他开源控制器应用就顺利多了。4.3 实验过程与结果验证代码写完后保存为learning_switch_13.py然后启动控制器ryu-manager learning_switch_13.py启动Mininetsudo mn --topotree,2,3 --controllerremote,ip127.0.0.1,port6653 --switchovsk,protocolsOpenFlow13在Mininet CLI中先执行pingall看连通性。如果都有0%丢包再依次执行以下命令观察流表mininet dpctl dump-flows这里有一个很多新手会犯错的细节dpctl默认连接的是OpenFlow1.0的端口而你当前OVS跑的是1.3协议所以你需要指定协议版本才能看到流表mininet dpctl dump-flows --protocolsOpenFlow13执行后你会看到类似这样的输出cookie0x0, duration..., table0, n_packets1, n_bytes..., priority1,ip,in_port1,dl_src... dl_dst... actionsoutput:2这行流表项表示优先级为1入端口1源MAC地址和目的MAC地址分别为对应值动作是输出到端口2。这就是刚刚控制器下发的精确匹配规则的落地。反过来如果你在pingall之前就执行dump-flows看到的只会有那条优先级为0、把所有包上送控制器的缺省规则说明学习规则还没有建立。这个对比是理解控制器如何逐步接管交换逻辑的绝佳切入点。还有一个实践点建议做一下在Mininet里打开xterm运行一个持续ping进程然后观察控制器终端的日志输出。每次出现新的通信对时会打印一次packet in日志等同一个通信对后续流量命中流表后控制器不会再被打扰。这个首包上送、后续本地转发的行为在所有真实SDN交换机上都是一样的理解了它才算摸到SDN数据平面的门。5. 实验做完后流表怎么看问题怎么排5.1 有没有一种更直观的流表查看方式dpctl dump-flows命令看流表对老手来说足够但对新手来说一堆十六进制的字段看得出来有点吃力。我推荐在实际实验时用OVS原生命令来看细节比dpctl更丰富。在Mininet的xterm中打开某台交换机的终端比如host的终端执行xterm s1然后在s1终端里执行ovs-ofctl dump-flows s1 -O OpenFlow13这能看到完整的流表信息包括每条规则的匹配字段、动作、包计数和字节计数。还可以进一步执行ovs-ofctl dump-ports s1观察每个端口收发报文统计。如果你怀疑某个端口丢包这个命令会比在Mininet里反复ping更直接。甚至可以在s1终端里手工下发一条流表规则做实验验证比如ovs-ofctl add-flow s1 priority100,in_port1,actionsoutput:2 -O OpenFlow13看看这样手动加规则之后Mininet里的行为会不会改变。做这种实验时对环境的掌控感比仅仅跑通一个demo要强得多也是在学习SDN过程中值得认真训练的基本功。5.2 常见问题速查表我在搭建和使用RYUMininet的过程中遇到过大量问题下面直接整理成一张速查表如果你也卡在类似的地方可以直接对照排查现象可能原因解决办法RYU启动后Mininet的交换机显示连不上控制器OpenFlow协议版本不匹配检查RYU里写的OFP_VERSIONS和Mininet启动命令的protocols是否一致pingall有丢包且控制器日志没有packet in消息缺省流表项没下发成功重启控制器后重新创建拓扑观察握手日志dpctl dump-flows看不到流表默认协议版本不对加--protocolsOpenFlow13参数或指定协议Mininet启动时提示OVS端口冲突上次实验残留的网桥没清理干净执行sudo mn -c清理再重建拓扑RYU日志报eventlet相关错误eventlet版本兼容性问题升级或降级eventlet到兼容版本控制器能连上但拓扑感知为空没有启动LLDP相关应用加载ryu.app.simple_switch_13配合topology discovery应用或单独写拓扑发现逻辑ovs-vsctl show里网桥存在但端口不对拓扑初始化异常执行sudo mn -c后重新创建5.3 排查问题的一套实战思路很多人遇到问题会先怀疑代码但我踩过无数次坑之后的经验是先确认环境层再查协议层最后才查应用逻辑。环境层的核心排查命令是sudo mn -c它能清理上次实验残留的命名空间、网桥和进程。很多莫名其妙的端口冲突都源于没有清理干净。每次实验结束顺手执行一下sudo mn -c可以帮你省掉大量迷惑。另外用sudo ovs-vsctl show确认当前系统里存在的网桥如果看到一个不知道为什么存在的名为s1的网桥先把它删掉再跑实验。协议层要看OVS和RYU的日志。RYU的终端日志会持续输出控制器收到的所有事件如果你发现根本没有任何握手事件那就要检查Mininet启动命令中--controllerremote,ip...的IP和端口是否和实际rvu监听地址一致以及防火墙有没有拦截TCP连接。还要检查OVS日志路径一般在/var/log/openvswitch/ovs-vswitchd.log其中有controller相关的连接记录和错误信息这里面的提示比你在应用层瞎猜要精准得多。应用逻辑层的排查就依赖你代码里的logger信息了。当packet log被打出来说明握手、上送、事件调度这一整条链路都是通的问题只出在你自己的转发决策逻辑里。此时把代码里每个分支的logger打得更详细一些基本就能定位。整个过程就像剥洋葱一层层确认不要一上来就怀疑方案本身。6. 项目做完之后的几点经验沉淀这个项目做到最后表面上是搭建了一套SDN实验环境但真正的收获远远超过装了两个工具、跑通了一个pingall。我自己的体会是过去看SDN文章里讲OpenFlow协议时总觉得抽象什么多级流表group tablemeter表看官方规范翻几页就犯困。但自己从零写了一个学习交换机亲手抓到Packet-In消息、看过流表的action字段之后协议的很多概念自然就有了具体的锚点。另外有一点可能对做毕设或课程项目的同学有参考价值RYU这个平台的可扩展性比你想的大得多。学习交换机只是一个起点你完全可以在它的基础上做流量监控应用、基于端口的访问控制、甚至简单的负载均衡器。REST API也可以继续深挖做一个可视化前端调用ofctl_rest接口画出一张实时的拓扑和流量动态图就是一套颇具完成度的课程设计展示。如果时间有限但想加深理解最后一个小建议去读一下RYU的ryu/app/simple_switch_13.py源码——虽然你已经知道它的行为但源码里的set_ev_cls装饰器、事件调度机制、OFPPacketOut和OFPFlowMod的配对使用都是RYU框架的精髓。把这段代码在py文件库里反反复复读几遍再对照自己写的版本比较差异效果胜过翻十篇教程。
返回列表