OpenStack计费项目Cloudkitty系列详解(一)
云计算是一种按需付费的服务模式,虽然OpenStack前期在计量方面走了些“弯路”,但现在的ceilometer、gnocchi、aodh、panko项目的稳步并进算是让其峰回路转。然而,目前来看OpenStack的计费项目Cloudkitty并未柳暗花明,为此,借助本文向大家介绍Cloudkitty的架构、用户使用指导、开发以及社区方面的最新动态,希望可以吸引更多方面的关注、使用和社区参与。
当前upstream版本Cloudkitty可以完成虚拟机实例(compute)、云硬盘(volume)、镜像(image)、网络进出流量(network.bw.in, network.bw.out)、浮动IP(network.floating)的计费。得益于Cloudkitty的巧妙而优秀的设计,软件插件化思想更是体现的淋漓尽致,使得添加新的计费源异常容易,版本升级也十分方便。同样也能方便将Cloudkitty用于cloudstack、VMWare等环境中。
Cloudkitty主要依赖于遥测相关的项目,包括ceilometer和gnocchi,甚至是将要使用panko;计费策略和hashmap计费模型是其核心;模块插件化是其设计灵魂;接下来的三个部分内容将带您逐步探索。
第一部分:Cloudkitty架构详解本部分为理论架构篇,将会详细介绍当前Cloudkitty的整体架构,包括计费服务的对象获取(Tenant Fetcher)、计费数据源的收集(Collector)、计费引擎(Rating)的实现,计费费用数据的存储(Storage);另外将重点介绍hashmap计费模型。
一、计费服务对象Tenant FetcherTenant Fetcher是用来获取合法的计费服务对象,即Cloudkitty要知道需要对谁的资源进行计费。当前具体实现了从csv文件中获取和keystone中获取两种方式,分别对应FakeFetcher类和KeystoneFetcher类,均实现了父类的get_tenants抽象方法。所以只要继承基类BaseFetcher实现get_tenants方法,并配置tenant_fetcher后端,就能处理xls文件完成从Excel文档中获取需要计费的租户信息。
Keystone是默认的获取计费租户的方式,同时支持V2和V3版本。具体逻辑是检查Cloudkitty用户是否在某个tenant内并拥有rating角色。所以在使用Cloudkitty时,一般对于想要计费的租户需要执行命令’keystone user-role-add --user cloudkitty --role rating --tenant demo’,将Cloudkitty用户加入租户并赋予rating角色。
二、计费源数据收集Collector和统一化Transformer单纯从计费角度来看,计费源主要有两类,一类是静态资源,比如虚拟机实例、镜像、云硬盘、浮动IP;一类是动态资源,比如网络流量。对于静态资源一旦被创建,那么它的单价就固定,只需根据其生命周期按时间长短计费即可;对于动态资源需要统计计量情况进行计算费用。这就意味着计费的数据源应该包括event和measurement两部分,但当前Cloudkitty在计费数据收集方面没有加入事件分析能力。
这一步除了计费源数据的收集,还有一个十分重要的工作就是把这些数据转化为统一格式输入给计费引擎。Cloudkitty中的transformer模块将针对不同collector的不同类型的服务数据进行格式统一。这里需要特别说明的是transformer过程一般情况下分为两个阶段:
第一个阶段是将资源数据通过对应的CeilometerTransformer或者GnocchiTransformer进行转换。
第二阶段是使用CloudKittyFormatTransformer统一数据格式为data = [{'usage': {'service_type': [{'vol': {'unit': xx, 'qty': xxx}, 'desc': {'xxx': 'xxx',…, 'metadata': {‘xxx’: ’xxx’}}}]}, 'period': {'begin': xxxxx, 'end': xxxxx}}]交付给计费引擎。当然在不需要对资源数据进行转化的情况下,也可以直接使用CloudKittyFormatTransformer将数据转化为计费引擎所能识别的格式。
collector和transformer模块分别有多种实现,collector实现了ceilometer,fake,gnocchi和 meta四种方式,transformer实现了CeilometerTransformer,GnocchiTransformer以及通用的CloudKittyFormatTransformer三种方式。所以这两个模块分别提供了get_collector和get_transformers方法,根据后端配置动态选择对应的插件,具体是使用stevedore模块的driver方式和extension方式加载实现的。
每种collector的实现最终都会通过retrieve函数将归一化的数据发送给计费引擎。对于ceilometer和fake,这个retrieve函数会根据不同的服务调用get_compute,get_image,get_volume,get_network_bw_in,get_network_bw_out,get_network_floating,这六个方法就分别对应着cloudkitty的计费服务compute, image, volume, network.bw.in, network.bw.out, network.floating。而每个方法获得相关服务数据后会做transformer处理返回给计费引擎,最终计费引擎根据计费模型计算出具体费用再将费用数据存储起来便完成了整个计费流程。
对于gnocchi,它的retrieve函数则有所不同,gnocchi直接使用自己的metric.get_measures和resource.search方法获得相关资源数据。这恰恰也体现了cloudkitty collector模块的插件化,并且实现上非常灵活,意味着如果想为CloudStack,Eucalyptus,OpenNebula等IaaS平台添加collector插件也是件愉快而轻松的事。
三、计费引擎RatingOrchestrator类是Cloudkitty计费引擎的具体实现,支持多线程,支持多种计费模型同时运行,并能指定优先级。在计费策略上Cloudkitty采用的是周期轮询计费的方式,默认是每一小时对云环境中所需计费的资源进行费用核算,并将费用数据持久化存储起来。还有一个大周期是当月内,如果rated_data_frames表中的数据是空的时候,每次启动cloudkitty-processor就会重新计算当月内每小时的费用(这对调试Cloudkitty也很有帮助)。
Cloudkitty当前的计费模型有三个,分别是noop,hashmap和pyscripts。
1、noop模型为空,仅作为测试用;2、hashmap是当前Cloudkitty实用价值最高,最容易使用,最接近实际案例的计费模型,后文将单独做详细介绍;3、pyscripts计费模型提供了使用python代码定制计费的接口,用户可以直接将含有计费逻辑的python脚本上传给cloudkitty实现定制化的计费,所以pyscripts计费模型使用门槛较高。 计费模型的设计和实现相对比较复杂,首先计费模型要具有enabled和priority属性并能设置,借助stevedore作为插件注册到cloudkitty.rating.processors命名空间内;其次要实现抽象方法process执行具体的费用计算逻辑;最后还需要借助pecan提供对外使用的Rest API接口。但是hashmap计费模型目前能完成绝大部分实际场景需求的计费工作,后文将对它做详细介绍。关于大家关心的按秒计费,需要加入event分析后才比较容易实现,这块内容将在第三部分提到,也是我接下来的主要计划。
四、费用数据的存储Storage前面提到Cloudkitty的计费引擎是周期性计算资源费用的,而这个周期可以根据实际情况修改,只要能在一个计费周期内完成所有资源的费用计算即可。对应一项资源的服务默认在一个小时的计费周期下,一个月内就会有720条的费用数据生成,如果将计费周期调得更小,云环境中的需要计费的资源更多,那么将会对费用数据的存储带来挑战,它会直接影响到数据的使用效果,因此费用数据的存储也是一个重要的环节。 Cloudkitty的storage目前支持sqlalchemy和gnocchi_hybrid两种方式存放费用数据,因为它们都是通过插件方式实现的,所以具体采用哪种可以通过配置文件指定,再通过get_storage方法获得具体storage实例,十分灵活方便。 Storage的具体实现过程包括:(1)通过抽象方法get_time_frame从存储后端中的数据来确定下一个时间范围。(2)通过append方法将费用数据记入提交缓存,期间可能会通过get_tenants函数和_dispatch函数做预处理或加工。(3)通过commit函数将费用数据写入到后端存储持久化。这个过程会做_pre_commit,_commit,_post_commit一系列的工作确保数据被可靠地存储。(4)对外提供get_total方法,返回费用情况。 sqlalchemy和gnocchi_hybrid的数据格式或者说表的结构都包括begin, end, unit, qty, res_type, rate, tenant_id和对资源描述相关的字段。实际上两种后端存储都是基于SQL实现的,所以随着时间的推移和云环境中需要计费资源的增加,费用数据的增加同样会出现之前类似ceilometer用SQL存储measurement数据带来性能瓶颈的问题。所以将费用数据存储在gnocchi中是必须的,详见https://review.openstack.org/#/c/319425/。
五、hashmap计费模型Hashmap计费模型是Cloudkitty的核心,其数据结构如下图所示,接着是hashmap计费模型中的几个元素及核心概念。
-
Hashmap Group:一个Group表示一组计费规则,例如你可以创建多个计费规则,将它们分成两组来分别为instance和volume计费,避免计费规则混乱。
-
Hashmap Service:一个Service将计费规则映射到具体的数据collector,例如compute,volume,image,network.bw.in,network.bw.out,network.floating。
-
Hashmap Field:一个Field通常是指某个资源元数据metadata字典中的一个字段。例如对于instance,可以指定Field为flavor,availability_zone等;对于volume可以指定Field为volume_type,availability_zone和status等。既可以为Field指定mapping类型的计费规则也能指定Threshold类型的计费规则。
-
Hashmap Mapping: 一个Mapping是指某个最终的计费规则,mapping这类计费规则可以为某个服务指定价格,也可以为Field指定价格。例如你可以指定compute服务的基础价格Flat=10$,这会适用于所有的云主机;同时你也能具体指定flavor name,value=m1.tiny,Rate=1.2,则flavor为m1.tiny的云主机价格就为10$*1.2=12$,value=m1.medium,Flat=20$,则flavor为m1.medium的云主机价格就是20$;再例如指定volume的基础单价为2$,选定Field为volume_type,SATA类型打九五折value=sata,Rate=0.95,则sata盘的单价就是2$*0.95=1.9$;SAS为标准云硬盘不打折,则不用设定,单价依然为2$;SSD为高速硬盘应加价0.2倍value=ssd,Rate=1.2,则ssd盘的单价就是2$*1.2=2.4$。
-
Hashmap Threshold:Threshold是另外一种最终计费规则,threshold这类计费规则更适用于基于level的Rate价格。例如云硬盘基础单价为2$(可用mapping设定),如果用户购买超过50GB,可以打九折Level=50,Rate=0.9,此时单价为2$*0.9=1.8$;超过100GB打八折Level=100,Rate=0.8,此时单价为2$*0.8=1.6$。
通过以上介绍和具体举例,说明hashmap计费模型具有较广泛的使用范围,能满足实际需求中较复杂的计费场景。总结一下hashmap计费模型,它可以直接为资源或者服务设定价格(Flat或者Rate);也能根据资源的Field设定mapping和threshold类型的价格(Flat或者Rate);还能根据资源某字段的Level设定价格(Rate为主)。