OpenStack Nova/nova-scheduler · 主机过滤、加权与资源申领

筛选计算主机,按权重选择并申请资源

Nova 把必须满足的主机条件与偏好评分分开:过滤候选、归一化各项权重并排序,再尝试向 Placement 申领资源,失败则继续其他候选。

31.0.0调度与资源分配核验 2026-10-12
相关概念

工程问题#

创建虚拟机既要满足资源与放置约束,也要在多个可用主机间选择。不同评分量纲不能直接相加,而查询到资源可用只是一个候选视图:并发调度器可能在实际分配前先占用同一资源。

实际实现#

调度器取得 Placement 候选后,由 HostManager 执行已配置的过滤器,再由 weighers 计算各项评分。BaseWeightHandler 将同项原始评分归一化,乘以该项在相应主机上的倍率,累加并按总分降序排列。调度器可打散最高同分组,并从配置允许的前若干名中随机选一台移到首位,以减少相似请求集中选择同一主机。随后按候选顺序申领资源,只有申领成功才记录该主机;批量创建还会消费本地 HostState,影响下一实例的过滤和评分。

处理流程#

  1. 从 Placement 候选建立 HostState,并应用硬约束过滤器
  2. 逐项计算评分,在候选间归一化后乘以配置倍率并累加
  3. 按总分排序,根据配置打散同分组或随机选择前部候选
  4. 按顺序向 Placement 申领资源;某个候选申领失败就尝试下一候选
  5. 成功后更新本地已消费资源,再为批量请求的下一实例重新评估

源码片段#

固定版本 · 原文摘录
nova/scheduler/manager.py31.0.0 · L712–L726
查看原文件
712    def _get_sorted_hosts(self, spec_obj, host_states, index):713        """Returns a list of HostState objects that match the required714        scheduling constraints for the request spec object and have been sorted715        according to the weighers.716        """717        filtered_hosts = self.host_manager.get_filtered_hosts(host_states,718            spec_obj, index)719720        LOG.debug("Filtered %(hosts)s", {'hosts': filtered_hosts})721722        if not filtered_hosts:723            return []724725        weighed_hosts = self.host_manager.get_weighed_hosts(726            filtered_hosts, spec_obj)
_get_sorted_hosts · 过滤后评分

先排除不满足条件的主机,再对可行集合评分;没有可行主机直接返回空列表。

nova/weights.py31.0.0 · L43–L56
查看原文件
43    if maxval is None:44        maxval = max(weight_list)4546    if minval is None:47        minval = min(weight_list)4849    maxval = float(maxval)50    minval = float(minval)5152    if minval == maxval:53        return [0] * len(weight_list)5455    range_ = maxval - minval56    return ((i - minval) / range_ for i in weight_list)
normalize · 统一评分尺度

默认使用本项评分的最大最小值,weigher 也可指定边界;归一化上下界相同时返回零向量。RAMWeigher 指定 minval 为 0,不能笼统地认为候选原始分数相同就一定归零。

nova/weights.py31.0.0 · L155–L172
查看原文件
155            log_data = {}156157            for i, weight in enumerate(weights):158                obj = weighed_objs[i]159                multiplier = weigher.weight_multiplier(obj.obj)160                weigher_score = multiplier * weight161                obj.weight += weigher_score162163                log_data[(obj.obj.host, obj.obj.nodename)] = (164                    f"{multiplier} * {weight}")165166            LOG.debug(167                "%s: score (multiplier * weight) %s",168                weigher.__class__.__name__,169                {name: log for name, log in log_data.items()}170            )171172        return sorted(weighed_objs, key=lambda x: x.weight, reverse=True)
get_weighed_objects · 加权累加与排序

每个已归一化分数乘以该主机的倍率后累加,最终降序排列;倍率可以因 host_state 不同而不同。

nova/scheduler/manager.py31.0.0 · L746–L757
查看原文件
746        # We randomize the first element in the returned list to alleviate747        # congestion where the same host is consistently selected among748        # numerous potential hosts for similar request specs.749        host_subset_size = CONF.filter_scheduler.host_subset_size750        if host_subset_size < len(weighed_hosts):751            weighed_subset = weighed_hosts[0:host_subset_size]752        else:753            weighed_subset = weighed_hosts754755        chosen_host = random.choice(weighed_subset)756        weighed_hosts.remove(chosen_host)757        return [chosen_host] + weighed_hosts
_get_sorted_hosts · 首选主机随机化

只从配置给定的前部候选中随机选一个移到首位,剩余候选仍保留供后续申领尝试;不是把全体主机完全随机排序。

nova/scheduler/manager.py31.0.0 · L455–L476
查看原文件
455                # TODO(jaypipes): Loop through all allocation_requests instead456                # of just trying the first one. For now, since we'll likely457                # want to order the allocation_requests in the future based on458                # information in the provider summaries, we'll just try to459                # claim resources using the first allocation_request460                alloc_req = host.allocation_candidates[0]461                if utils.claim_resources(462                    elevated, self.placement_client, spec_obj, instance_uuid,463                    alloc_req,464                    allocation_request_version=allocation_request_version,465                ):466                    claimed_host = host467                    break468469            if claimed_host is None:470                # We weren't able to claim resources in the placement API471                # for any of the sorted hosts identified. So, clean up any472                # successfully-claimed resources for prior instances in473                # this request and return an empty list which will cause474                # select_destinations() to raise NoValidHost475                LOG.debug("Unable to successfully claim against any host.")476                break
_select_destinations · 逐主机申领

当前版本每台主机只尝试它的第一个 allocation candidate;成功即选中,失败继续外层主机循环。不能描述成遍历每台主机的全部 allocation requests。

nova/scheduler/manager.py31.0.0 · L478–L498
查看原文件
478            claimed_instance_uuids.append(instance_uuid)479            claimed_hosts.append(claimed_host)480            claimed_alloc_reqs.append(alloc_req)481482            # update the provider mapping in the request spec based483            # on the allocated candidate as the _consume_selected_host depends484            # on this information to temporally consume PCI devices tracked in485            # placement486            for request_group in spec_obj.requested_resources:487                request_group.provider_uuids = alloc_req[488                    'mappings'][request_group.requester_id]489490            # Now consume the resources so the filter/weights will change for491            # the next instance.492            self._consume_selected_host(493                claimed_host, spec_obj, instance_uuid=instance_uuid)494495        # Check if we were able to fulfill the request. If not, this call will496        # raise a NoValidHost exception.497        self._ensure_sufficient_hosts(498            context, claimed_hosts, num_instances, claimed_instance_uuids)
_select_destinations · 消费状态并核验批量结果

成功后更新请求的 provider 映射和本地已消费资源,使下一实例重新评分时看到本批次已经使用的资源;不足时进入统一失败清理检查。

六段原文覆盖过滤评分入口、归一化、加权排序、候选随机化、资源申领与批量消费。过滤器和 RAM weigher 的实际接入另附源码。

收益与取舍#

工程分析

带来的收益

  • 把可行性条件与排序偏好分开,避免用高评分补偿不满足的硬约束
  • 归一化降低评分量纲差异的直接影响,倍率仍可显式表达偏好方向和强度
  • 随机化与申领重试缓解多个调度器争抢同一候选的情况,不把候选查询误当作已完成的资源预留

付出的代价

  • 评分结果随候选集合、weigher 的 min/max 与倍率变化;归一化并不提供全局最优放置或公平性保证
  • 忽略过滤器和外部查询成本时,H 个候选、W 个评分器通常需要 O(WH) 评分及 O(H log H) 排序;批量创建会重复评估,申领还包含网络与服务端成本
  • 加大随机候选范围可能降低争抢,也可能选择分数较低的主机;主机满足当时约束仍不保证后续构建成功,需要其他重试与清理机制

可比较的选择

轮询所有计算主机选择成本低,但不能直接表达异构资源、亲和约束及可调偏好,仍需额外可行性检查。

全局优化求解一批实例放置可以显式优化整体目标,但模型、求解时间和实时资源变化处理更复杂;Nova 此路径采用逐实例的启发式选择。

版本与适用范围#

固定 OpenStack Nova 31.0.0(2025.1),实现位于 nova/scheduler/manager.py,不是旧版本的 filter_scheduler.py。主分类属于云基础设施,Nova 是控制平面,不是 hypervisor;可与 libvirt、QEMU、Linux KVM 的执行路径关联。本例限定有 Placement allocation candidates 的普通创建路径;rebuild 等 legacy 路径另有处理。申领失败继续候选由 Nova 源码支持,但本例不据此推导 Placement 服务端的全部并发控制保证。

参考来源#

源码、官方文档与原始论文
01
nova · _get_sorted_hosts · 过滤后评分

nova/scheduler/manager.py · _get_sorted_hosts · 过滤后评分

源码31.0.0
02
nova · normalize · 统一评分尺度

nova/weights.py · normalize · 统一评分尺度

源码31.0.0
03
nova · get_weighed_objects · 加权累加与排序

nova/weights.py · get_weighed_objects · 加权累加与排序

源码31.0.0
04
nova · _get_sorted_hosts · 首选主机随机化

nova/scheduler/manager.py · _get_sorted_hosts · 首选主机随机化

源码31.0.0
05
nova · _select_destinations · 逐主机申领

nova/scheduler/manager.py · _select_destinations · 逐主机申领

源码31.0.0
06
nova · _select_destinations · 消费状态并核验批量结果

nova/scheduler/manager.py · _select_destinations · 消费状态并核验批量结果

源码31.0.0
07
nova · get_weighed_objects · 归一化与加权完整调用

nova/weights.py · get_weighed_objects · 归一化与加权完整调用

源码31.0.0
08
nova · _select_destinations · 实例循环与候选遍历

nova/scheduler/manager.py · _select_destinations · 实例循环与候选遍历

源码31.0.0
09
nova · _get_sorted_hosts · 可配置的最高同分组打散

nova/scheduler/manager.py · _get_sorted_hosts · 可配置的最高同分组打散

源码31.0.0
10
nova · RAMWeigher · 空闲内存与倍率

nova/scheduler/weights/ram.py · RAMWeigher · 空闲内存与倍率

源码31.0.0
11
nova · HostManager · 已配置的过滤器与评分器

nova/scheduler/host_manager.py · HostManager · 已配置的过滤器与评分器

源码31.0.0
12
nova · _ensure_sufficient_hosts · 批量不足时清理申领

nova/scheduler/manager.py · _ensure_sufficient_hosts · 批量不足时清理申领

源码31.0.0
13
nova · _consume_selected_host · 本地资源与反亲和状态

nova/scheduler/manager.py · _consume_selected_host · 本地资源与反亲和状态

源码31.0.0

相关实现与比较#

4 个相关案例
19 个匹配案例