上一节交代了Neutron基本的组网原理,本节我们来看一看Neutron在软件层面的实现。
在架构设计上, Neutron沿用了OpenStack完全分布式的思想,各组件之间通过消息机制进行通信,使得Neutron中各个组件甚至各个进程都可以运行在任意的节点上,如下图所示。这种微内核的架构使得开发者可以集中精力在网络业务的实现上。目前Neutron提供了众多的插件与驱动,基本上可以满足各种部署的需要,如果这些还难以支撑实际所需的环境,则可以方便地在Neutron的框架下扩展插件或驱动。
上图中,除了消息机制以外还涉及5类Neutron组件,neutron-server,neutron agent,neutron plugin,neutron database,neutron provider,下面先对这几个组件是什么进行简单的介绍,再按照根据这几个组件间的交互来介绍Neutron主体代码的实现。
1)Neutron-server可以理解为一个专门用来接收Neutron REST API调用的服务器,然后负责将不同的rest api分发到不同的neutron-plugin上。 2)Neutron-plugin可以理解为不同网络功能实现的入口,各个厂商可以开发自己的plugin。Neutron-plugin接收neutron-server分发过来的REST API,向neutron database完成一些信息的注册,然后将具体要执行的业务操作和参数通知给自身对应的neutron agent。 3)Neutron-agent可以直观地理解为neutron-plugin在设备上的代理,接收相应的neutron-plugin通知的业务操作和参数,并转换为具体的设备级操作,以指导设备的动作。当设备本地发生问题时,neutron-agent会将情况通知给neutron-plugin。 4)Neutron database,顾名思义就是Neutron的数据库,一些业务相关的参数都存在这里。
Network provider,即为实际执行功能的网络设备,一般为虚拟交换机(OVS或者Linux Bridge)。
5)在开始分析代码之前,还需要先对neutron-plugin进行一点更为详细的介绍,neutron-plugin分为core-plugin和service-plugin两类。
6)Core-plugin,Neutron中即为ML2(Modular Layer 2),负责管理L2的网络连接。ML2中主要包括network、subnet、port三类核心资源,对三类资源进行操作的REST API被neutron-server看作Core API,由Neutron原生支持。其中:
7)Service-plugin,即为除core-plugin以外其它的plugin,包括l3 router、firewall、loadbalancer、V**、metering等等,主要实现L3-L7的网络服务。这些plugin要操作的资源比较丰富,对这些资源进行操作的REST API被neutron-server看作Extension API,需要厂家自行进行扩展。
上一节曾经提到,“Neutron对Quantum的插件机制进行了优化,将各个厂商L2插件中独立的数据库实现提取出来,作为公共的ML2插件存储租户的业务需求,使得厂商可以专注于L2设备驱动的实现,而ML2作为总控可以协调多厂商L2设备共同运行”。在Quantum中,厂家都是开发各自的Service-plugin,不能兼容而且开发重复度很高,于是在Neutron中就为设计了ML2机制,使得各厂家的L2插件完全变成了可插拔的,方便了L2中network资源扩展与使用。
(注意,以前厂商开发的L2 plugin跟ML2都存在于neutron/plugins目录下,而可插拔的ML2设备驱动则存在于neutron/plugins/ml2/drivers目录下)
ML2作为L2的总控,其实现包括Type和Mechanism两部分,每部分又分为Manager和Driver。Type指的是L2网络的类型(如Flat、VLAN、VxLAN等),与厂家实现无关。Mechanism则是各个厂家自己设备机制的实现,如下图所示。当然有ML2,对应的就可以有ML3,不过在Neutron中L3的实现只负责路由的功能,传统路由器中的其他功能(如Firewalls、LB、V**)都被独立出来实现了,因此暂时还没有看到对ML3的实际需求。
=============================== Codes Start ==================================
一般而言,neutron-server和各neutron-plugin部署在控制节点或者网络节点上,而neutron agent则部署在网络节点上和计算节点上。我们先来分析控制端neutron-server和neutron-plugin的工作,然后再分析设备端neutron-agent的工作。
=============================== 控制端的实现 ==================================
从neutron-server的启动开始说起。neutron-server的启动入口在neutron.server.__init__中,主函数中主要就干了两件事,第一是下图l 48处启动wsgi服务器监听Neutron REST API,第二是在l 52启动rpc服务,用于core plugin与agent间的通信,两类服务作为绿色线程并发运行。从SDN的角度来看,wsgi负责Neutron的北向接口,而Neutron的南向通信机制主要依赖于rpc来实现(当然,不同厂家的plugin可能有其它的南向通信机制)。
(一)WSGI
从48行代码回溯WSGI的机理。向前追踪到neutron.service中的_run_wsgi方法,该方法中加载了各类Neutron资源(l 173),实例化wsgi server(l 177),然后启动wsgi socket(l 178),这样neutron-sever就开始监听Neutron REST API了。177和178行没什么好说的,173行后面的学问比较多,下面来具体分析一下。
Load_paste_app这个方法属于wsgi的paste deployment,用于发现、配置、连接WSGI Application和Server。对于Neutron来说就是在neutron –server和各类Neutron资源建立关系,使得neutron-server能够将对A plugin中资源进行操作的REST API分发给A plugin,将对B plugin中资源进行操作的REST API分发给B plugin。Neutron的paste deployment配置文件目录路径为/etc/neutron/api-paste.ini,该文件中设计Neutron REST API的主要涉及这么几行,如下图所示。
Sevice-plugins的处理发生在neutron.api.extensions文件的plugin_aware_extension_middleware_factory方法中。Filter_factory返回neutron.api.extensions中的ExtensionMiddleware(l 388),在该过程中实例化了/etc/neutron/neutron.conf中配置的core-plugin和service-plugin(l 387),ExtensionMiddleware则负责了扩展资源URL的生成和资源的Application化。这部分的代码是在太过于底层,比较让人头疼,就没有细致的捉摸,不过其大致的处理思路和下面要说的core_plugin是类似的。
Core-plugin的处理发生在neutron.api.v2.router.APIRouter。APIRouter类在实例化的过程中,首先获得core_plugin(l 76)然后生成core_plugin资源的URL(l 103),最后将资源Application化为Controller的实例(l 104,l 109),Controller实例即支持大家喜闻乐见的“REST资源的增删改查”。这样当wsgi server收到REST API请求后,就能够根据请求中的URL找到资源的Controller,然后Controller会自动拼接字符串,得到并调用相应的core_plugin方法,比如所请求操作的资源是network,Action是“Create”,则应该调用的core_plugin方法就是“create_network”。
默认配置中Neutron的core_plugin为ML2,ML2在执行create_network方法时(neutron.plugins.ml2.plugin,l 365),首先通过Type Manager分配可用的network id(l 384),并向neutron database进行注册(l 385),然后通过Mechanism Manager将该network的参数传给底层各个Mechanism Driver(neutron.plugins.ml2.managers,l 193),Mechanism Driver再具体进行执行(l 168)。
讲到这里Neutron北向接口wsgi的部分就分析完了,而REST API中业务请求在网络设备中的落实则主要通过rpc机制来完成。
(二)RPC
控制端neutron-plugin与设备端相应neutron-agent间的通信一般由rpc机制实现。在开始进入Neutron rpc服务的代码分析前,我们需要先简单地了解一下rpc。 Openstack 组件内部rpc机制的实现基于 AMQP作为通讯模型。AMQP 是用于异步消息通讯的消息中间件协议,有四个重要的概念:
1)Exchange:根据Routing key转发消息到不同的Message Queue中。
2)Routing key:用于Exchange判断哪些消息需要发送对应的Message Queue。
3)Publisher:消息发送者,将消息发送给Exchange并指明Routing Key。
4)Consumer:消息接受者,从Message Queue获取消息,并在本地进行执行。
总体来说:消息发布者Publisher将Message发送给Exchange并且说明Routing Key。Exchange 负责根据Message的Routing Key进行路由,将Message正确地转发给相应的Message Queue。Consumer将会从Message Queue中读取消息。
Publisher可以分为4类:Direct Publisher发送点对点的消息;Topic Publisher采用“发布——订阅”模式发送消息;Fanout Publisher发送广播消息的发送;Notify Publisher同Topic Publisher,发送 Notification 相关的消息。类似地,Exchange可以分为3类:Direct Exchange根据Routing Key进行精确匹配,只有对应的 Message Queue 会接受到消息;
Topic Exchange根据Routing Key进行模式匹配,只要符合模式匹配的Message Queue都会收到消息;Fanout Exchange将消息转发给所有绑定的Message Queue。
了解了基础知识以后,我们接着从neutron_server启动文件主函数(neutron.server.__init__)中来分析各个neutron-plugin中rpc服务的启动。从L 52中调用的serve_rpc方法回溯,发现该方法(neutron.service l 141)只启动了core_plugin的rpc监听(l 142,l 157,l 160),而没有显示地启动service_plugins的rpc,实际上service_plugins的rpc在各个plugins实例化(在serve_wsgi方法中完成)的过程中已经启动了。
Neutron中控制端neutron-plugin和设备端相应的neutron-agent间rpc通信是单向异步的, plugin和agent上都要开启Publisher和Consumer。由于同一类的agent往往不止一个,因此Neutron rpc采用“发布——订阅”模式在plugin和agent间传递消息,在Publisher和Consumer实例化时需要指定全局唯一的Topic(规定在neutron.common.topics)。
Neutron中Publisher类的命名规范为**AgentNotifierApi,它们的实例可以向特定的Consumer发送消息,Consumer接收到消息后,通过dispatcher解封装,在调用**RpcCallBacks在本地执行消息体。Core_plugin在构造函数执行过程中实例化了AgentNotifierApi,其RpcCallBacks的实例化由neutron-server主函数中的l 52完成,如下第一张图所示;而service_plugins在构造函数执行过程中一起实例化了AgentNotifierApi和RpcCallBacks,如下第二张图所示。思路就是这个样子了,具体的代码就不再一行一行分析了。
===================== 设备端的实现 ========================
控制端neutron-server通过wsgi接收北向REST API请求,neutron-plugin通过rpc与设备端进行南向通信。设备端agent则向上通过rpc与控制端进行通信,向下则直接在本地对网络设备进行配置。Neutron-agent的实现很多,彼此之间也没什么共性的地方,下面选取比较具有代表性的ovs-neutron-agent的实现进行简单的介绍。
Ovs-neutron-agent的启动入口为/neutron/plugins/openvswitch/agent/ovs-neutron-agent.py中的main方法,其中负责干活的两行代码在l 1471和l 1476。L 1471实例化了OVSNeutronAgent类,负责在本地配置OVS,而l 1476则启动了与控制端的rpc通信。
OVSNeutronAgent的实例化过程中主要干了如下6个工作,其中各个网桥的作用请参考上一小节的介绍,流表的具体逻辑这里也不细谈了,下一节内容会专门讲。
1)L 203,通过setup_intergration_br方法启动ovs br-int网桥。该方法中通过ovs-vsctl在本地创建网桥(l 671),配置安全模式(l 672),删除patch端口(l 674),并对网桥中的流表进行初始化(l 675-679)。
2)L 206,通过setup_rpc方法启动rpc。该方法中实例化了rpc Publisher(l 262,l 263),实例化了rpc Consumer(l 268-277),启动了rpc心跳机制(l 284)。注意,Ovs-neutron-agent的rpc Consumer只监听4类topic:update_port,delete_network,tunnel_update,sg_update。
3)L 208,通过setup_physical_bridge方法启动ovs br-eth1。该方法中通过ovs-vsctl在本地创建网桥并对其进行初始化(l 832,l 833),建立与ovs br-int间的veth-pair(l 838,l 854),并封锁两个网桥间不经过veth-pair的通信(l 856,l 859),然后开启veth-pair(l 864,l 865)。
4)L227,通过setup_tunnel_br方法启动ovs br-tun。该方法中通过ovs-vsctl在本地创建网桥并对其进行初始化(l 717,l 719),建立与ovs br-int间的patch连接(l 720- 722),然后生成流表逻辑(l 730-767)。
5)L232,实例化OVSSecurityGroupAgent,开启rpc(l 119),并初始化防火墙(l 121)。
6)L237,把run_daemon_loop变量置为True(l 237),开始循环查询的工作。当run_daemon_loop变量置为True,main方法调用daemon_loop方法,之后调用rpc_loop。
rpc_loop做的工作主要就是轮询一些状态,根据这些状态,进行相应的操作。比如一旦探测到本地的OVS重启了(l 1295,l 1309),就重新创建本地的网桥(l 1294-1300),并重新添加port(l 1336);再比如一旦rpc监听到update_port事件(l 1309),则在本地使能相应的port(l 1336)。
L 1336执行的process_network_ports方法(l 1129)有必要说一下,对port的增加和更新的处理调用treat_devices_added_or_updated方法(l 1152),对port的删除调用treat_devices_removed(l 1176)方法。Treat_devices_added_or_updated方法(l 1008)调用treat_vif_port方法(l 1038),treat_vif_port方法(l 936)中调用port_bound方法(l 948),port_bound方法(l 597)调用了provision_local_vlan方法(l 611)。Provision_local_vlan方法(l 436)向各个网桥下发相应的流表,完成port与本地vlan_id的绑定工作。
ovs-neutron-agent的启动也就是这些工作了,启动完毕后,便开始了与相应plugin(OVS Plugin或者OVS Mechanism Driver)的rpc通信。
=============================== Codes End ===================================
Neutron的软件实现就简单地介绍到这里了,下一节我们来具体看看Neutron中各个OVS上的流表逻辑是怎样的。