首页 > 代码库 > OpenStack源码分析——Nova-Scheduler
OpenStack源码分析——Nova-Scheduler
一、服务启动
Nova-scheduler服务的启动入口脚本是cmd包下的scheduler.py,其主要监听来自于消息队列中topic=scheduler(可配置)的消息。在服务启动过程中,其将初始化一个SchedulerManager实例作为该服务的Handler,来处理接受到的消息请求。
同时,Nova-scheduler服务在启动的过程中,会将自己注册到DB中,即将自己的host、binary、topic、report_count信息添加到services表中,并将自己同样注册到ServiceGroup服务中,默认是DBServiceGroup服务,并定时向ServiceGroup服务发送心跳,其本质上就是定时地更新services表中的report_count字段。另,ServiceGroup服务还有基于MC、ZK两种实现方式。
二、主要源码说明
1、manager.py:
SchedulerManager类主要来处理服务接收的消息,目前主要的操作就是live_migration、
run_instance、prep_resize、select_hosts、select_destinations,后两者将要deprecated。这些操作本质上还是要依赖于具体的SchedulerDriver去完成。
另外,_SchedulerManagerV3Proxy类与SchedulerManager类主要的区别在于消息版本的不同,
_SchedulerManagerV3Proxy消息是3.0版本,而SchedulerManager是2.9版本。
2、driver.py、chance.py、filter_scheduler.py、caching_scheduler.py:
Scheduler类是所有SchedulerDriver类的基类,其定义了关键的接口协议。目前主要有ChanceScheduler、FilterScheduler和CachingScheduler三种实现方式,具体说明如下:
1)ChanceScheduler:随机选择一台物理机,前提是该物理机上的nova-compute服务正常且该
物理机不在指定的ignore_hosts列表中。
2)FilterScheduler:筛选出能通过整个过滤器链的物理机,然后根据相应指标计算权重,并进
行排序,最后返回一个besthost。具体过滤器详见下文。
3)CachingScheduler:FilterScheduler的子类,在FilterScheduler的基础上将host资源信息缓
存在了本地内存中,然后通过后台定时任务定时从DB中拉取最新的host资源信息。在多节
点环境下存在问题。
3、host_manager.py:
HostState:表示物理机的资源信息集合,比如当前可用内存、已用内存、虚拟机数量、任务
数量、io负载、可用磁盘、已用磁盘等等,以及一些更新方法。
HostManager:该类是最重要的host筛选类,其内部主要有三个关键函数:
get_filtered_hosts:返回经过层层过滤器筛选后的host列表,其依赖于FilterHandler
get_weighed_hosts:返回经过权重计算排序后的host列表,其依赖于WeightHandler
get_all_host_states:获取当前最新的host资源列表。
4、filters:
这里是全部的各种filter的实现方式,主要有:
1)__init__.py:
BaseHostFilter:继承自BaseFilter,是所有具体Filter子类的基类,其中一个较重要的成
员变量run_filter_once_per_request表示该Filter子类是否在在一次request中仅执行一次。
HostFilterHandler:最重要的函数get_filtered_objects,load指定的filter_classes,层层过滤
host,最后返回符合所有过滤条件的host列表。
2)剩余就是很多个不同的具体Filter子类的实现,基于各自不同的策略,具体暂不一一介
绍。
5、weights:
1)__init__.py:
BaseHostWeigher:继承自BaseWeigher,是所有具体Weigher子类的基类,其中主要的函
数就是_weigh_object,由具体子类实现,完成对目标host列表权重的计算与排序,具体策
略见下文。
HostWeightHandler:最重要的函数get_weighed_objects,load指定的weigher_classes,层层
依据各自指标对目标host列表进行权重计算,最后排序后返回。
2)ram.py:
RAMWeigher:基于剩余可用内存进行权重计算排序。
3)metrics.py:
MetricsWeigher:支持自定义一些指标进行权重计算排序。
三、流程与算法说明
1、这里主要是基于FilterScheduler来说明下在Scheduler服务中创建虚拟机(包括筛选物理机)的流程(其他流程同理):
1)当nova-scheduler服务从MQ中接收到run_instance消息时,则由SchedulerManager类对其
进行处理,执行run_instance函数。
2)SchedulerManager将具体逻辑交由具体的SchedulerDriver执行,这里就是FilterScheduler。
3)FilterScheduler接收到请求后,开始进行host筛选,选择出instance_num个目标host。
4)首先判断这次request的retry次数是否已经达到max_attempts次,若达到,则停止retry,抛
出NoValidHost异常;若是第一次,则设置filter_properties属性retry={num_attempts:1,hosts:
[]};否则继续执行。
5)然后从DB中获取当前最新的computenode节点资源信息,并更新本地内存缓存中的host列
表资源数据。这里的好处是当一次批量申请多个虚拟机时,可以避免多次请求DB,但是
一个不容忽视的问题就是如果多个Scheduler服务并行部署时,就会因资源信息的延迟不同
步而导致虚拟机创建失败(实际后台host已不足)。
6)再次开始循环为每个instance筛选目标host:首先根据指定的ignore_hosts、force_hosts、
force_nodes属性对候选hosts列表进行筛选,留下符合这些属性条件的hosts。
7)然后根据配置指定的filter来循环层层过滤6)中的候选hosts,具体代码如下:
def get_filtered_objects(self, filter_classes, objs,filter_properties, index=0): list_objs = list(objs) LOG.debug(_("Starting with %d host(s)"), len(list_objs)) for filter_cls in filter_classes: cls_name = filter_cls.__name__ filter = filter_cls() if filter.run_filter_for_index(index): objs = filter.filter_all(list_objs,filter_properties) if objs is None: LOG.debug(_("Filter %(cls_name)s says to stop filtering"), {‘cls_name‘: cls_name}) return list_objs = list(objs) if not list_objs: LOG.info(_("Filter %s returned 0 hosts"), cls_name) break LOG.debug(_("Filter %(cls_name)s returned %(obj_len)d host(s)"), {‘cls_name‘: cls_name, ‘obj_len‘: len(list_objs)}) return list_objs
8)在7)中筛选的这批hosts此时可以认为是满足该instance的资源要求,然后开始对这批
hosts根据配置的各个Weigher子类进行权重计算并排序,具体如下:
def get_weighed_objects(self, weigher_classes, obj_list,weighing_properties): """Return a sorted (descending), normalized list of WeighedObjects.""" if not obj_list: return [] weighed_objs = [self.object_class(obj, 0.0) for obj in obj_list] for weigher_cls in weigher_classes: weigher = weigher_cls() weights = weigher.weigh_objects(weighed_objs, weighing_properties) # Normalize the weights weights = normalize(weights,minval=weigher.minval,maxval=weigher.maxval) for i, weight in enumerate(weights): obj = weighed_objs[i] obj.weight += weigher.weight_multiplier() * weight return sorted(weighed_objs, key=lambda x: x.weight, reverse=True)
9)从8)中根据权重排序后的hosts取出scheduler_host_subset_size(默认是1)个host,表明
未来该instance会落在该host上,并预扣掉该host的剩余可用资源(注意这里仅仅是修改
的本地缓存中host资源信息)。
10)在为每个instance选择好候选目标host后,开始循环创建虚拟机:更新instance表
(host,node,scheduled_at),并向nova-compute服务发送rpc请求。
11)nova-compute服务接收到创建虚拟机请求后:首先将DB中instance状态设置为
vm_state=BUILDING,task_state=SCHEDULING。
12)然后获取该node的ResourceTracker实例(其主要是维护跟踪该node的资源明细),
claim将要预留资源信息,设置mac和network信息,最后开始构建虚拟机。期间在不同
的task阶段,会相应更新DB中vm_state和task_state状态。
13)若创建虚拟机流程中发生错误,会重新向MQ发送创建虚拟机请求,此时nova-scheduler
服务会继续处理该消息,则继续回到1)步骤。
2、下图展示了整个调度过程的大体流程:
这里主要说明下各个host的权重是如何计算出来的,大体公式如下:
host_weight = Weigher1_multiplier * Weigher1_host_weight+……+ WeigherN_multiplier
* WeigherN_host_weight
举例说明:
假若有6台候选Host:H1,H2,H3,H4,H5,H6
3个Weigher:W1,W2,W3,且其multiplier分别为1,2,1
具体一个Weigher如何计算出一个Host在该Weigher的权重,则依赖各个Weigher自己的
实现,比如RAMWeigher则是以每个Host的free_ram_mb作为其原生权重。
1)假若H1~H6经过W1计算后的原生权重值如下:
W(multiplier) | H1 | H2 | H3 | H4 | H5 | H6 |
W1 (1) | 50 | 10 | 20 | 10 | 90 | 110 |
W2 (2) |
|
|
|
|
|
|
W3 (1) |
|
|
|
|
|
|
然后需要对这些原始权重值进行normalize化,具体方式是:找出这些原始权重值中的
最大值max_weight和最小值min_weight,然后按照如下公式来计算其比例权重值:
ratio_weight = ( raw_weight - min_weight ) / ( max_weight -min_weight )
那么H1~H6原生权重W1normalize化之后(ratio_weight):
W(multiplier) | H1 | H2 | H3 | H4 | H5 | H6 |
W1 (1) | 0.4 | 0 | 0.1 | 0 | 0.8 | 1 |
W2 (2) |
|
|
|
|
|
|
W3 (1) |
|
|
|
|
|
|
2)假若H1~H6经过W2计算后的原生权重值如下:
W(multiplier) | H1 | H2 | H3 | H4 | H5 | H6 |
W1 (1) | 0.4 | 0 | 0.1 | 0 | 0.8 | 1 |
W2 (2) | 10 | 4 | 6 | 11 | 1 | 9 |
W3 (1) |
|
|
|
|
|
|
那么H1~H6原生权重W2normalize化之后(ratio_weight):
W(multiplier) | H1 | H2 | H3 | H4 | H5 | H6 |
W1 (1) | 0.4 | 0 | 0.1 | 0 | 0.8 | 1 |
W2 (2) | 0.9 | 0.3 | 0.5 | 1 | 0 | 0.8 |
W3 (1) |
|
|
|
|
|
|
3)假若H1~H6经过W3计算后的原生权重值如下:
W(multiplier) | H1 | H2 | H3 | H4 | H5 | H6 |
W1 (1) | 0.4 | 0 | 0.1 | 0 | 0.8 | 1 |
W2 (2) | 0.9 | 0.3 | 0.5 | 1 | 0 | 0.8 |
W3 (1) | 15 | 25 | 10 | 5 | 10 | 5 |
那么H1~H6原生权重W3normalize化之后(ratio_weight):
W(multiplier) | H1 | H2 | H3 | H4 | H5 | H6 |
W1 (1) | 0.4 | 0 | 0.1 | 0 | 0.8 | 1 |
W2 (2) | 0.9 | 0.3 | 0.5 | 1 | 0 | 0.8 |
W3 (1) | 0.5 | 1 | 0.25 | 0 | 0.25 | 0 |
4)最后得出H1~H6的最终权重为:
W(multiplier) | H1 | H2 | H3 | H4 | H5 | H6 |
W1 (1) | 0.4 | 0 | 0.1 | 0 | 0.8 | 1 |
W2 (2) | 0.9 | 0.3 | 0.5 | 1 | 0 | 0.8 |
W3 (1) | 0.5 | 1 | 0.25 | 0 | 0.25 | 0 |
ratio_weights | 2.7 | 1.6 | 1.35 | 2 | 1.05 | 2.6 |
5)最后对H1~H6的ratio_weight进行降序排序,那么这次Host的排序则为H1,H6,H4,H2,H3,H5
3、nova-scheduler与nova-compute之间是如何知道具体host资源的详情的?
1)每当nova-compute服务起来之后,会自动更新DB中对应compute_node的资源信息,若是
第一次,则会将自己添加到compute_node中;并同时会将这些资源信息缓存在本地内存中,
并由一个ResourceTracker实例进行跟踪。
2)每次nova-compute服务接收到创建虚拟机的申请时,通过ResourceTracker进行
instance_claim时,会将这次申请所需要扣除的虚拟机资源大小及时反映到computenode中,
同时更新本地ResourceTracker中的缓存信息。
3)nova-scheduler每次在接收到创建虚拟机请求时,都会从computenode中取出最新的host资
源信息,被暂时缓存在本地内存中,当成功选择出一台host时,会及时扣除本地缓存相应
的host资源信息。