12月技术周 | OpenStack Router原理分析


一、路由功能概述


在OpenStack中,不同网络(vlan)的虚拟机不能直接通信,除非借助路由器,可以是物理路由器或者虚拟路由器。在OpenStack中,默认使用虚拟机路由器功能,通过L3 agent来实现。


二、L3 agent概述


L3 agent 即 neutron-l3-agent 服务。运行在OpenStack的网络节点上,允许用户创建用于连接 2 层网络的虚机路由。L3 agent 实现了 3 层网络转发和 NAT功能。利用 Linux Network Namespaces,可以创建多个具有重合 IP 地址段的虚拟路由器,每个虚拟路由器都具有自己的 namespace,基于 router 在 Neutron 中的uuid来命名。


三、L3 agent的配置


1、在网络节点上,编辑l3_agent.ini,

vim /etc/neutron/l3_agent.ini

其中[DEFAULT] section的interface_driver是最重要的配置选项,用来指定

mechanism driver,neutron支持三种interface_driver,具体配置项如下:


使用openvswitch:

interface_driver =

neutron.agent.linux.interface.OVSInterfaceDriver


使用linuxbridge:

interface_driver = 

neutron.agent.linux.interface.BridgeInterfaceDriver


使用null:

interface_driver = 

neutron.agent.linux.interface.NullDriver


2、在控制节点上运行neutron命令行,检查neutron服务是否运行正常。


四、虚拟router操作


1、horizon界面创建虚拟机router,并添加网络接口。


12月技术周 | OpenStack Router原理分析

12月技术周 | OpenStack Router原理分析

2、重复操作,添加两个vlan网络到虚拟路由器,并在两个网络中分别创建一台虚拟机,拓扑效果如下:


12月技术周 | OpenStack Router原理分析


ps:后续的分析使用该环境数据:

虚拟机test1,IP 192.168.1.10, 网段192.168.1.0/24, 网关192.168.1.1

虚拟机test2,IP 192.168.2.12, 网段192.168.2.0/24, 网关192.168.2.1


3、验证虚拟机test1到test2的连通性。


五、虚拟router原理分析


1、创建虚拟router,并添加网络接口时,L3 agent会在网络节点的br-int创建一个port qr-xxx,同时创建一个qrouter-<router-id>的namespace,并在namespace里,将网段192.168.1.0/24的网关(默认是x.x.x.1)配置在qr-xxx网卡上。


a.查看br-int上的qr-xxx port

b.查看qrouter-<router-id> namespaces

c.查看namespace中的网关配置信息

c.查看qrouter-<router-id> namespace路由

d.查看虚拟机内部路由信息

ps:

a. qr-xxx的命名是使用gateway_port的id前11位

b. 使用namespace是为了解决网络地址重叠的问题

c. 该网段的所有虚拟机上在启动时,都下发一条指向网关(默认是x.x.x.1)的路由。


六、instance通过Router出外网


1、创建一个外部网络,并绑定到路由器。在内网上创建一个虚拟机test1,拓扑效果如下:


12月技术周 | OpenStack Router原理分析


实验环境:

内网: 192.168.1.0/24

外网: 10.100.1.0/24

test1 ip: 192.168.1.12,gateway:192.168.1.1

qg- ip:10.100.1.11


2、执行上述操作之后,L3 agent会在网络节点的br-int创建一个qg-xxx port,并在router namespace里,将从外网中随机分配一个ip(10.100.1.11)配置在qg-xxx网卡上,并下发一条iptables规则(snat)。


3、当test1 ping外网的ip时,会查询路由表,根据默认路由,将报文送到router。


4、当报文送达router之后,会先进入iptables的PREROUTING链,然后查询路由表。


然后会进入POSTROUTING链,做一次SNAT和连接跟踪。


5、通过以上流程实现instance到外网的通信。


七、浮动ip的实现分析


1、分配一个浮动ip,并关联到test1。


12月技术周 | OpenStack Router原理分析


绑定floating ip之后,neutron会在router的namespace中添加3条规则:dnat&snat,并将floating ip配置在qg-xxx上。


instance绑定floating ip之后,需要分析两种情况:instance到外网和外网到instance。


instance出外网


a、instance会根据默认路由将报文(源ip为fix ip:192.168.1.10)送到路由器。


b、报文到达router之后,会先进入PREROUTING链(此时不会命中,不做任何改变),然后查询路由表,准备进行转发(此时还未进行转发)。


c、随后进入iptables的POSTROUTING链,命中snat规则,将源ip修改为浮动ip,进行转发,将报文送到外网ip。


外网到instance


a、外网的报文(目的ip为floating ip:10.100.1.3)到达router之后,先进入PREROUTING链,命中dnat规则,将目的ip修改为instance的fix ip。


b、检查路由表,命中路由之后,准备从对应端口进行转发(此时还未进行转发)。


c、然后进入POSTROUTING链(未命中),进行转发,将报文送到instance。


关于九州云99Cloud

九州云成立于2012年,是中国早期从事OpenStack和相关开源服务的专业公司。公司成立六年,秉承“开源 · 赋能变革”的理念,不断夯实自身实力,已实现业务从核心云到边缘云再到垂直行业应用的拓展和积累,先后为政府、金融、运营商、能源、制造业、商业、交通、物流、教育、医疗等各大行业的企业级客户提供高质量的云计算服务。目前拥有国家电网、中国人民银行、中国银联、中国移动、中国电信、中国联通、中国资源卫星、eBay、国际陆港集团、中国人寿、万达信息、东风汽车、诺基亚等众多重量级客户,被用户认可为开源界的领军企业。

12月技术周 | OpenStack Router原理分析

12月技术周 | DVR 原理深入分析


一、DVR实验环境


一套vlan模式的openstack环境,interface_driver使用openvswitch,并在计算节点添加br-ex,用于计算节点虚拟机出外网。


二、DVR配置


1、控制节点


a、编辑/etc/neutron/neutron.conf

[DEFAULT]

router_distributed = True


b、重启neutron-server服务


2、网络节点


a、编辑/etc/neutron/l3_agent.ini

[DEFAULT]

agent_mode = dvr_snat


b、编辑

/etc/neutron/plugins/ml2/openvswitch_agent.ini

[agent]

enable_distributed_routing = True


c、重启neutron-l3-agent和neutron-openvswitch-agent服务


3、计算节点


a、编辑/etc/sysctl.conf

net.ipv4.ip_forward=1

net.ipv4.conf.all.rp_filter = 0

net.ipv4.conf.default.rp_filter = 0


b、执行命令sysctl –p


c、编辑/etc/neutron/l3_agent.ini

[DEFAULT]

interface_driver=neutron.agent.linux.interface.OVSInterfaceDriver

external_network_bridge =

agent_mode = dvr


d、编辑

/etc/neutron/plugins/ml2/openvswitch_agent.ini

[agent]

enable_distributed_routing = True


e、重启neutron-l3-agent和neutron-openvswitch-agent服务


三、启用dhcp实现metadata功能


1、默认情况下,metadata功能通过l3实现,在dvr模式下,如果使用l3实现的话,需要额外配置metadata agent,为了简化,该实验启用dhcp agent来实现metadata功能。


a、在网络节点上编辑/etc/neutron/dhcp_agent.ini

[DEFAULT]

force_metadata = True


b、重启neutron-dhcp-agent服务。


四、DVR到网关流表分析


1、创建router,关联到test1网络(网关192.168.1.1),并在test1网络内创建instance(192.168.1.9),网络模型如下:


12月技术周 | DVR 原理深入分析


执行上述操作之后,neutron会在网络节点和计算节点(有instance运行)上都创建一个router的namespace,且配置完全一样。


也就是说,平台中会出现多个相同的router namespace,那么neutron如何实现虚拟机到网关的流量只到本地的router namespace呢?答案是flow table


2、举个例子:


a、在test1内ping网关(192.168.1.1),虚拟机会先发送arp请求去学习网关的mac地址,arp报文会从qvo-xxx端口进入br-int的table0,匹配arp和in_port,根据流表项,resubmit到table24。


b、在table24中匹配arp、in_port和arp_spa,命中之后,resubmit到table25。


c、在table25中匹配in_port和dl_src,命中之后,resubmit到table60。


d、在table60中,命中table-miss flow entry,根据action执行NORMAL。


e、网关地址是配置在router namespace中的qr-xxx上,而qr-xxx是br-int上的。


端口,所以qr-xxx会收到arp请求,并进行相应,arp相应报文会从qr-xxx端口再次进入br-int table0,命中table-miss flow entry,从而进行NORMAL转发,将报文转发给虚拟机的qvo-xxx。


f、虚拟机学到网关的mac地址之后,将icmp报文通过流表,转发给网关,ping流程完成。


3、现在分析neutron如何通过flow table 实现虚拟机到网关的流量只到本地的router namespace。


在上述的d步骤中,命中table-miss flow entry,根据action执行NORMAL,报文可能通过正常转发,到达br-vlan。


a、报文到达br-vlan后,会进入table=0,匹配in_port,命中后,resubmit到table1


b、在table1中,匹配dl_vlan、arp_tpa(arp报文)或者dl_dst,从而把到网关的报文,全部drop。


c、通过上述流表,保证到达网关的报文全部终结在宿主机本地。


五、虚拟机内网通信分析


DVR模式下,虚拟机的内网通信一般有四种场景:同网络同宿主机,同网络不同宿主机,不同网络同宿主机,不同网络不同宿主机,下面将对这四种场景逐一进行分析:


1、同网络同宿主机,不需要通过router,实验模型如下


12月技术周 | DVR 原理深入分析


test1和test2运行在同一台宿主上,执行test1 ping test2。


a、test1通过arp学习到test2的mac地址,流表匹配过程和步骤4(DVR到网关流表分析)中arp学习相同,然后封装icmp报文,从qvo-xxx port进入br-int的table0,匹配in_port,命中后,resubmit到table25。


b、在table25中,匹配in_port和dl_src,命中后,resubmit到table60。


c、在table60,命中table-miss,执行NORMAL转发。


d、test2的qvo-yyy port也在br-int上,报文通过qvo-yyy发送到test2,test2回包给test流程和test1到test2相同。ping流程结束。


2、同网络不同宿主机,不需要router,实验模型如下:


12月技术周 | DVR 原理深入分析


test1和test2运行在不同宿主上,执行test1 ping test2。


此场景下,步骤a,b,c和同网络同宿主机场景步骤完全相同,报文会在br-int的table60执行NORMAL,此处不再赘述。


d、执行NORMAL后,报文会从phy-br-vlan port进入br-vlan的table0,匹配in_port,命中后,resubmit到table1。


e、在table1中,命中table-miss,resubmit table2。


f、在table2中,匹配in_port和dl_vlan,命中后,修改local vlan 1为全局vlan 196,然后执行NORMAL,此时报文会离开宿主机,从业务口发出去。


g、报文会被送到运行test2的宿主机业务口,然后进入br-vlan的table0,命中后,resubmit到table3。


h、在table3中,命中后,执行NORMAL,报文被送到br-int。


i、在br-int的table0中,匹配in_port和dl_vlan,命中后,修改全局vlan 196 为local vlan 1,然后执行NORMAL,报文会从qvo-yyy进入虚拟机test2。


j、test2回包和test1发包过程相同,至此,ping操作完成。


3、不同网络同宿主机,需要router,实验模型如下:


12月技术周 | DVR 原理深入分析


创建router分别连接两个网络,每个网络上个各创建一台instance,执行上述操作之后,neutron会在网络节点和计算节点(有instance运行)上都创建一个router的namespace,且配置完全一样,qr-设备会被配置上对应网关ip地址。


test1和test2运行在同宿主上,执行test1 ping test2。


a、test1 通过计算知道要到达test2需要走三层,它会先发arp请求,学习网关的mac地址,然后封装icmp报文(目的ip是test2的ip,目的mac是网关的mac地址),通过默认路由,将报文送到本地的router namespace。

(学习网关过程中匹配流表的过程请参考 <4、DVR到网关流表分析>章节)。


b、报文从qr-test1进入router的PREROUTING链,然后查找到test2的路由,命中之后,进入POSTROUTING链,并从qr-test2重新进入br-int的table0。


c、在table0中,命中table-miss,resubmit到table60。


d、在table60中,命中后,执行NORMAL,将报文从qvo-xxx送到test2。


e、test2回包的过程和test1发包过程相同,至此,ping操作完成。


4、不同网络不同宿主机,需要router,实验模型如下:


12月技术周 | DVR 原理深入分析


test1和test2运行在不同宿主上,执行test1 ping test2。


此场景下,步骤a,b,c和不同网络同宿主机场景步骤完全相同,

报文从qr-test2出来之后,在br-int的table60执行NORMAL,此处不再赘述。


d、在c中执行NORMAL之后,报文通过int-br-vlan port进入br-vlan的table0。


e、在table0中,匹配in_port,命中之后,resubmit到table1。


f、在table1中,匹配dl_vlan和dl_src,命中之后,修改源mac地址为neutron分配给宿主机mac,resubmit到table2。


注:开启DVR之后,neutron会给每个compute节点分配一个唯一的mac地址,避免物理交换机出现mac地址冲突的问题。


g、在table2中,匹配in_port和dl_vlan,命中之后,修改local vlan 2为全局

vlan 148,执行NORMAL,此时报文会从业务口eth1离开宿主机。


h、报文会进入运行test2的宿主机的业务口eth1进入br-vlan的table0。


i、在table0中,命中之后,resubmit到table3。


j、在table3中,匹配dl_src(neutron分配给宿主机的mac),将报文从phy-br-vlan送出给br-int table0。


k、在br-int的table0中,匹配in_port和dl_src(neturon分配给宿主机的mac),resubmit给table2。


l、在table2中,匹配dl_vlan和dl_dst,修改源mac为test2网关的mac,resubmit到table60。


m、在table60中,匹配dl_vlan和dl_dst ,剥去vlan,通过output将报文直接送到test2。


n、test2回包的过程和test1发包过程相同,至此,ping操作完成。


六、虚拟机出外网原理分析


1、创建一个router,并绑定内部网络test1,设置路由器的网关外网为external,在内网,外网上各创建一台虚拟机,用于测试,实验模型如下:


12月技术周 | DVR 原理深入分析


执行上述操作之后,neutron会在网络节点创建三个namespace:qrouter-xxx、fip-yyy、snat-xxx。


计算节点(instance运行)创建qrouter-xxx、fip-yyy。


ps:各节点的namespace会在接下来的分析中说明其作用。


2、虚拟机test1的报文被送到本宿主机router namespace的qr-xxx设备上(上述已经说明,此处不再赘述),进入PREROUTING链(未命中,不做任何修改),查看策略路由,使用默认路由准备进行转发,然后进入POSTROUTING链(未命中),报文从qr-xxx发出,送往192.168.1.11进行处理(流表全部命中NORMAL)。


3、可能有人会有疑问,这里的192.168.1.11是啥?


事实上这个ip地址是neutron分配给snap-yyy namespace的一个ip,被配置在sg-zzz上,snap-yyy namespace在网络节点上,neutron通过策略路由和sg-zzz port将计算节点router namespace中的报文,转发到网络节点的snat-yyy中。


4、此时报文到达网络节点的snat-yyy namespace中的sg-zzz port上,在路由之前报文会进入PREROUTING链(未命中),然后查找路由。


随后进入POSTROUTING链,进行snat,并做连接跟踪,之后报文进行转发。


5、经过上述操作,虚拟机通过snat-yyy中的qg-设备出外网,通过连接跟踪返回(流表命中NORMAL)。


七、Floating ip原理分析


1、在章节6的基础上,从外网分配一个floating ip(10.100.0.7),并关联到虚拟机test1(192.168.1.9)上,并需要自行配置br-ex,用于虚拟机出外网,实验模型如下:


12月技术周 | DVR 原理深入分析


执行上述操作之后,neutron会在对应的namespace里面添加若干策略,下面的分析中会逐一说明:


1、虚拟机test1的报文被送到本宿主机router namespace的qr-xxx设备上(上述已经说明,此处不再赘述),进入PREROUTING链(未命中,不做任何修改),查看策略路由。


通过策略路由,将报文通过rfp-6347c62b-2转发给169.254.109.47处理,随后进入POSTROUTING链,做snat修改。


2、到这里,大家可能会有一个疑问,这个rfp-6347c62b-2设备是啥?

事实上这个是veth pair的一端,它的另一端在fip-xxx namespace里面


neutron 使用这对veth pair,将报文从router的namespace里面,通过策略路由,转发到fip-yyy的namespace里。


3、至此,报文到达fip-yyy namespace,进入PREROUTING链(未命中),查看路由。


再进入POSTROUTING链(未命中),从而将报文从fg-7ec56cee-b5设备转发到外网。


4、至此,报文顺利从fg port发送到外网(流表命中NORMAL)。


5、现在开始分析外网设备通过floating ip进入虚拟机的情况。


假设存在一台外网设备external(10.100.0.14)去ping floating ip(10.100.0.7),外网设备首先会通过arp学习10.100.0.7的mac地址,而上述描述中,neutron并没有配置10.100.0.7的ip地址在任何设备上,也就是说,10.100.0.7并不存在,那报文是如何准确的送到fg口的呢?


事实上,neutron在fg port上开启了arp_haproxy功能,相当于进行了arp欺骗,这样,外网设备就将fg的mac地址学习成10.100.0.7的mac,并更新到自己的mac表中。


6、外网报文到达fg口之后,进入PREROUTING链(未命中),查看route表,准备将目的ip为floating ip的报文从fpr-6347c62b-2发送169.254.109.46。


随后进入POSTROUTING链(未命中),报文转发到router的namespace中。


7、报文到达router的namespace中的rfp-6347c62b-2设备,进入PREROUTING链,进行dnat操作。将目的ip从floating ip转换成内部fix ip(192.168.1.9)


随后查看route,准备将报文从qr-xxx port转发。


然后进入POSTROUTING链(未命中),将报文从qr- port转发到虚拟机test1。


8、至此,实现外网设备到内部ip的转发完成。


关于九州云99Cloud

九州云成立于2012年,是中国早期从事OpenStack和相关开源服务的专业公司。公司成立六年,秉承“开源 · 赋能变革”的理念,不断夯实自身实力,已实现业务从核心云到边缘云再到垂直行业应用的拓展和积累,先后为政府、金融、运营商、能源、制造业、商业、交通、物流、教育、医疗等各大行业的企业级客户提供高质量的云计算服务。目前拥有国家电网、中国人民银行、中国银联、中国移动、中国电信、中国联通、中国资源卫星、eBay、国际陆港集团、中国人寿、万达信息、东风汽车、诺基亚等众多重量级客户,被用户认可为开源界的领军企业。

12月技术周 | DVR 原理深入分析

12月技术周 | Nova 刚生出来的 Placement 是什么东西?


历史背景

私有云的用户,尤其是传统 IT 架构转型的私有云用户一般会拥有各式各样的存量资源系统,与这些系统对接会让 OpenStack 的资源体系变得复杂。

从用户的视角出发,或许你会希望

  • 作为使用共享存储解决方案的用户,我会希望 Nova 和 Horizon 能够正确报告共享存储磁盘资源的总量和使用量信息。

  • 作为高级的 Neutron 用户,我预期会使用外部的第三方路由网络功能,希望 Nova 能够掌握和使用特定的网络端口与特定的子网池相关联,确保虚拟机能够在该子网池上启动。

  • 作为高级的 Cinder 用户,我希望当我在 nova boot 命令中指定了 cinder volume-id 后 Nova 能够知道哪一些计算节点与 Request Volume 所在的 Cinder 存储池相关联。

所以,OpenStack 除了要处理计算节点 CPU,内存,PCI 设备、本地磁盘等内部资源外,还经常需要纳管有如 SDS、NFS 提供的存储服务,SDN 提供的网络服务等外部资源。

但在以往,Nova 只能处理由计算节点提供的资源类型。Nova Resource Tracker 假定所有资源均来自计算节点,因此在周期性上报资源状况时,Resource Tracker 只是单纯针对计算节点清单进行资源总量和使用量的加和统计。显然,这无法满足上述复杂的生产需求,也违背了 OpenStack 一向赖以自豪的开放性原则。随着 OpenStack 的定义被社区进一步升级为「一个开源基础设施集成引擎」,也意味着 OpenStack 资源体系将会由更多样的外部资源类型构成。

所以,当资源类型和提供者变得多样时,自然就需求一种高度抽象且统一的管理方法,让用户和代码能够便捷的使用、管理、监控整个 OpenStack 的系统资源,这就是 Placement(布局)

Placement

Placement 肩负着这样的使命,最早在 Newton 版本被引入 openstack/nova repo,以 API 的形式存在,所以也常被称为 Placement API。它参与到 nova-scheduler 选择目标主机的调度流程中,负责跟踪记录 Resource Provider 的 Inventory 和 Usage,并使用不同的 Resource Classes 来划分资源类型,使用不同的 Resource Traits 来标记资源特征。

NOTE:Ocata 版本的 Placement API 是一个可选项,建议用户启用并替代 CpuFilter、CoreFilter 和 DiskFilter。Pike 版本则强制要求启动 Placement API 服务,否则 nova-compute service 无法正常运行。

直至成文前段时间(2018/11),Placement API 开始了 openstack/nova repo 剥离流程,从 Placement API 蜕变为 OpenStack Placement。虽然现在的 OpenStack Placement 还不算稳定,但毋容置疑,Placement 就是 OpenStack 云平台统一资源管理的未来。

OpenStack Placement provides an HTTP service for managing, selecting, and claiming providers of classes of inventory representing available resources in a cloud.

Placement 拥有独立的 REST API。

  1. [root@control01 ~]# openstack catalog show placement

  2. +-----------+------------------------------------------+

  3. | Field     | Value                                    |

  4. +-----------+------------------------------------------+

  5. | endpoints | RegionOne                                |

  6. |           |   public: http://172.18.22.222/placement |

  7. |           |                                          |

  8. | id        | 9b5683b4a5284369bafad76944a0610f         |

  9. | name      | placement                                |

  10. | type      | placement                                |

  11. +-----------+------------------------------------------+

Placement 拥有独立的数据库和数据模型。

  1. MariaDB [(none)]> use placement


  2. MariaDB [placement]> show tables;

  3. +------------------------------+

  4. | Tables_in_placement          |

  5. +------------------------------+

  6. | alembic_version              |

  7. | allocations                  |

  8. | consumers                    |

  9. | inventories                  |

  10. | placement_aggregates         |

  11. | projects                     |

  12. | resource_classes             |

  13. | resource_provider_aggregates |

  14. | resource_provider_traits     |

  15. | resource_providers           |

  16. | traits                       |

  17. | users                        |

  18. +------------------------------+

NOTE:本篇主要分析、记录 1.0 版本的 Placement 实现。

  1. [root@control01 ~]# openstack versions show --service placement

  2. +-------------+--------------+---------+---------+---------------------------------+------------------+------------------+

  3. | Region Name | Service Type | Version | Status  | Endpoint                        | Min Microversion | Max Microversion |

  4. +-------------+--------------+---------+---------+---------------------------------+------------------+------------------+

  5. | RegionOne   | placement    | 1.0     | CURRENT | http://172.18.22.222/placement/ | 1.0              | 1.30             |

  6. +-------------+--------------+---------+---------+---------------------------------+------------------+------------------+

基本概念

Resource Provider:资源提供者,实际提供资源的实体,例如:Compute Node、Storage Pool、IP Pool 等。

Resource Class:资源种类,即资源的类型,Placement 为 Compute Node 缺省了下列几种类型,同时支持 Custom Resource Classes。

  1. [root@control01 ~]# export OS_PLACEMENT_API_VERSION=1.17


  2. [root@control01 ~]# openstack resource class list

  3. +----------------------------+

  4. | name                       |

  5. +----------------------------+

  6. | VCPU                       |

  7. | MEMORY_MB                  |

  8. | DISK_GB                    |

  9. | PCI_DEVICE                 |

  10. | SRIOV_NET_VF               |

  11. | NUMA_SOCKET                |

  12. | NUMA_CORE                  |

  13. | NUMA_THREAD                |

  14. | NUMA_MEMORY_MB             |

  15. | IPV4_ADDRESS               |

  16. | VGPU                       |

  17. | VGPU_DISPLAY_HEAD          |

  18. | NET_BW_EGR_KILOBIT_PER_SEC |

  19. | NET_BW_IGR_KILOBIT_PER_SEC |

  20. +----------------------------+

Inventory:资源清单,资源提供者所拥有的资源清单,例如:Compute Node 拥有的 vCPU、Disk、RAM 等 inventories。

Provider Aggregate:资源聚合,类似 HostAggregate 的概念,是一种聚合类型。

Traits:资源特征,不同资源提供者可能会具有不同的资源特征。Traits 作为资源提供者特征的描述,它不能够被消费,但在某些 Workflow 或者会非常有用。例如:标识可用的 Disk 具有 SSD 特征,有助于 Scheduler 灵活匹配 Launch Instance 的请求。

Resource Allocations:资源分配状况,包含了 Resource Class、Resource Provider 以及 Consumer 的映射关系。记录消费者使用了该类型资源的数量。

数据模型解析

Data Models

  • ResourceProvider:资源提供者

  • Inventory:资源提供者的资源清单

  • ResourceClass:资源种类

  • ResourceProviderAggregate:资源聚合,实际上是资源提供者和主机聚合的映射关系

  • Trait:资源特征描述类型

  • ResourceProviderTrait:资源提供者和特征描述的对应关系

  • Allocation:分配给消费者的资源状况

  • Consumer:消费者,本质是一串 UUID

  • User:Keystone User

Resource Provider Inventory 的属性

  1. MariaDB [nova_api]> desc inventories;

  2. +----------------------+----------+------+-----+---------+----------------+

  3. | Field                | Type     | Null | Key | Default | Extra          |

  4. +----------------------+----------+------+-----+---------+----------------+

  5. | created_at           | datetime | YES  |     | NULL    |                |

  6. | updated_at           | datetime | YES  |     | NULL    |                |

  7. | id                   | int(11)  | NO   | PRI | NULL    | auto_increment |

  8. | resource_provider_id | int(11)  | NO   | MUL | NULL    |                |

  9. | resource_class_id    | int(11)  | NO   | MUL | NULL    |                |

  10. | total                | int(11)  | NO   |     | NULL    |                |  总大小

  11. | reserved             | int(11)  | NO   |     | NULL    |                |  保留大小

  12. | min_unit             | int(11)  | NO   |     | NULL    |                |  最小分配单位

  13. | max_unit             | int(11)  | NO   |     | NULL    |                |  最大分配单位

  14. | step_size            | int(11)  | NO   |     | NULL    |                |  步长

  15. | allocation_ratio     | float    | NO   |     | NULL    |                |  超分比

  16. +----------------------+----------+------+-----+---------+----------------+

每个 Compute Node 缺省有 3 条 inventories 记录 0,1,2 分别对应 3 种资源种类 CPU(s)、RAM(MB) 和 DISK(GB)

  1. MariaDB [nova_api]> select * from resource_providers;

  2. +---------------------+---------------------+-----+--------------------------------------+------------------------------------------------------+------------+----------+

  3. | created_at          | updated_at          | id  | uuid                                 | name                                                 | generation | can_host |

  4. +---------------------+---------------------+-----+--------------------------------------+------------------------------------------------------+------------+----------+

  5. | 2018-05-03 09:07:01 | 2018-07-02 07:03:57 |   1 | e367ded8-9501-42b1-a18d-e7f3bab233c6 | domain-c69.22bebe01-eb68-4a5c-839d-11398df43232      |        252 |     NULL |


  6. MariaDB [nova_api]> select * from inventories;

  7. +---------------------+---------------------+----+----------------------+-------------------+--------+----------+----------+----------+-----------+------------------+

  8. | created_at          | updated_at          | id | resource_provider_id | resource_class_id | total  | reserved | min_unit | max_unit | step_size | allocation_ratio |

  9. +---------------------+---------------------+----+----------------------+-------------------+--------+----------+----------+----------+-----------+------------------+

  10. | 2018-05-03 09:07:07 | 2018-07-02 07:03:57 |  1 |                    1 |                 0 |     42 |        0 |        1 |       42 |         1 |                2 |

  11. | 2018-05-03 09:07:07 | 2018-07-02 07:03:57 |  2 |                    1 |                 1 | 407562 |     2048 |        1 |   407562 |         1 |                1 |

  12. | 2018-05-03 09:07:07 | 2018-07-02 07:03:57 |  3 |                    1 |                 2 |    736 |        2 |        1 |      736 |         1 |                1 |

如何查看消费者使用了哪一个资源提供者的哪几种资源种类及数量?e.g. 消费者(虚拟机 648bda64-1d7a-44d2-ba38-20c84959dabe)使用了资源提供者 97 的 1U/256M/1G 的 CPU/RAM/DISK 资源。

  1. MariaDB [nova_api]> select * from allocations;

  2. +---------------------+------------+-----+----------------------+--------------------------------------+-------------------+------+

  3. | created_at          | updated_at | id  | resource_provider_id | consumer_id                          | resource_class_id | used |

  4. +---------------------+------------+-----+----------------------+--------------------------------------+-------------------+------+

  5. | 2018-05-23 10:40:49 | NULL       |  97 |                   97 | 648bda64-1d7a-44d2-ba38-20c84959dabe |                 0 |    1 |

  6. | 2018-05-23 10:40:49 | NULL       |  98 |                   97 | 648bda64-1d7a-44d2-ba38-20c84959dabe |                 1 |  256 |

  7. | 2018-05-23 10:40:49 | NULL       |  99 |                   97 | 648bda64-1d7a-44d2-ba38-20c84959dabe |                 2 |    1 |

如何查看消费者分配了的资源状况?e.g. 查看分配给消费者(虚拟机 648bda64-1d7a-44d2-ba38-20c84959dabe)的资源状况。

  1. MariaDB [nova_api]> select * from allocations;

  2. +---------------------+------------+----+----------------------+--------------------------------------+-------------------+------+

  3. | created_at          | updated_at | id | resource_provider_id | consumer_id                          | resource_class_id | used |

  4. +---------------------+------------+----+----------------------+--------------------------------------+-------------------+------+

  5. | 2018-08-01 10:52:15 | NULL       |  7 |                    1 | f8d55035-389c-47b8-beea-02f00f25f5d9 |                 0 |    1 |

  6. | 2018-08-01 10:52:15 | NULL       |  8 |                    1 | f8d55035-389c-47b8-beea-02f00f25f5d9 |                 1 |  512 |

  7. | 2018-08-01 10:52:15 | NULL       |  9 |                    1 | f8d55035-389c-47b8-beea-02f00f25f5d9 |                 2 |    1 |

  8. +---------------------+------------+----+----------------------+--------------------------------------+-------------------+------+


  9. # consumer_id 消费者

  10. # resource_class_id 资源类型

  11. # resource_provider_id 资源提供者

  12. # used 分配的数量

Command Line

Placement CLI 以 OS Client Placement Plugin 的形式提供,是 openstack 指令的扩展。

Installation

  1. pip install python-openstackclient

  2. pip install osc-placement

EXAMPLE

  1. [root@control01 ~]# export OS_PLACEMENT_API_VERSION=1.17


  2. [root@control01 ~]# openstack resource provider list

  3. +--------------------------------------+-----------+------------+--------------------------------------+----------------------+

  4. | uuid                                 | name      | generation | root_provider_uuid                   | parent_provider_uuid |

  5. +--------------------------------------+-----------+------------+--------------------------------------+----------------------+

  6. | 5c5a578f-51b0-481c-b38c-7aaa3394e585 | control01 |         25 | 5c5a578f-51b0-481c-b38c-7aaa3394e585 | None                 |

  7. +--------------------------------------+-----------+------------+--------------------------------------+----------------------+


  8. [root@control01 ~]# openstack resource provider inventory list 5c5a578f-51b0-481c-b38c-7aaa3394e585

  9. +----------------+------------------+----------+----------+-----------+----------+-------+

  10. | resource_class | allocation_ratio | max_unit | reserved | step_size | min_unit | total |

  11. +----------------+------------------+----------+----------+-----------+----------+-------+

  12. | VCPU           |             16.0 |       32 |        0 |         1 |        1 |    32 |

  13. | MEMORY_MB      |              1.5 |    40959 |      512 |         1 |        1 | 40959 |

  14. | DISK_GB        |              1.0 |       49 |        0 |         1 |        1 |    49 |

  15. +----------------+------------------+----------+----------+-----------+----------+-------+


  16. [root@control01 ~]# openstack resource provider usage show 5c5a578f-51b0-481c-b38c-7aaa3394e585

  17. +----------------+-------+

  18. | resource_class | usage |

  19. +----------------+-------+

  20. | VCPU           |     5 |

  21. | MEMORY_MB      |  3648 |

  22. | DISK_GB        |     7 |

  23. +----------------+-------+


  24. [root@control01 ~]# openstack allocation candidate list --resource VCPU=1

  25. +---+------------+--------------------------------------+-------------------------+--------------------------------------------------------------+

  26. | # | allocation | resource provider                    | inventory used/capacity | traits                                                       |

  27. +---+------------+--------------------------------------+-------------------------+--------------------------------------------------------------+

  28. | 1 | VCPU=1     | 5c5a578f-51b0-481c-b38c-7aaa3394e585 | VCPU=5/512              | HW_CPU_X86_SSE2,HW_CPU_X86_SSE,HW_CPU_X86_MMX,HW_CPU_X86_SVM |

  29. +---+------------+--------------------------------------+-------------------------+--------------------------------------------------------------+

更多详细请浏览 Command Line Reference。

NOTE:这里指定 micro-version 为 1.17,通过环境变量 OS_PLACEMENT_API_VERSION 指定,也可以通过 request header OpenStack-API-Version:placement 1.17 指定。

Placement Web Application

要分析 Placement 的实现,实际是分析 placement-api service 的实现,当前 Placement 只提供单一的 API 服务。placement-api 是一个标准的 WSGI 实现,WSGI Script placement-api 存放在 usr/bin 或 /usr/local/bin 目录下,被 WSGI-capable web servers 加载启动。e.g.

  1. /usr/bin/uwsgi --procname-prefix placement --ini /etc/placement/placement-uwsgi.ini

WSGI(Web Server Gateway Interface,Web 服务器网关接口) 指代一种编程规范,将 Web 系统抽象为 Web Server、Middleware 和 Application 三个层次,实现让不同协议之间的实现可以互相通信,故称之为 “网关”。实现了 WSGI 规范的程序可称为 WSGI 组件,常见的 WSGI 组件有 mod_wsgi、uwsgi 等,Python 代码可以运行在 Apache、Nginx 等 HTTP Server 上也得益于此。简而言之,WSGI 定义了 Web Server 如何与 Python Web Application 进行交互的规则。OpenStack 项目常使用 Apache + WSGI 的组合来支撑 REST API 服务。

NOTE:如果是 Devstack 部署的环境,应用了 Apache ProxyPass 机制将 http://<ip>/placement的访问重定向到 devstack@placement-api service 处理。所以从 catalog 看见的 URL 才会长成这个样子。

  1. ProxyPass "/placement" "unix:/var/run/uwsgi/placement-api.socket|uwsgi://uwsgi-uds-placement-api/" retry=0.

当然了,这种部署方式对于开发调试有着诸多不便,所以开发者可以直接执行指令 placement-api 来启动一个用于测试的 API 服务进程。

  1. [root@control01 placement]# placement-api

  2. ...

  3. ********************************************************************************

  4. STARTING test server placement.wsgi.init_application

  5. Available at http://control01:8000/

  6. DANGER! For testing only, do not use in production

  7. ********************************************************************************

回到正题,这里我们只关注 Web Application 层面的实现。通过 setup.cfg 我们可以找到 placement-api 的程序入口。

  1. wsgi_scripts =

  2.    placement-api = placement.wsgi:init_application

顺着入口往下撸,很快就能找到 placement-api 的实现模块和 Application 类:

  1. # file: /opt/master/placement/placement/handler.py


  2. class PlacementHandler(object):

  3.    """Serve Placement API.


  4.    Dispatch to handlers defined in ROUTE_DECLARATIONS.

  5.    """


  6.    def __init__(self, **local_config):

  7.        self._map = make_map(ROUTE_DECLARATIONS)

  8.        self.config = local_config['config']

API 的 URL Routes、HTTP Method 以及 View function 的 Mapping 实现到了 ROUTE_DECLARATIONS 字典类型对象,所有的 API Resources Class 都被封装在 plcement.handlers 包下,是一个风格非常经典的 REST API 实现。不多赘述。

Placement 参与主机调度

12月技术周 | Nova 刚生出来的 Placement 是什么东西?图1. Launch Instance 调度流程图

上图可见(红色部分)Placement 主要参与了 nova-scheduler 选择目标主机的过程。以此切入,继续深究其中的调用细节。

12月技术周 | Nova 刚生出来的 Placement 是什么东西?图2. nova-scheduler 调度主机 UML 图

对于上面整个流程,我们主要关心 nova-scheduler 对 placement-api 的两次调用。第一次,nova-scheduler 向 placement-api 请求获取一组 Allocation Candidates(分配候选人),所谓 Allocation Candidates 就是能够满足资源需求的 Resource Provider。

Example

  1. GET /allocation_candidates?resources=VCPU:1,MEMORY_MB:2048,DISK_GB:100

NOTE:获取 Allocation Candidates 的实现是一系列复杂的数据库级联查询,以 query params 作为过滤条件。该例子传递了 Launch Instance 所需的 vCPU、RAM 和 Disk 资源,除此之外,还可以提供 required 和 member_of 参数,分别用于指定 Resource Traits 和 Resource Provider Aggregate 特性,使 Allocation Candidates 的获取方式更加灵活。

  1. [root@control01 ~]# openstack allocation candidate list --resource VCPU=1,MEMORY_MB=2048,DISK_GB=10 --required HW_CPU_X86_SSE2

  2. +---+----------------------------------+--------------------------------------+----------------------------------------------+--------------------------------------------------------------+

  3. | # | allocation                       | resource provider                    | inventory used/capacity                      | traits                                                       |

  4. +---+----------------------------------+--------------------------------------+----------------------------------------------+--------------------------------------------------------------+

  5. | 1 | VCPU=1,MEMORY_MB=2048,DISK_GB=10 | 5c5a578f-51b0-481c-b38c-7aaa3394e585 | VCPU=5/512,MEMORY_MB=3648/60670,DISK_GB=7/49 | HW_CPU_X86_SSE2,HW_CPU_X86_SSE,HW_CPU_X86_MMX,HW_CPU_X86_SVM |

  6. +---+----------------------------------+--------------------------------------+----------------------------------------------+--------------------------------------------------------------+

placement-api 返回给 nova-scheduler 的 JSON object with a list of allocation requests and a JSON object of provider summary objects 数据结构如下,关键在于 allocation_requests 和 provider_summaries 两个字段,它们在后续的 Scheduler Filters 逻辑中也发挥着重要作用。

  1. {

  2.  "allocation_requests": [

  3.    <ALLOCATION_REQUEST_1>,

  4.    ...

  5.    <ALLOCATION_REQUEST_N>

  6.  ],

  7.  "provider_summaries": {

  8.    <COMPUTE_NODE_UUID_1>: <PROVIDER_SUMMARY_1>,

  9.    ...

  10.    <COMPUTE_NODE_UUID_N>: <PROVIDER_SUMMARY_N>,

  11.  }

  12. }


  • allocation_requests:包含了所有能够满足需求的 resource provider 及其预期分配的资源清单。

  1. "allocation_requests": [

  2.        {

  3.            "allocations": {

  4.                "a99bad54-a275-4c4f-a8a3-ac00d57e5c64": {

  5.                    "resources": {

  6.                        "DISK_GB": 100

  7.                    }

  8.                },

  9.                "35791f28-fb45-4717-9ea9-435b3ef7c3b3": {

  10.                    "resources": {

  11.                        "VCPU": 1,

  12.                        "MEMORY_MB": 1024

  13.                    }

  14.                }

  15.            }

  16.        },

  17.        {

  18.            "allocations": {

  19.                "a99bad54-a275-4c4f-a8a3-ac00d57e5c64": {

  20.                    "resources": {

  21.                        "DISK_GB": 100

  22.                    }

  23.                },

  24.                "915ef8ed-9b91-4e38-8802-2e4224ad54cd": {

  25.                    "resources": {

  26.                        "VCPU": 1,

  27.                        "MEMORY_MB": 1024

  28.                    }

  29.                }

  30.            }

  31.        }

  32.    ],

NOTE:有趣的是,可以看见提供 VCPU/MEMORY 和 DISK 的 resource provider 并不相同,说明一台虚拟机的资源并非只能由一个 ComputeNode 提供,也可以是 ComputeNode 与外部 Storage Pool 组合提供。

  • provider_summaries:包含了所有满足需求的 resource providers 的各项资源总量和使用量信息。

  1. "provider_summaries": {

  2.        "a99bad54-a275-4c4f-a8a3-ac00d57e5c64": {

  3.            "resources": {

  4.                "DISK_GB": {

  5.                    "used": 0,

  6.                    "capacity": 1900

  7.                }

  8.            },

  9.            "traits": ["MISC_SHARES_VIA_AGGREGATE"],

  10.            "parent_provider_uuid": null,

  11.            "root_provider_uuid": "a99bad54-a275-4c4f-a8a3-ac00d57e5c64"

  12.        },

  13.        "35791f28-fb45-4717-9ea9-435b3ef7c3b3": {

  14.            "resources": {

  15.                "VCPU": {

  16.                    "used": 0,

  17.                    "capacity": 384

  18.                },

  19.                "MEMORY_MB": {

  20.                    "used": 0,

  21.                    "capacity": 196608

  22.                }

  23.            },

  24.            "traits": ["HW_CPU_X86_SSE2", "HW_CPU_X86_AVX2"],

  25.            "parent_provider_uuid": null,

  26.            "root_provider_uuid": "35791f28-fb45-4717-9ea9-435b3ef7c3b3"

  27.        },

  28.        "915ef8ed-9b91-4e38-8802-2e4224ad54cd": {

  29.            "resources": {

  30.                "VCPU": {

  31.                    "used": 0,

  32.                    "capacity": 384

  33.                },

  34.                "MEMORY_MB": {

  35.                    "used": 0,

  36.                    "capacity": 196608

  37.                }

  38.            },

  39.            "traits": ["HW_NIC_SRIOV"],

  40.            "parent_provider_uuid": null,

  41.            "root_provider_uuid": "915ef8ed-9b91-4e38-8802-2e4224ad54cd"

  42.        },

  43.        "f5120cad-67d9-4f20-9210-3092a79a28cf": {

  44.            "resources": {

  45.                "SRIOV_NET_VF": {

  46.                    "used": 0,

  47.                    "capacity": 8

  48.                }

  49.            },

  50.            "traits": [],

  51.            "parent_provider_uuid": "915ef8ed-9b91-4e38-8802-2e4224ad54cd",

  52.            "root_provider_uuid": "915ef8ed-9b91-4e38-8802-2e4224ad54cd"

  53.        }

  54.    }

NOTE:可以看出 SRIOV_NET_VF 亦被当做为一种资源类型,由专门的 resource provider 提供。

nova-scheduler 在获得了 Allocation Candidates 之后再进一步通过 Filtered 和 Weighed 机制来最终确定目标主机。然后再根据 allocation requests 和 provider summaries 数据结构来扣除目标主机对应的 resource provider 的资源使用量,这就是 nova-scheduler 第二次调用 placement-api 所做的事情。

这里不妨回顾一下 allocations tables 的内容:

  1. MariaDB [nova_api]> select * from allocations;

  2. +---------------------+------------+----+----------------------+--------------------------------------+-------------------+------+

  3. | created_at          | updated_at | id | resource_provider_id | consumer_id                          | resource_class_id | used |

  4. +---------------------+------------+----+----------------------+--------------------------------------+-------------------+------+

  5. | 2018-08-01 10:52:15 | NULL       |  7 |                    1 | f8d55035-389c-47b8-beea-02f00f25f5d9 |                 0 |    1 |

  6. | 2018-08-01 10:52:15 | NULL       |  8 |                    1 | f8d55035-389c-47b8-beea-02f00f25f5d9 |                 1 |  512 |

  7. | 2018-08-01 10:52:15 | NULL       |  9 |                    1 | f8d55035-389c-47b8-beea-02f00f25f5d9 |                 2 |    1 |

  8. +---------------------+------------+----+----------------------+--------------------------------------+-------------------+------+


  9. # consumer_id 消费者

  10. # resource_class_id 资源类型

  11. # resource_provider_id 资源提供者

  12. # used 分配的数量

  13. # 上述记录表示为虚拟机分配了 vCPU 1颗,RAM 512MB,Disk 1GB

显然,其中的 Consumer 就是要创建的虚拟机了。

Resource provider aggregates

Resource provider aggregates 是一个类似于 Host Aggregate 的功能,获取 Allocation Candidates 时,支持通过 member_of request query parameter 从特定的 Aggregate 中获取。Resource provider aggregates 非常适用于拥有不同主机聚合( e.g. 高性能主机聚合,大存储容量主机聚合)类型的生产场景中。

  • Create resource provider aggregates

  1. [root@control01 ~]# openstack aggregate create --zone nova host_aggregate_1

  2. +-------------------+----------------------------+

  3. | Field             | Value                      |

  4. +-------------------+----------------------------+

  5. | availability_zone | nova                       |

  6. | created_at        | 2018-12-08T05:49:55.051678 |

  7. | deleted           | False                      |

  8. | deleted_at        | None                       |

  9. | id                | 1                          |

  10. | name              | host_aggregate_1           |

  11. | updated_at        | None                       |

  12. +-------------------+----------------------------+


  13. [root@control01 ~]# openstack aggregate add host host_aggregate_1 control01

  14. +-------------------+---------------------------------+

  15. | Field             | Value                           |

  16. +-------------------+---------------------------------+

  17. | availability_zone | nova                            |

  18. | created_at        | 2018-12-08T05:49:55.000000      |

  19. | deleted           | False                           |

  20. | deleted_at        | None                            |

  21. | hosts             | [u'control01']                  |

  22. | id                | 1                               |

  23. | metadata          | {u'availability_zone': u'nova'} |

  24. | name              | host_aggregate_1                |

  25. | updated_at        | None                            |

  26. +-------------------+---------------------------------+


  27. [root@control01 ~]# openstack aggregate show host_aggregate_1

  28. +-------------------+----------------------------+

  29. | Field             | Value                      |

  30. +-------------------+----------------------------+

  31. | availability_zone | nova                       |

  32. | created_at        | 2018-12-08T05:49:55.000000 |

  33. | deleted           | False                      |

  34. | deleted_at        | None                       |

  35. | hosts             | [u'control01']             |

  36. | id                | 1                          |

  37. | name              | host_aggregate_1           |

  38. | properties        |                            |

  39. | updated_at        | None                       |

  40. +-------------------+----------------------------+


  41. [root@control01 ~]# openstack resource provider list

  42. +--------------------------------------+-----------+------------+--------------------------------------+----------------------+

  43. | uuid                                 | name      | generation | root_provider_uuid                   | parent_provider_uuid |

  44. +--------------------------------------+-----------+------------+--------------------------------------+----------------------+

  45. | 5c5a578f-51b0-481c-b38c-7aaa3394e585 | control01 |         26 | 5c5a578f-51b0-481c-b38c-7aaa3394e585 | None                 |

  46. +--------------------------------------+-----------+------------+--------------------------------------+----------------------+


  47. [root@control01 ~]# openstack resource provider aggregate list 5c5a578f-51b0-481c-b38c-7aaa3394e585

  48. +--------------------------------------+

  49. | uuid                                 |

  50. +--------------------------------------+

  51. | 5eea7084-0207-44f0-bbeb-c759e8c766a1 |

  52. +--------------------------------------+


  • List allocation cadidates filter by aggregates

  1. # REQ

  2. curl -i "http://172.18.22.222/placement/allocation_candidates?resources=VCPU:1,MEMORY_MB:512,DISK_GB:5&member_of=5eea7084-0207-44f0-bbeb-c759e8c766a1"

  3. -X GET

  4. -H 'Content-type: application/json'

  5. -H 'Accept: application/json'

  6. -H 'X-Auth-Project-Id: admin'

  7. -H 'OpenStack-API-Version: placement 1.21'

  8. -H 'X-Auth-Token:gAAAAABcC12qN3GdLvjYXSSUODi7Dg9jTHUfcnF7I_ljmcffZjs3ignipGLj6iqDvDJ1gXkzGIDW6rRRNcXary-wPfgsb3nCWRIEiAS8LrReI4SYL1KfQiGW7j92b6zTz7RoSEBXACQ9z7UUVfeJ06n8WqVMBaSob4BeFIuHiVKpYCJNv7LR6cI'


  9. # RESP

  10. {

  11.    "provider_summaries": {

  12.        "5c5a578f-51b0-481c-b38c-7aaa3394e585": {

  13.            "traits": ["HW_CPU_X86_SSE2", "HW_CPU_X86_SSE", "HW_CPU_X86_MMX", "HW_CPU_X86_SVM"],

  14.            "resources": {

  15.                "VCPU": {

  16.                    "used": 5,

  17.                    "capacity":