在上一篇“
nova-scheduler调度过程分析”中,对过滤称重的过程一笔带过了,这篇着重介绍一下。
首先,我们声明一下host为主机,node为节点,在OpenStack中一个host可以运行多个node或者nova-compute服务。
第一步,过滤filter,通过HostManager的get_filtered_hosts实现:
选取Icehouse版本支持的部分主机过滤器进行简要说明:
RetryFilter -> 如果主机已经尝试被调度了,那么过滤不通过
AvailabilityZoneFilter -> 我们可以根据物理位置对主机划分“可用域”, 如果创建虚拟机实例时指定了availability_zone,而主机又不在
该availability_zone中,那么过滤不通过
RamFilter -> 如果主机的可用内存不足以提供虚拟机实例需要的内存,那么过滤不通过
ComputeFilter -> 如果主机的计算服务被禁用掉或者down掉(一定时间内没有收到心跳包), 那么过滤不通过
ComputeCapabilitiesFilter -> 创建虚拟机实例时可以指定一些额外的要求,如果主机的能力不满足这些要求,那么过滤不通过
ImagePropertiesFilter -> 我们可以给镜像创建属性,譬如在镜像属性指定架构、hypervisor和虚拟机模式,如果主机不支持这些属性的虚拟机,那么过滤不通过
ServerGroupAntiAffinityFilter、ServerGroupAffinityFilter ->给定一个实例组,那么可以获取运行实例成员的主机集合,如果主机
在/不在这个主机集合里,那么过滤不通过
# nova-scheduler默认的过滤器列表
cfg.ListOpt('scheduler_default_filters',
default=[
'RetryFilter',
'AvailabilityZoneFilter',
'RamFilter',
'ComputeFilter',
'ComputeCapabilitiesFilter',
'ImagePropertiesFilter',
'ServerGroupAntiAffinityFilter',
'ServerGroupAffinityFilter',
],
help='Which filter class names to use for filtering hosts '
'when not specified in the request.'),
# 过滤器基类
class BaseFilter(object):
def _filter_one(self, obj, filter_properties):
return True
def filter_all(self, filter_obj_list, filter_properties):
for obj in filter_obj_list:
if self._filter_one(obj, filter_properties):
yield obj
run_filter_once_per_request = False
# 我们可以一次创建多个同类型的实例, 每个实例我们都需要对主机进行一次过滤称重操作,
# 有些过滤器类只需要在针对第一个实例的过滤过程中运行一次即可, 之后的实例就不再运行
def run_filter_for_index(self, index):
if self.run_filter_once_per_request and index > 0:
return False
else:
return True
# 主机过滤器基类
class BaseHostFilter(filters.BaseFilter):
def _filter_one(self, obj, filter_properties):
return self.host_passes(obj, filter_properties)
def host_passes(self, host_state, filter_properties):
raise NotImplementedError()
# 举例: 磁盘过滤器
class DiskFilter(filters.BaseHostFilter):
def host_passes(self, host_state, filter_properties):
# 求出创建虚拟机实例所需的磁盘大小
instance_type = filter_properties.get('instance_type')
requested_disk = (1024 * (instance_type['root_gb'] +
instance_type['ephemeral_gb']) +
instance_type['swap'])
# 主机的当前可用磁盘大小
free_disk_mb = host_state.free_disk_mb
# 主机的总物理磁盘大小
total_usable_disk_mb = host_state.total_usable_disk_gb * 1024
# disk_allocation_ratio是总虚拟机实例磁盘/物理磁盘的比率, 默认是1.0;
# 我们创建一个硬盘大小为60GB的虚拟机实例, 它并不会立刻占用60GB的物理磁盘;
# 因此, 我们可能在1000GB的物理硬盘上, 创建30个硬盘大小为60GB的虚拟机实例,
# 这完全是可以的, 只是存在磁盘爆满的情况; 所以这个disk_allocation_ratio我们
# 可以根据具体应用场景酌情设置高一些
disk_mb_limit = total_usable_disk_mb * CONF.disk_allocation_ratio
# 当前已使用硬盘大小
used_disk_mb = total_usable_disk_mb - free_disk_mb
# 可用虚拟机实例磁盘大小
usable_disk_mb = disk_mb_limit - used_disk_mb
# 当可用虚拟机实例磁盘<创建虚拟机实例所需的磁盘大小时, 我们认为不能让实例落在该主机上
if not usable_disk_mb >= requested_disk:
LOG.debug(_("%(host_state)s does not have %(requested_disk)s MB "
"usable disk, it only has %(usable_disk_mb)s MB usable "
"disk."), {'host_state': host_state,
'requested_disk': requested_disk,
'usable_disk_mb': usable_disk_mb})
return False
# 更新主机的状态信息
disk_gb_limit = disk_mb_limit / 1024
host_state.limits['disk_gb'] = disk_gb_limit
return True
class BaseFilterHandler(loadables.BaseLoader):
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()
# 判断该过滤器在当前index下是否需要运行
if filter.run_filter_for_index(index):
# 对每个list_obj进行_filter_one过滤, _filter_one实际会调用host_passes进行真正的过滤操作
# 因此, 每个过滤器类只需要继承BaseHostFilter类并实现host_passes方法即可,
# 这里会返回一个生成器对象
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:
# 如果在该过滤器运行之后,没有list_obj通过过滤,
# 那么就不需要运行剩余的过滤器了
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)})
# 返回最终通过所有过滤的list_obj
return list_objs
class HostManager(object):
# 返回filter_cls_names指定的过滤器列表
def _choose_host_filters(self, filter_cls_names):
if filter_cls_names is None:
# 如果filter_cls_names未空,那么就返回配置选项scheduler_default_filters中指定的过滤器列表
filter_cls_names = CONF.scheduler_default_filters
if not isinstance(filter_cls_names, (list, tuple)):
filter_cls_names = [filter_cls_names]
# 返回所有可用的过滤器名称和对应的过滤器类
# 过滤器采用了插件模式,我们可以自定义过滤器,只需要将文件放入nova/scheduler/filters下面,
# 那么就可以通过配置选项scheduler_default_filters来使用其中的过滤器
# 每当nova-scheduler开始运行, 它就会自动在该目录下加载过滤器
cls_map = dict((cls.__name__, cls) for cls in self.filter_classes)
# 我们可能会因为手误配置了不存在的调度器名, 那么下面就是对要使用的过滤器进行验证,
# 以杜绝这种错误的发生
good_filters = []
bad_filters = []
for filter_name in filter_cls_names:
if filter_name not in cls_map:
bad_filters.append(filter_name)
continue
good_filters.append(cls_map[filter_name])
if bad_filters:
msg = ", ".join(bad_filters)
# 如果要使用的调度器不存在,就抛出异常
raise exception.SchedulerHostFilterNotFound(filter_name=msg)
# 返回要使用的过滤器类
return good_filters
# 对参数hosts中的所有主机进行过滤,返回通过所有过滤条件的主机
def get_filtered_hosts(self, hosts, filter_properties,
filter_class_names=None, index=0):
# 从host_map中去除hostname在hosts_to_ignore中的主机
def _strip_ignore_hosts(host_map, hosts_to_ignore):
ignored_hosts = []
for host in hosts_to_ignore:
for (hostname, nodename) in host_map.keys():
if host == hostname:
del host_map[(hostname, nodename)]
ignored_hosts.append(host)
ignored_hosts_str = ', '.join(ignored_hosts)
msg = _('Host filter ignoring hosts: %s')
LOG.audit(msg % ignored_hosts_str)
# 从host_map中去除hostname不在hosts_to_force中的主机
def _match_forced_hosts(host_map, hosts_to_force):
forced_hosts = []
for (hostname, nodename) in host_map.keys():
if hostname not in hosts_to_force:
del host_map[(hostname, nodename)]
else:
forced_hosts.append(hostname)
if host_map:
forced_hosts_str = ', '.join(forced_hosts)
msg = _('Host filter forcing available hosts to %s')
else:
forced_hosts_str = ', '.join(hosts_to_force)
msg = _("No hosts matched due to not matching "
"'force_hosts' value of '%s'")
LOG.audit(msg % forced_hosts_str)
# 从host_map中去除nodenames不在nodes_to_force中的主机
def _match_forced_nodes(host_map, nodes_to_force):
forced_nodes = []
for (hostname, nodename) in host_map.keys():
if nodename not in nodes_to_force:
del host_map[(hostname, nodename)]
else:
forced_nodes.append(nodename)
if host_map:
forced_nodes_str = ', '.join(forced_nodes)
msg = _('Host filter forcing available nodes to %s')
else:
forced_nodes_str = ', '.join(nodes_to_force)
msg = _("No nodes matched due to not matching "
"'force_nodes' value of '%s'")
LOG.audit(msg % forced_nodes_str)
# 获取filter_class_names指定的过滤器类列表
filter_classes = self._choose_host_filters(filter_class_names)
ignore_hosts = filter_properties.get('ignore_hosts', [])
force_hosts = filter_properties.get('force_hosts', [])
force_nodes = filter_properties.get('force_nodes', [])
if ignore_hosts or force_hosts or force_nodes:
# 我们不能假设host是唯一的,因为一个host可能运行多个nodes
name_to_cls_map = dict([((x.host, x.nodename), x) for x in hosts])
if ignore_hosts:
# 从name_to_cls_map中去除hostname在ignore_hosts中的host
_strip_ignore_hosts(name_to_cls_map, ignore_hosts)
if not name_to_cls_map:
return []
if force_hosts:
# 从name_to_cls_map中去除hostname不在force_hosts中的host
_match_forced_hosts(name_to_cls_map, force_hosts)
if force_nodes:
# # 从name_to_cls_map中去除nodename不在force_nodes中的host
_match_forced_nodes(name_to_cls_map, force_nodes)
if force_hosts or force_nodes:
# 当我们强制了虚拟机要落在哪些host或node上, 就可以跳过过滤过程
if name_to_cls_map:
return name_to_cls_map.values()
hosts = name_to_cls_map.itervalues()
# 对hosts使用filter_classes中的过滤器进行过滤, 返回通过过滤的主机列表
return self.filter_handler.get_filtered_objects(filter_classes,
hosts, filter_properties, index)
第二步,称重weigh,通过HostManager的get_weighed_hosts实现, 为什么要称重呢?我们可能有多个主机通过过滤filter,那我们怎么决定实例落在哪个主机上了,这就是称重的作用,帮助我们对这些主机进行排序, 以选取出相对最优的主机。
Icehouse版本支持的称重器就以下2种:
RAMWeigher -> 内存称重器, 可用内存越多,重量越重
MetricsWeigher -> 指标称重器, 可以通过配置的形式指定主机的多种指标和比率, 将这些指标结合起来进行称重
我曾经因为需要, 自己写过VCPU称重器,方法很简单,但是对于权重的选择要把握好
# 称重目标类
class WeighedObject(object):
def __init__(self, obj, weight):
self.obj = obj
self.weight = weight
def __repr__(self):
return "<WeighedObject '%s': %s>" % (self.obj, self.weight)
# 称重器抽象基类
@six.add_metaclass(abc.ABCMeta)
class BaseWeigher(object):
minval = None
maxval = None
def weight_multiplier(self):
return 1.0
@abc.abstractmethod
def _weigh_object(self, obj, weight_properties):
def weigh_objects(self, weighed_obj_list, weight_properties):
weights = []
for obj in weighed_obj_list:
# 对obj.obj进行称重
weight = self._weigh_object(obj.obj, weight_properties)
# 记录和更新重量的最大最小值, 方便进行归一化
if self.minval is None:
self.minval = weight
if self.maxval is None:
self.maxval = weight
if weight < self.minval:
self.minval = weight
elif weight > self.maxval:
self.maxval = weight
weights.append(weight)
return weights
# 举例: 内存称重器
# 每个称重器只要实现_weigh_object即可, weight_multiplier可根据情况进行覆盖
class RAMWeigher(weights.BaseHostWeigher):
minval = 0
def weight_multiplier(self):
# 返回配置的内存称重权重, 默认为1.0
return CONF.ram_weight_multiplier
def _weigh_object(self, host_state, weight_properties):
# 返回主机的可用内存大小, 内存越大, 优先级越高;
# 如果我们希望越小优先级越高, 可通过乘以-1实现
return host_state.free_ram_mb
# 称重处理器基类
class BaseWeightHandler(loadables.BaseLoader):
object_class = WeighedObject
def get_weighed_objects(self, weigher_classes, obj_list,
weighing_properties):
if not obj_list:
return []
# 将要称重的object封装进WeighedObject里, weight为0.0
weighed_objs = [self.object_class(obj, 0.0) for obj in obj_list]
for weigher_cls in weigher_classes:
# 实例化称重器
weigher = weigher_cls()
# 对weighed_objs逐个进行称重, 并返回weights列表
weights = weigher.weigh_objects(weighed_objs, weighing_properties)
# 对weights进行归一化, 让每个weight处于[0.0,1.0]之间
# 为什么要这样做呢?我们不能让称重元素的重量过多的影响称重结果;
# 假设我们要对主机的内存、硬盘进行称重, 一般主机的内存会比硬盘小很多, 譬如内存为128GB, 硬盘为2048GB;
# 如果我们不进行归一化, 由于硬盘和内存的大小相差较大, 导致内存的称重对称重结果影响微乎其微, 甚至毫无作用;
# 我们可以通过归一化抹平不同称重元素之间的差异性, 而通过权重来指定它们的重要性
weights = normalize(weights,
minval=weigher.minval,
maxval=weigher.maxval)
for i, weight in enumerate(weights):
obj = weighed_objs[i]
# 更新obj的重量
# weight_multiplier方法返回的是该称重器的权重
# 我们可以根据实际需要, 为不同称重器指定不同的权重, 一般来说相对重要的称重器权重更高
obj.weight += weigher.weight_multiplier() * weight
# 对称重目标列表按照重量的大小进行降序排列
# 在OpenStack中重量越大, 意味着主机的优先级越高
return sorted(weighed_objs, key=lambda x: x.weight, reverse=True)
# 主机称重处理器
class HostWeightHandler(weights.BaseWeightHandler):
object_class = WeighedHost
def __init__(self):
super(HostWeightHandler, self).__init__(BaseHostWeigher)
class HostManager(object):
def __init__(self):
...
# 创建主机称重处理器实例
self.weight_handler = weights.HostWeightHandler()
# 同过滤器一样, 称重器也是采用了插件模式, 我们可以自定义称重器,只需要将文件放入nova/scheduler/weights下面;
# 这里用于获取和加载该目录下的称重器类
self.weight_classes = self.weight_handler.get_matching_classes(
CONF.scheduler_weight_classes)
def get_weighed_hosts(self, hosts, weight_properties):
# 这里调用的是BaseWeightHandler中的get_weighed_objects方法;
# 对hosts使用weight_classes中的称重器进行称重, 返回称重后的有序主机列表
return self.weight_handler.get_weighed_objects(self.weight_classes,
hosts, weight_properties)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)