也来说说Neutron的组件及其RPC通讯

1.Neutron中的各个角色

Neutron虽然只是OpenStack中的一个角色,网络,但已经足够复杂,它内部又有很多概念。(这些概念在其他的openstack角色之间也是通用的),初学者动不动就被吓得体无完肤,挣扎了近一个月,饱经摧残,真相总算渐渐浮出水面,谢谢那些帮助我的老员工,在KPI压力很大的情况下,依然抽出时间回答我的问题,bless。

Neutron可以认为是一个中心,四周插件遍地扩展的结构,中心是Core Plugin,扩展(Extention)呢,就是单独完成独立功能的一个个组件,比如负载均衡LBaaS,防火墙FWaaS之类的。他们都是以扩展的方式嵌在Core Plugin的周围。

1.1.Neutron的基础

先回答个问题:Neutron的最最基础的功能是什么呢?
管理网络,再基础一点呢?管理二层网络。没错是二层网络,所以Neutron管理的核心3大件是:Port,Network,和Subnet。至于路由,dhcp,负载均衡,防火墙都是上层的扩展。

1.2.Neutron Server

我们要介绍的第一个大角色,Neutron Server是一个实体,是一个(组)实实在在进程,它的任务这么几个:

  • 接受API过来的指令,比如创建一个network,删除一个router等等
  • 通过RPC和各个Agent通讯
  • 调度,比如一个网络的2个DHCP Agent,应该调度在那些网络节点上
  • 对插件的管理和使用。Neutron本身可以很简单,但为了支持复杂的功能,可以通过扩展不断的添加再添加

1.3.AMQP

AMQP,全称是啥?Advanced Message Queue Protocol? 也许吧,反正就是一种协议,规定了不同角色之间传递消息的格式是什么。有很多实现了AMQP协议的开源方案,我们这里用的是QPID,QPID也是一组进程,它的职责就是收到一个消息后路由给响应的对象,大家可以看Openstack RPC 通信原理

1.4.ML2

最著名,最基础的plugin,也就是core plugin。抽象出来一层逻辑,屏蔽下层复杂的的二层网络,flat,vxlan,gre之类的,都抽象成对上面3个基础组件的管理,port,network,以及subnet。

Service 与 Plugin

我理了好久关于Neutron里各个概念的意思,也看到过云耳的一片博客如何区分 OpenStack Neutron Extension 和 Plugin
但我觉得他理解的还是不对。我的理解是这样的:
从最基础的管理角度来说,core plugin管理最基础的网络组件Port,Network和Subnet。当有更丰富的网络功能需要填充的时候,比如lbaas,fwaas,vpnaas等,这些功能不是很核心,然后怎么办呢,它们是以service的方式扩展出来的。
我更觉得Core Plugin应该改名字为Core Serivce。
一个标准的Service包含4个大件:

  • Plugin. 该逻辑在Neutron Server里的实现,包含操作数据库
  • Agent. 该逻辑在网络节点的代码,接受Neutron Server的指令
  • Driver. 在Agent端驱动数据流
  • 调度. 调度给哪个Agent处理

1.5.Extention

终于提到了扩展,扩展是对Core Plugin和Service的扩充,比如,对调度模块增加一种算法,比如network需要增加一个多AZ属性等,都需要通过Extention来实现,那问了,为啥不是直接该调度和network结构呢?
显然么,作为一个被普遍使用的产品要考虑升级的兼容性,虽然升了级,但不想用这个extention,不夹在这个extentions即可。
使用下面的命令,可以看当前的neutron开启了哪些扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
neutron@mynet-206-215-44 ~ $ neutron ext-list
+-----------------------+-----------------------------------------------+
| alias | name |
+-----------------------+-----------------------------------------------+
| ext-gw-mode | Neutron L3 Configurable external gateway mode |
| security-group | security-group |
| l3_agent_scheduler | L3 Agent Scheduler |
| fwaas | Firewall service |
| binding | Port Binding |
| provider | Provider Network |
| agent | agent |
| quotas | Quota management support |
| dhcp_agent_scheduler | DHCP Agent Scheduler |
| l2pop_fdb_entries | l2pop_fdb_entries |
| multi-provider | Multi Provider Network |
| external-net | Neutron external network |
| router | Neutron L3 Router |
| allowed-address-pairs | Allowed Address Pairs |
| extra_dhcp_opt | Neutron Extra DHCP opts |
| extraroute | Neutron Extra Route |
+-----------------------+-----------------------------------------------+

我个人认为扩展是针对功能说的,一个extention支持一个或几个功能,但从软件的角度讲,扩展包括下面几个部分:

1.5.1.Agent

Agent是相对于Neutron Server的,一般管Nentron Server所在的点叫控制节点,Agent所在的节点为网络节点,虚拟机所在的节点为计算节点。
Agent是干嘛用的呢,字面意思就能看出来,向Agent所在的机器执行neutron server发过来的指令,在有必要的情况下也向neutorn server反馈一些信息。

1.5.2.Plugin

了解了Agent后,Plugin就轻松一些了,Plugin可以认为是Agent在Neutron Server端的逻辑,因为Neutorn是插件化的东西,比如想基于neutron开发一个功能负载均衡,需要在neutron server端和agent端各自实现负载均衡的逻辑,要不然只在Agent端实现了,neutron server这边不知道具体逻辑是什么,怎么通讯呢?
此外,Plugin还有一个很重要的功能,就是读写数据库,因为一般一个功能都需要读写数据库。

1.5.3.Driver

Extention的Driver基本上是和Agent在一起的,一个进程里包含这2个部分。Agent主要负责和Neutron Server(plugin)的交互,而driver呢,主要用于实现对该extention的数据平面对应的管理平面的配置。
说的有点绕啊,举个例子就明白了:
比如你想用haproxy实现一个负载均衡的扩展,LBAgent用于从Neutron Server接受创建/销毁 负载均衡实例的请求,然后告诉driver处理,driver干什么呢?就是生成haproxy的配置文件,然后启动一个haproxy的进程来加载配置文件。
所以Driver还是只是管理平面的一个东西。

2.Neutron角色之间的的RPC通讯

2.1.RPC是什么

好,基本概念介绍完了后,我们可以开始我们的主题了,那就是RPC通讯,一般来讲,RPC通讯就是Agent和Plugin的通讯,或者说是Agent和Neutron Server的通讯,其实Plugin本身对外只提供函数,从AMQP消息翻译成对应的Plugin里的函数的操作已经在Neutron Server里完成了。所以基本上就是Agent发一个消息,Plugin里就有一个函数被调用了,我们这些主要想弄清它们是怎么关联的。

2.2.基本规则

通讯分2端,一般我们把主动发起的这段叫客户端,另一端为server端。RPC也分2个方向上的通讯,从Agent到Neutron Server和反过来的。

2.2.1.客户端

客户端都是通过继承RpcProxy来实现,这个类不能直接找到,在rpc_inner目录里的proxy.py。 派生类的命名方式都是XxxxApi的格式。
然后还可以细化成两类:

  • XxxPluginXxxApi: 这类基本上都是Agent发起的对Plugin的调用,在Agent端封装发送消息,在Plugin端实现动作。
  • XxxNotifierApi: 这类基本上是在Plugin(Neutron Server)封装发送消息,在Agent端实现动作。
    客户端继承了这些Api,只是发送出来消息而已,需要在Server端实现对应的动作,可以简单的看一点代码:
    比如DHCP Agent想为一个用户启动一个服务dnsmasq的时候,需要先创建一个port,但这个port自己创建不了,需要通知neutron server,然后通过openvswitch来做,这个在DhcpPluginApi是这么干的:
    [python]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class DhcpPluginApi(proxy.RpcProxy):
    def create_dhcp_port(self, port):
    """Make a remote process call to create the dhcp port."""
    port = self.call(self.context,
    self.make_msg('create_dhcp_port',
    port=port,
    host=self.host),
    topic=self.topic)
    if port:
    return dhcp.DictModel(port)
    所以它只是从客户端的角度发出来一个msg而已。

    2.2.2.服务端

    上面提到过客户端是怎么干的,那么服务端怎么找到对应的逻辑呢,我们虽然不需要知道,怎么从msg翻译成函数的,但需要知道在Neuron端如何实现相应的逻辑:
  • Neutron Server作为服务端
    在这里也有类似的命名规则:
    通常实现逻辑的类都叫XxxRpcCallback,或者后面有个Mixin,XxxRpcCallbackMixin,比如上面的dhcp的例子,在Neutron Server里,会有一个类DhcpRpcCallbackMixin里实现一个create_dhcp_port的功能。

那么这次callback是怎么注册的呢?让Neutron Server知道这个消息来了,该谁处理。
通常callback是plugin的一部分,plugin初始化的时候,会创建consumer,并说我关注哪些topic。比如Ml2Plugin是这么干的:

[python]
1
2
3
4
5
6
7
8
def start_rpc_listener(self):
self.callbacks = rpc.RpcCallbacks(self.notifier, self.type_manager)
self.topic = topics.PLUGIN
self.conn = c_rpc.create_connection(new=True)
self.dispatcher = self.callbacks.create_rpc_dispatcher()
self.conn.create_consumer(self.topic, self.dispatcher,
fanout=False)
return self.conn.consume_in_thread()

上面是说这个callback关心PLUGIN topic

而openvswitch的plugin是这么干的:

[python]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def setup_rpc(self):
# RPC support
self.service_topics = {svc_constants.CORE: topics.PLUGIN,
svc_constants.L3_ROUTER_NAT: topics.L3PLUGIN}
self.conn = rpc.create_connection(new=True)
self.notifier = AgentNotifierApi(topics.AGENT)
self.agent_notifiers[q_const.AGENT_TYPE_DHCP] = (
dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
)
self.agent_notifiers[q_const.AGENT_TYPE_L3] = (
l3_rpc_agent_api.L3AgentNotify
)
self.callbacks = OVSRpcCallbacks(self.notifier, self.tunnel_type)
self.dispatcher = self.callbacks.create_rpc_dispatcher()
for svc_topic in self.service_topics.values():
self.conn.create_consumer(svc_topic, self.dispatcher, fanout=False)
# Consume from all consumers in a thread
self.conn.consume_in_thread()

上面是说这个callback关心那2个topic,即PLUGIN和L3PLUGIN
所以来一个msg后,先找配置的TOPIC的consumer,然后根据消息的名字,找处理函数。

  • Agent作为服务端
    此外还有另一个方向的,即Neutron Server要向Agent广播消息,比如一个port创建了,需要告诉所有的OVSAgent,干嘛干嘛。
    这时候Neutron Server这端,是通过继承NotifierAPI来通知的,然后这时候Server端就是Agent端了。
    那么Agent端怎么做才能收到,处理这些消息呢?2种方式:
    继承RpcCallback方式
    这个方式和Neutron Server端的实现一样,继承某某RpcCallback函数,然后在初始化的时候创建consumer。OVSNeutronAgent就这么干的
    [python]
    1
    2
    class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
    l2population_rpc.L2populationRpcCallBackMixin):
    Service的方式
    不需要继承RpcCallback函数,但需要实现一样的逻辑函数,然后通过neutron_service.Service.create来创建一个Service,这个Service会根据收到的消息,调用你的实现函数,DHCPAgent就这么干的。
    [python]
    1
    2
    3
    4
    5
    server = neutron_service.Service.create(
    binary='neutron-dhcp-agent',
    topic=topics.DHCP_AGENT,
    report_interval=cfg.CONF.AGENT.report_interval,
    manager='neutron.agent.dhcp_agent.DhcpAgentWithStateReport')
    DhcpAgentWithStateReport是DHCPAgent的实现类,里面有callback函数,比如network_create_end, networkd_delete_end等。