随着DragonFlow的不断演进,从最初的利用多级流表解决虚拟网络东西向流量的分布式的问题,到现在的l3 local controller,分布式的dhcp和dnat,再到分布式DB的使用,总体来说DragonFlow在不断的向前推进,功能在不断的完善,基于DragonFlow架构的介绍网上有很多,这里不做过多的介绍,本文将基于DragonFlow现有代码做出分析,有错误或者不完善的地方请批评指正。
关于分布式DB
对上层应用抽象出一层api_nb,不同的db_driver由api_nb进行统一管理,所以对于上层应用来说不会感知具体的db_driver。目前实现的db_driver有:etcd、ovsdb、ramcloud、redis、zookeeper。在这里主要介绍下zookeeper的实现,目前实现的db接口主要为增删改查。所有数据保存在zookeeper的/openstack目录下,登录到zookeeper,可以看到所有数据的信息。
/openstack/lswitch 保存的为network信息
/openstack/lport 保存的为port信息
/openstack/lrouter 保存的为router信息
/openstack/floatingip 保存的为lport和floatingip的对应信息
/openstack/lsecgroup 保存的为securitygroup信息
/openstack/chassis 保存的为server信息,
/openstack/publisher 保存的为publisher信息
总体看来就执行了非常简单的操作,dragonflow plugin将数据写进zookeeper,利用zookeeper的集群同步功能,将数据同步到其他节点,其他节点通过本地数据和zookeeper的数据对比结果进行更新操作。但zookeeper集群建立需要基数节点(1,3,5..)。就目前zookeeper提交的代码上看,很多zookeeper的特性没有表现出来。
关于l3 local controller
取代最初版本的集中式的l3 controller agent,将l3 controller分布在各个节点上,避免单点故障。
dragonflow/controller/df_local_controller.py文件为df_local_controller的启动文件,main函数中,获取chassis_name(服务器name),生产DfLocalController实例。调用实例run()方法进行启动。
1.DfLocalController实例初始化:
- 生产DbStore实例,用于本地数据的存储。
- 生产NbApi实例,NbApi实例提供统一的Nb的API。
- 生产vswitch_api实例,该实例将作为ovsdb-client去连接ovsdb-server,作为操作ovsdb的统一接口类。
- l3_local_controller加载DFAdapter作为ryu的APP,该APP继承ryu的基础APP OFPHandler,OFPHandler注册了监听openflow的hello、feature、port_stats、echo、error消息的处理,DFAdapter注册了监听openflow的feature(重写)、port、packet_in消息处理。
处理完hello消息后,发送feature_request消息,收到feature_reply,调用dispatcher发送到df_app去,目前有dhcp_app、dnat_app、l2_app、l3_app、l3_proactive_app、sg_app处理收到该消息,然后发送port_stats_request消息。收到port_stats_reply消息后调用dispatcher发送到df_app去,目前没有app处理收到该消息。
如果收到port_status消息上报,如果是ADD,从db_store获取该port的信息,并设置其ofport和is_local属性,调用notify_add_local_port函数,该函数调用dispatcher将add_local_port消息发送到df_app去,目前dhcp_app、l2_app、l3_proactive_app、sg_app处理收到该消息。如果是DELETE,从db_store获取该port的信息,调用notify_remove_local_port函数,该函数调用dispatcher将remove_local_port消息发送到df_app去,目前dhcp_app、l2_app、l3_proactive_app、sg_app处理收到该消息。如果是MODIFY,不处理。
对于接收到packet_in消息,根据消息中的table_id,调用不同df_app中的packet_in的处理函数,目前dhcp_app、dnat_app、l3_app处理其对应table收到的packet_in消息。
2.调用run()函数:
- 调用NbApi的initialize函数,用于NbApi实例中的db_driver连接数据库的操作。
- 调用vswitch_api的initialize函数,去连接ovsdb-server,向ovsdb-server注册ovsdb_monitor_table_filter_default信息。发送sync_started/sync_finished消息,生产OvsdbMonitor实例并初始化。
- 生产Topology实例。
- 设置ovs的controller,fail_mode。
- 加载df基础app。调用db_sync_loop进行处理。
3.db_sync_loop函数处理:
该函数为死循环函数,每一次循环休眠一秒钟,调用run_db_poll函数。
4.run_db_poll函数处理:
- nb_api的sync函数,该函数直接pass,不处理。
- 调用register_chassis函数注册chassis,本地ip和tunnel_type,写入db_driver中。
- 调用create_tunnels函数进行tunnel的创建,通过vswitch_api获取当前ovs上所有的tunnel_port,通过nb_api获取db_driver所有chassis,调用chassis_created创建tunnel_port,chassis_id为remote_chassis_name,操作ovs删除其他多余的tunnel_port。
- 调用read_switches函数从db_driver中读取所有lswitch的信息,调用logical_switch_updated函数进行处理。该函数为新的lswitch分配id号,id号分配为0开始累加,建立network_id和id的映射关系,存入db_store中。建立network_id和LogicalSwitch的映射关系,存入db_store。调用dispatcher将’update_logical_switch’消息发出,目前dhcp_app、dnat_app处理。
- 调用read_security_groups函数从db_driver中读取所有secgroup的信息,调用security_group_updated函数进行处理,删除多余的group。对于新添加的secgroup,调用_add_new_security_group函数进行处理,调用dispatcher将’add_security_group_rule’消息发出,目前sg_app处理。并将secgroup存入db_store。
- 调用port_mappings函数从db_driver中读取所有lport的信息,删除多余的ports,对于新增的port,如果是本地port,调用notify_add_local_port函数。通过dispatcher发送到各个df_app,其中dhcp_app、l2_app、l3_proactive_app、sg_app注册了接收该消息。如果是远端port,调用notify_add_remote_port函数。通过dispatcher发送到各个df_app,l2_app、l3_proactive_app、sg_app注册了接收该消息。最有将lport存入db_store。
- 调用read_routers函数从db_driver中读取所有lrouter的信息,调用router_updated函数进行处理,对于新增的lrouter,调用_add_new_lrouter函数,处理该lrouter下所有的router_interface,调用dispatcher将’add_router_port’消息发出,目前l3_app、l3_proactive_app处理。最后存入db_store。
- 调用read_floatingip从db_driver中读取所有floatingip的信息,调用floatingip_updated进行floatingip的处理。对于新增的floatingip,调用_associate_floatingip函数,将floatingip存入db_store,调用dispatcher将’associate_floatingip’消息发出,目前dnat_app处理。
自此完成main函数启动过程,循环处理本地db_store和db_driver的对比结果。
关于df_app
1.aging_app
该app主要用来处理df_local_controller发生重启后的操作,ovs_sync_started函数中获取canary_flow(table_id=200),如果没有该流表,aging_cookie重置为0x1,然后下发canary_flow携带该cookie。如果获取到,取一个新的cookie。更新canary_flow的cookie,删除所有old cookie的flow。
2.dhcp_app
该app主要用来处理dhcp报文的,vm发出的dhcp报文上送到控制器,由控制器做统一代答。该app注册了处理table=11上送控制器的packet_in报文。在switch_features_handler时,下发默认流表处理dhcp的discover报文:
table=9,cookie,priority=100,IP,nw_dst(broadcast),ip_protocol17,tp_src,tp_dst,actions=goto_table:11
table=11,cookie,priority=1,actions=gototable:17。
遍历db_store的lswitch,获取每个lswitch所有的subnet。如果subnet是ipv4的并且dhcp_en,则下发dhcp_request报文上送控制器的流表:
table=9,cookie,priority=100,metadata,IP,nw_dst(dhcp_server),ip_protocol=17,tp_src,tp_dst,actions=goto_table:11,
table=11,cookie,priority=100,in_port,actions=write_metadata,controller。
3.dnat_app
该app主要用来处理dnat的,通过flow做内外网地址转换。该app注册了处理table=15上送控制器的packet_in报文。在switch_features_handler时,连接br-int和br-ex,定时发送外网网关的arp_request。associate_floatingip在port绑定fip后被调用。
处理ingress流程,下发流表:
table=0,cookie,priority=1,in_port=external_port,actions=gototable:15。
处理gw的arp_reply,下发流表:
table=15,cookie,priority=100,arp,arp_op=2,arp_tpa=fip,arp_spa=gw_ip,actions=controller。
处理gw的免费arp,下发流表:
table=15,cookie,priority=100,arp,arp_op=1,arp_tpa=gw_ip,arp_spa=gw_ip,actions=controller。
处理dnat,下发流表:
table=15,cookie,priority=100,ip,nw_dst=fip,actions=dl_src=fip_mac,dl_dst=vm_mac,DecTTL,nw_dst=vm_ip,output。
处理egress流程,下发流表:
table=20,cookie,priority=100,ip,nw_src=vm_ip,metadata,actions=goto_table:30。
table=30,cookie,priority=100,ip,nw_src=vm_ip,metadata,actions=dl_src=fip_mac,dl_dst=gw_mac,DecTtl,nw_src=fip,output。
4.l2_app
流量从VM发出去流程:
table=0,cookie,priority=100,in_port,actions=reg6(tunnel_key),metadata(network_id),goto_table:3
table=3,cookie,priority=1,actions=goto_table:9
table=9,cookie,priority=100,arp,actions=goto_table:10
对于arp处理,如果配置文件中使能arp responder:
table=10,cookie,priority=100,arp,arp_tpa,arp_op=1,metadata(network_id),actions=arp_op=2,MOVE(arp_tpa),MOVE(arp_spa),MOVE(arp_sha),MOVE(arp_tha),IN_PORT
对于arp处理,如果配置文件中不使能arp responder:
table=10,cookie,priority=1,actions=goto_table:17
table=9,cookie,priority=1, actions=goto_table:17
table=17,cookie,priority=200,metadata(network_id),dl_dst(broadcast),actions=reg7(tunnel_key),resubmit(IN_PORT,64),reg7(tunnel_key),resubmit(IN_PORT,64)... … …
table=17,cookie,priority=100,metadata(network_id),dl_dst(vm_mac),actions=reg7(tunnel_key),goto_table:64
table=17,cookie,priority=200,metadata(network_id),dl_dst(gw_mac),actions=reg7(tunnel_key),goto_table:20
table=64,cookie,priority=100, reg7(tunnel_key),actions=metadata(network_id),goto_table:72
table=72,cookie,priority=1, actions=goto_table:78
table=78,cookie,priority=100,reg7(tunnel_key), actions=output(local)
table=78,cookie,priority=100,reg7(tunnel_key), actions=tunnel_id,output(remote)
流量发给VM流程:
table=0,cookie,priority=100,tunnel_id,actions=reg7(tunnel_key),metadata(network_id),goto_table:72
从以上流程看出主要的两个参数为tunnel_key和network_id,其中network_id被用来作为openflow的metadata使用用来隔离不同network的流量,tunnel_key被用来精确识别VM。
5.l3_app
在feature handle时下发默认三层流表:
table=20,cookie,priority=1, actions=goto_table:64。
在处理router_interface时判断为ipv4则下发arp proxy的流表,和L2App里有重复。对于目的IP为router_interface的报文,下发:
table=20,cookie,priority=200,metadata(network_id),nw_dst(router_interface), actions=goto_table:64,
对于该router所连结所有subnet,下发匹配网段路由上控制器的流表:
table=20,cookie,priority=100, metadata(network_id),nw_dst/mask,actions=controller。
同时注册处理packet_in的函数,当VM发出的IP报文被送到控制器时,实现动态下发处理三层流量的流表:
table=20,cookie,priority=300,metadata(network_id),nw_dst(vm_ip), actions=mod_dl_src,mod_dl_dst,reg7(tunnel_key),goto_table:64。
从而完成模拟网关功能,实现东西向流量的分布式路由。
6.l3_proactive_app
该app主要用来处理l3转发表的静态下发。switch_features_handler时处理默认流表的下发。下发流表:
table=20,cookie,priority=1,actions=goto_table:64。
对于添加router_interface,下发流表:
table=20,cookie,priority=200,ip,metadata,nw_dst,actions=goto_table:64。
table=25,cookie,priority=200,ip,metadata,nw_dst,actions=goto_table:64。
下发网段路由:table=25,cookie,priority=100,ip,metadata,nw_dst/mask,actions=DecTtl,metadata,dl_src=route_mac,goto_table:25。
对于VM,下发流表:
table=25,cookie,priority=200,ip,metadata,nw_dst,actions=dl_dst=vm_mac,reg7,goto_table:64。
7.sg_app
Ovs在2.5.0以上版本上支持了connect tracking,使得将原先openstack中sg的方案可以从linux bridge上放在ovs上,我对connect tracking这一块没怎么接触过,在研究了ovs上的代码后,对这一块进行补齐。
关于df_plugin
/neutron/plugin.py 该plugin作为df的核心plugin,被neutron所加载。
1.对于network的创建,create_network函数将被调用
调用函数self.create_network_nb_api,组装数据:name=network_id,topic=tenant_id,external_ids={‘neutron:network_name’: network_name},
router_external=True/False,subnets=[],调用server的nb_api.create_lswitch函数,该函数创建lswitch={},调用driver的create_key函数,该函数将在zk本地server上创建/openstack/lswitch/NETWORK_ID节点,该节点里保存lswitch的基本信息。如果使能pub/sub机制,调用self._send_db_change_event函数进行消息分发。构建DbUpdate数据结构,通过publisher将消息发布出去。
Pub/sub机制的引入,使有网络topo发生变化时,各个df_app能够及时得到通知进行处理,从而不必等待轮询处理,提高处理效率。底层采用zeromq实现。
2.对于network的删除,delete_network函数将被调用
通过nb_api.get_all_logical_ports函数获取到当前的所有的ports信息,对该network下的port,调用nb_api.delete_lport函数,组装数据:name=port_id,topic=tenant_id,调用driver的delete_key函数,该函数将在zk本地server上删除/openstack/lport/PORT_ID节点,如果使能pub/sub机制,调用self._send_db_change_event函数进行消息分发。构建DbUpdate数据结构,通过publisher将消息发布出去。删除完port,然后把network信息删除,调用nb_api.delete_lswitch函数,构建数据:name=network_id,topic=tenant_id,调用driver的delete_key函数,该函数将在zk本地server上删除/openstack/lswitch/NETWORK_ID节点。
3.对于network的更新,update_lswitch函数将被调用
调用driver的get_key函数,该函数将在zk本地server上获取/openstack/lswitch/NETWORK_ID节点信息。将更新的信息回写到zk上,如果使能pub/sub机制,调用self._send_db_change_event函数进行消息分发。构建DbUpdate数据结构,通过publisher将消息发布出去。
其他如subnet/port/router/floatingip/sg等消息的处理基本思路和network的处理一致。
总结
Dragonflow总体的框架还是不错的,灵活度也比较高,对于开发者,由于使用了ryu作为核心controller,df_app的开发难度较小,根据roadmap的计划,后续的特性也较为期待。Dragonflow local controller在下发流表之前需要对全网数据进行处理,当大规模部署时,流表下发性能需要进行评估。维护两个数据库之间的信息同步的时序也是一个问题。
本文由九州云授权发布,谢绝转载