本来这一节打算写Neutron中各个OVS上的流表逻辑的,突然想起来之前学习Neutron时深入研究过的一个问题——虚拟机接入OVS是如何实现的?既然流表是为了虚拟机通信用的,那么没有虚拟机的接入,流表也就没有了用武之地。因此,本节就来讲一讲OpenStack中虚拟机启动过程中,是如何获取MAC、IP地址,如何在Neutron上绑定port,以及如何获取IP地址的。这一节就当做是对上一节“Neutron的软件实现”的一个补充吧。
从头开始讲。虚拟机的启动通常来自于控制节点命令行的nova boot,该命令被组装成REST API送到nova-api。Nova-api与neutron-server干的是一样的活:接收REST请求,然后跑一些调度机制,计算出虚拟机部署的位置,然后通过rpc与相应计算节点上的agent——nova-compute进行通信,而启动虚拟机的实际工作由nova-compute完成。
当然,以上过程与网络并没有发生什么关系,这里不去分析具体的代码实现,有兴趣的读者请移步http://www.linuxqq.net/archives/1277.html。
假定nova-compute已经通过rpc收到了开始干活的命令,我们就从这里开始漫长的代码分析。在此之前,先来看一看OpenStack组件层面的调用流程。这里借用OpenStack大神SammyLiu的一张图吧,图中1-6步骤依次做了这么几件事:
- Nova-compute向neutron-server请求虚拟机对应的Port资源。
- Neutron-server根据neutron-database生成Port资源。
- Neutron-server通知Dhcp agent虚拟机信息。
- Dhcp agent将虚拟机信息通知给dhcp server。
- 虚拟机接入并启动。
- 虚拟机从dhcp server处获得IP地址。
最后一步就是传统的dhcp的交互过程,这里就不讲了,下面来看1-5的实现。在开始具体的代码解读之前,我们首先给出码的主体思路。
- Nova-compute向neutron-server发送create_port的REST API请求,生成新的Port资源。
- Neutron-server收到该REST请求,通过APIRouter路由到ML2的create_port方法。该方法中,获得了neutron-database新生成的Port,并通知ML2 Mechanism Driver该Port的生成。
- Nova-compute向neutron发送update_port的REST API请求,
- Neutron-server收到该REST请求,通过APIRouter路由到ML2的update_port方法。该方法中,在neutron-database更新该Port的状态,并根据ML2 Mechanism Driver的不同,决定后续的处理:若Mechanism Driver为hyperv/linuxbridge/ofagent/openvswitch,则需要通过ML2的update_port方法中执行rpc远程调用update_port;对于其余的Mechanism Driver,ML2的update_port方法调用其的update_port_postcommit方法进行处理,这些Mechanism Driver可能使用非rpc方式与自身的agent通信(如REST API、Netconf等)。
- ML2执行完update_port方法后,Port资源在wsgi中对应的Controller实例通过DhcpAgentNotifyAPI实例rpc通知给网络节点上的dhcp agent(也可能通过一些调度机制通知给分布在计算节点上的dhcp agent)。
- Dhcp agent收到该rpc通知,通过call_driver方法将虚拟机MAC与IP的绑定关系传递给本地的DHCP守护进程Dnsmaq。
- Nova-compute通过libvirt driver的spawn方法将虚拟机接入网络,然后启动虚拟机。
下面开始一步步地回溯相关代码。先提醒大家做好看到头晕的准备,不过只要狠下心来把这一节的代码搞懂了,OpenStack中基础网络功能的实现,可以说也就能搞懂一大半了。
====================== Codes Start ========================
(一)Nova-compute向neutron-server发出Port资源的REST请求
首先,nova-compute接收远端的rpc调用,入口方法为nova.compute.manager l 2173的run_instance方法,方法被多个修饰符包装,最终执行_run_instance方法(l 2183),其中参数node为当前物理宿主机,instance为待启动的虚拟机。
_run_instance方法(l 1237)中主要做了两件事情。第一件事是调用_prebuild_instance方法(l 1254)做一些预备工作,包括检查instance的唯一性(l 1287),更新nova资源追踪器中该虚拟机的状态(l 1290)等。第二件事是调用_build_instance方法(l 1265)开始构建虚拟机。
_build_instance方法(l 1328)中做了三件涉及网络的事情,最后将虚拟机接入网络并启动虚拟机(l 1385)。最后的处理这里暂且不提,涉及网络的前两件事件是由libvirt生成一个物理宿主机可用的MAC地址集合(l 1361),以及生成虚拟机的dhcp选项(l 1362),dhcp选项数据结构如下图所示(nova.virt.baremetal.pxe)。
_build_instance方法干的第三件事调用_allocate_network方法获取虚拟机网络参数(l 1364)。从_allocate_network方法跳入_allocate_network_async方法(l 1565),开始循环尝试(l 1580)获取虚拟机网络参数(l 1582)。
进入nova.network.neutronv2.api的allocate_for_instance方法(l 197),该方法负责与neutron-server进行REST API的通信,以获取虚拟机相应的网络资源。该方法比较长,这里把代码粘在下面,并用红框勾出两个主要的功能区块。
第一个区块中,分析业务请求的MAC是否在宿主机可用的MAC地址池内。第二个区块中,判断业务请求的Port是否已经存在,如果存在则向neutron-server发出update_port的REST API,否则需要先create_port再update_port。
到这里为止,nova-compute就完成了Port资源的申请,总结起来就是接收业务请求的虚拟机参数,然后将涉及网络的请求通过REST API扔给neutron-server。
(二)Neutron-server的工作
Port是3种核心资源里面的一种,neutron-server通过APIRouter将这个create_port或者update_port请求路由到ML2 plugin中(具体请参考上一节“Neutron的软件实现”中wsgi部分的介绍),假设Port是第一次申请,要先create再update。
2.1)Ml2对create_port的处理
收到create_port的REST API后,由ML2(neutron.plugins.ml2.plugin)执行create_port方法。该方法主要做了3个工作:第一是在neutron-database中新建该Port的信息(l 634),第二是进行port_binding(l 639),第三是将请求交给Mechanism Driver去完成Port在网络中的部署(l 649)。
首先,来看l 634中ML2调用的数据库方法。Ml2Plugin继承了neutron.db.db_base_plugin_v2.NeutronDbPluginV2,l 634中即执行了NeutronDbPluginV2类中的create_port方法,该方法中主要做了两个事情:第一是分配可用的MAC地址(l 1364-1373),如何业务请求的MAC地址可用,即为其分配该MAC地址,如果不可用则DB生成一个可用的MAC地址;第二是在虚拟机所在租户子网的地址池中获取IP地址(l 1376)。最后该方法返回了该Port(l
1413)。
接下来看l 639中port_binding(l 210)的处理。前面进行一堆数据库中Port属性的赋值(包括host,vnic_type和profile),l 259调用各个neutron.plugins.ml2.manager中Mechanism Manager的bind_port方法(l 440),该方法再调用(l 458)各个Mechanism Driver的bind_port方法。该方法有的driver实现了(hyperv/linuxbridge/ofagent/openvswitch),记录了segment_id,vif_type等信息,有的则直接pass掉了。
ML2的create_port方法执行最后一个工作(l 649),将请求交给Mechanism Driver去完成Port在网络中的创建。这个工作各家的实现又是天差地别,有的不处理(hyperv/linuxbridge/ofagent/openvswitch),有的通过Netconf(Cisco Nexus,Brocade),有的走rpc(Arista),还有的走REST API(ODL、ONOS),这里就不具体讲了。
2.2)Ml2对update_port的处理
收到update_port的REST API后,由ML2(neutron.plugins.ml2.plugin)执行update_port方法。该方法主要做了两件事:第一是向neutron-database更新Port状态(l 672);第二是将请求交给Mechanism Driver去完成Port在网络中的更新(l 698),更新后需要进行以下判断,由于hyperv/linuxbridge/ofagent/openvswitch这类driver自身并没有实现向下的通信机制,因此需要ml2在update_port方法中调用rpc的notify方法将update_port事件传给相应的agent(l
707)。Agent执行update_port事件的代码分析请参考上一节“Neutron的软件实现”中agent部分的分析。
以上步骤的代码处理与2.1中类似,这里就不再详细说了。
(三)Neutron与dhcp-agent间的rpc
Port资源生成好了,还需要向Dhcp server更新MAC地址与IP地址的绑定关系,这依赖于rpc通信两端的工作。
3.1)Neutron发出dhcp通知
这部分代码难找得要命,其机制在于wsgi的Port Controller(neutron.api.v2.base)在update方法(l 492)中不仅完成了到Ml2Plugin update_port方法的路由(l 528),还在其后实现了向dhcp-agent的rpc通知port_ update_end(l 549)。
3.2)Dhcp-agent接收rpc通知
Dhcp-agent的入口文件为neutron.agent.dhcp_agent,在初始化的过程中实例化了rpc的Publiser(l 77),可以主动地向neutron同步dhcp的数据库状态,当然dhcp-agent也可以被动地监听neutron发出的rpc通知。当收到port_update_end通知后,执行l 308的同名方法。
(四)Dhcp-agent向dhcp-server更新虚拟机地址
在port_update方法中,调用call_driver方法(neutron.agent.dhcp_agent,l 314)找到dhcp-server(dnsmaq),执行dnsmaq的reload_allocations方法(neutron.agent.linux.dhcp,l 394),之后开始加载虚拟机的地址信息。
(五)Nova-compute将虚拟机接入,并启动虚拟机
转了一大圈,回到了起点nova-compute(nova.compute.manager),前面(一)中提到了在_build_instance方法(l 1328)的最后(l 1385),nova-compute完成了启动虚拟机的收尾工作_spwan方法。_spwan方法(l 1791)调用nova.virt.baremetal中BareMetalDriver的_spwan方法(l 111),该方法主要包括两个工作,首先是调用_plug_vifs方法(l 250)将虚拟机接入网络,然后启动虚拟机(l 272-277)。
======================= Codes End ========================
到这里,虚拟机启动过程中的网络处理就都结束了,虚拟机间就可以开始通信了。下一节分析Neutron中OVS的流表逻辑,看看OVS是怎么支持虚拟机间的通信的。