nova分析(7)—— nova-scheduler

时间:2021-01-03 02:11:04

Nova-Scheduler主要完成虚拟机实例的调度分配任务,创建虚拟机时,虚拟机该调度到哪台物理机上,迁移时若没有指定主机,也需要经过scheduler。资源调度是云平台中的一个很关键问题,如何做到资源的有效分配,如何满足不同情况的分配方式,这些都需要nova-scheduler来掌控,并且能够很方便的扩展更多的调度方法,可能我需要虚拟机调度到空闲的机器,可能还需要将某类型的虚拟机调度到固定的机架等等,现在就来看看Nova-Scheduler是如何完成调度任务的。

Scheduler启动后,相应的MQ结构如下:

Exchange(nova,topic) <--- <routing_key:scheduler>---Queue(scheduler) ---> Consumer(scheduler)

Exchange(nova,topic) <---<routing_key:scheduler.controller>---Queue(scheduler.controller) ---> Consumer(scheduler.controller)

Exchange(scheduler_fanout,fanout) <---<routing_key:scheduler>---Queue(scheduler_fanout_xxx)--->Consumer(scheduler_fanout_xxx)

一、服务启动

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 两种实现方式。

二、主要源码说明

、 manager.py 

SchedulerManager 类主要来处理服务接收的消息,目前主要的操作就是live_migration 

run_instance 、 prep_resize 、 select_hosts 、 select_destinations ,后两者将要 deprecated 。这些操作本质上还是要依赖于具体的 SchedulerDriver 去完成。

另外, _SchedulerManagerV3Proxy 类与 SchedulerManager 类主要的区别在于消息版本的不同,

_SchedulerManagerV3Proxy 消息是 3.0 版本,而 SchedulerManager 是 2.9 版本。

、 driver.py 、 chance.py 、 filter_scheduler.py 、 caching_scheduler.py 

Scheduler 类是所有 SchedulerDriver 类的基类,其定义了关键的接口协议。目前主要有 ChanceScheduler 、 FilterScheduler 和 CachingScheduler 三种实现方式,具体说明如下:

) ChanceScheduler :随机选择一台物理机,前提是该物理机上的 nova-compute 服务正常且该物理机不在指定的 ignore_hosts 列表中。

) FilterScheduler :筛选出能通过整个过滤器链的物理机,然后根据相应指标计算权重,并进行排序,最后返回一个 best host 。具体过滤器详见下文。

) CachingScheduler : FilterScheduler 的子类,在 FilterScheduler 的基础上将 host 资源信息缓存在了本地内存中,然后通过后台定时任务定时从 DB 中拉取最新的 host 资源信息。在多节点环境下存在问题。

、 host_manager.py HostState :表示物理机的资源信息集合,比如当前可用内存、已用内存、虚拟机数量、任务数量、 io 负载、可用磁盘、已用磁盘等等,以及一些更新方法。

HostManager :该类是最重要的 host 筛选类,其内部主要有三个关键函数:

get_filtered_hosts :返回经过层层过滤器筛选后的 host 列表,其依赖于FilterHandler

get_weighed_hosts :返回经过权重计算排序后的 host 列表,其依赖于WeightHandler

get_all_host_states :获取当前最新的 host 资源列表。

、 filters 

这里是全部的各种 filter 的实现方式,主要有:

) __init__.py 

BaseHostFilter :继承自 BaseFilter ,是所有具体 Filter 子类的基类,其中一个较重要的成员变量 run_filter_once_per_request 表示该 Filter 子类是否在在一次 request 中仅执行一次。

HostFilterHandler :最重要的函数 get_filtered_objects , load 指定的filter_classes ,层层过滤

host ,最后返回符合所有过滤条件的 host 列表。

)剩余就是很多个不同的具体 Filter 子类的实现,基于各自不同的策略,具体暂不一一介绍。

、 weights 

) __init__.py BaseHostWeigher :继承自 BaseWeigher ,是所有具体 Weigher 子类的基类,其中主要的函数就是 _weigh_object ,由具体子类实现,完成对目标 host 列表权重的计算与排序,具体策略见下文。

HostWeightHandler :最重要的函数 get_weighed_objects , load 指定的weigher_classes ,层层依据各自指标对目标 host 列表进行权重计算,最后排序后返回。

) ram.py RAMWeigher :基于剩余可用内存进行权重计算排序。

) metrics.py MetricsWeigher :支持自定义一些指标进行权重计算排序。

三、流程与算法说明

、这里主要是基于 FilterScheduler 来说明下在 Scheduler 服务中创建虚拟机(包括筛选物理机)的流程(其他流程同理):

)当 nova-scheduler 服务从 MQ 中接收到 run_instance 消息时,则由SchedulerManager 类对其进行处理,执行 run_instance 函数。

) SchedulerManager 将具体逻辑交由具体的 SchedulerDriver 执行,这里就是 FilterScheduler 

) FilterScheduler 接收到请求后,开始进行 host 筛选,选择出 instance_num个目标 host 

)首先判断这次 request 的 retry 次数是否已经达到 max_attempts 次,若达到,则停止 retry ,抛出 NoValidHost 异常;若是第一次,则设置 filter_properties 属性 retry={num_attempts:1,hosts:[]} ;否则继续执行。

)然后从 DB 中获取当前最新的 compute node 节点资源信息,并更新本地内存 缓存 中的 host 表资源数据。这里的好处是当一次批量申请多个虚拟机时,可以避免多次请求DB ,但是一个不容忽视的问题就是如果多个 Scheduler 服务并行部署时,就会因资源信息的延迟不同步而导致虚拟机创建失败(实际后台 host 已不足)。

)再次开始循环为每个 instance 筛选目标 host :首先根据指定的ignore_hosts 、 force_hosts force_nodes 属性对候选 hosts 列表进行筛选,留下符合这些属性条件的 hosts

)然后根据配置指定的 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

)在 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)

)从 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 )步骤。

、下图展示了整个调度过程的大体流程:

nova分析(7)—— nova-scheduler

这里主要说明下各个 host 的权重是如何计算出来的,大体公式如下: 

nova分析(7)—— nova-scheduler

host_weight = Weigher1_multiplier * Weigher1_host_weight+……+ WeigherN_multiplier

* WeigherN_host_weight

举例说明:

假若有 6 台候选 Host : H1 , H2 , H3 , H4 , H5 , H6

个 Weigher : W1 , W2 , W3 ,且其 multiplier 分别为 1 , 2 , 1

具体一个 Weigher 如何计算出一个 Host 在该 Weigher 的权重,则依赖各个Weigher 自己的

实现,比如 RAMWeigher 则是以每个 Host  free_ram_mb 作为其原生权重。

)假若 H1~H6 经过 W1 计算后的原生权重值如下:

W(multiplier)

H1

H2

H3

H4

H5

H6

W1 (1)

50

10

20

10

90

110

然后需要对这些原始权重值进行 normalize 化,具体方式是:找出这些原始权重值中的

最大值 max_weight 和最小值 min_weight ,然后按照如下公式来计算其比例权重值:

ratio_weight = ( raw_weight - min_weight ) / ( max_weight - min_weight )

那么 H1~H6 原生权重 W1 normalize 化之后 (ratio_weight) 

W(multiplier)

H1

H2

H3

H4

H5

H6

W1 (1)

0.4

0

0.1

0

0.8

1

)假若 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

那么 H1~H6 原生权重 W2 normalize 化之后 (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

)假若 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 原生权重 W3 normalize 化之后 (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

)最后得出 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

)最后对 H1~H6 的 ratio_weight 进行降序排序,那么这次 Host 的排序则为H1 , H6 , H4 , H2 , H3 , H5

、 nova-scheduler 与 nova-compute 之间是如何知道具体 host 资源的详情的?

)每当 nova-compute 服务起来之后,会自动更新 DB 中对应 compute_node的资源信息,若是第一次,则会将自己添加到 compute_node 中;并同时会将这些资源信息缓存在本地内存中,并由一个 ResourceTracker 实例进行跟踪。

)每次 nova-compute 服务接收到创建虚拟机的申请时,通过ResourceTracker 进行instance_claim 时,会将这次申请所需要扣除的虚拟机资源大小及时反映到compute node 中,同时更新本地 ResourceTracker 中的缓存信息。

) nova-scheduler 每次在接收到创建虚拟机请求时,都会从 compute node中取出最新的 host 源信息,被暂时缓存在本地内存中,当成功选择出一台 host 时,会及时扣除本地缓存相应的 host 资源信息。

 

参考文档

http://www.choudan.net/2013/08/11/Nova-Scheduler分析.html

http://www.choudan.net/2013/08/09/Nova-Service启动.html

http://blog.csdn.net/gaoxingnengjisuan/article/details/15615743

http://blog.csdn.net/cloudresearch/article/details/19051043

http://blog.csdn.net/gaoxingnengjisuan/article/details/15734437

http://www.tuicool.com/articles/Vn2iia

http://docs.openstack.org/developer/nova/devref/filter_scheduler.html