综述
众所周知,OpenStack 是目前开源界第二大的项目,参与的厂商之多可谓少见,实属发展的分布式系统软件大作,面对这么一个项目,我用自己在网络方面的经验与大家分享以 Neutron 为例的 OpenStack 软件设计。
Neutron 简介
Neutron 是 OpenStack 的虚拟网络组件,用洋气点的话说,就是一个 SDN 控制器。为什么我们需要虚拟网络?过去我们只给客户提供虚拟机,你花钱,我租你一台,想连接上就再买个公网 IP,就像很多人在 DigitalOcean 做得一样(当然 DigitalOcean 现在也有虚拟网络)。那有了虚拟网络可以干什么呢?我们来看一下 UnitedStack 控制面板里的一张图:
这是一个所见即所得网络架构!我们可以在上面随意的增加、修改和删除2、3层网络、路由器和虚拟机。而且通过虚拟网络,为用户提供自助式的安全组、VPN、负载均衡设备等高级服务也将方便许多。
此外,从软件设计角度来看 Neutron,它做到了 Unified API、Small Core、Pluggable Open Architecture 和Extensible,可以说是一个设计精良的系统。
架构
Neutron 的组件设计
- 需要借助 Keystone 完成认证服务
- 由 neutron-server 接受请求
- 组件间用消息中间件完成交流
- 消息会在 Server、Plugin 和 Agent 间传递
- Agent 和 Plugin 与数据库交互
- Agent 需要调用 Provider 完成功能
下面我们微观的看一下 Neutron:
Neutron Server
在上面的图里我们可以看到,Neutron 的服务端是个很复杂的模块,他的复杂之处主要体现在它在”微内核“的基础上实现了 Plugin、Service Plugin、Extension 的插拔,及其与 DB 的交互。
先从请求的入口说起。简单的说,Neutron Server 就是一个 WSGI Application,WSGI 的好处相信不必我多说,它可以方便的增加或者抽取 Middleware,就像一个洋葱一样,把最核心的东西放在里面,别的各种类似 Filter 的东西一层一层叠在外面就好了。借用一个 Pylon 官方文档的图:
只说核心 App,APIRouter,顾名思义,它是用来路由 API 请求的,把 API 请求真正发往那些实现功能的函数,在这里 Neutron 会加载 core plugin 和 plugin extension,扩展它的 Resource map,针对每一个资源集合(比如 networks、subnets、ports 等)建立 Controller,以后 API 将被路由到 Controller 去,Controller 再去调用对应的 create、get、update、delete 方法。同时在这里还可以做一些通用的检查,比如权限、数据有效性、自动转化数据格式等等。
如何清晰的表达一个资源?首先要说明这是一个扩展的资源,而不是新的资源,然后它扩展的是 port,接着是否允许 post、put,需要怎么转换数据格式,用什么做数据验证,是否要做权限检查,默认值多少,在 get 资源时是否显示,就算不懂 Neutron 的人也能看懂这些语句的意思。这里体现了 Neutron/OpenStack 的代码框架设计的优雅高明的地方,节省了程序员写 dirty code 的时间,减少了初学者的学习门槛。
Core Plugin and Extensions
为什么在Middleware里要加 Extension 相关的 Filter?很简单,因为 Extension 扩展了 Resource Map,我们需要配置其相应的 URL,那么基础资源是哪些呢?在 Neutron 里,经过一些考虑后,我们把三类资源定义为核心资源,这三类资源分别是network、subnet、port。这三类资源的实现呢,对应的要求由 Core Plugin 来实现。
用户将可以在配置文件里自由的设置 Plugin,然后再由NeutronManager加载。其结构也很清晰,CommonDbMixin完成基础的 DB 操作,比如在查询时增加 hook,设置 filter 等,NeutronDbPluginV2做与 Core Plugin 相关的数据库操作,最后 Core Plugin 继承NeutronDbPluginV2以及扩展的类。那么_supported_extension_aliases是做什么的呢,我们前边说 Server 有一层 Filter 去加载 Extension 的 Resource Map,它在查找 Extension 时呢,是先根据目录进行查找的,查找 extensions 目录下的所有 Python 文件,然后每个 extension 都会有一个 alias 名字,extension manager 再去查我们当前加载的 Core Plugin 是否支持这个 Extension,如果支持则加载,否则在日志里记录一下这个"extension not supported by plugin"。
Service Plugin
刚才只提到了加载 Core Plugin 和 Extensions,其实加载 Core Plugin 之后就加载 Service Plugin 了,而且因为 Core Plugin 和 Service Plugin 有很多相似的地方,所以 Core Plugin 很多地方也都按照 Service Plugin 去处理,就比如说 Resource Map,我们核心资源是写在RESOURCE_ATTRIBUTE_MAP里的,Plugin Extension 由 Extension Manager 加载,那 Service Plugin 呢?刚才我们说 Extension Manager,并没有区分什么 Plugin Extension Manager 或者 Service Extension Manager,就是因为这两个确实是不做区分的,而且很重要的一点是我们统一的只由 Extension 去扩展资源,就如前面 Filter 只有 Extension 但没有别的,所以如果你需要扩展 Resource Map,那么写一个扩展,然后把它加在你的 Service Plugin 的 supported_extension 里,Server 就可以接受相应的请求并路由了。
Agent
把服务端做得优雅,微内核之后,我们就要将调用发到 Agent 端去干活了,首先我们要解决的是 RPC 远程调用的问题。为了简化设计,多应用已成熟的东西,Neutron 或者别的 OpenStack 组件都是使用现有的 MessageQueue 服务完成的,而且 MessageQqueue 的调用有单独抽象出来一个模块叫做 Oslo.Messaging,于是对于 Neutron、Nova 这些服务它们也就不需要去考虑底层的 MQ 支持哪些,该如何调用这些问题了。目前 Oslo.Messaging 支持的 MQ 组件有 RabbitMQ、ZeroMQ 和 Qpid。我们平时用的比较多的 MQ Backend 主要是 RabbitMQ,其对 Topic 类型消息的处理过程大概如图所示:
OK,现在消息能发能收了,但可以通讯还不够,我们需要实现远程过程调用,俗称 RPC,在 Neutron 里这主要涉及到两个类:
右边的Rpcroxy是服务端联系 Agent 用的,而RpcCallback是 Agent 联系服务端用的。在无论消息段还是服务端,都会运行来自oslo.messaging的MessageHandlingServer或类似的类,也就是一个个用绿色线程启的Rpcorker,当RpcWoker收到任务时会去执行相应的调用,在一些 Agent (比如 L3 Agent)里还实现了优先队列,以保证高优先级的任务被首先执行。
总结
将分散在全世界的数百名来自不同厂家,专注不同业务的工程师集合起来,完成一个目前没有成熟标准和现成学习者的软件,不得不说开源社区的力量很强大,它既要保证代码的优秀、自然,还要兼顾不同厂商的利益与差异,能在 3 年多的时间完成一个现在我们已经敢把它部署在 UnitedStack 线上,同时运营着共有云和托管云,支撑数千名客户的稳定高效使用的虚拟网络组件已经表现不俗。在此希望所有对网络虚拟化感兴趣的同学加入进UnitedStack,我们并肩打造一个更加优秀的云计算平台!
转载自:http://mp.weixin.qq.com/s__biz=MjM5NjUxNDIwNw==&mid=201853844&idx=1&sn=4567bf6952dc8dd37c25f3afe64810bf&scene=2&from=timeline&isappinstalled=0#rd,作者UnitedStack王为。