为实例隔离调度器数据库

https://blueprints.launchpad.net/nova/+spec/isolate-scheduler-db

作为上述蓝图的一部分,已经确定几个调度器过滤器直接访问 nova 数据库,或调用 nova 计算 API。为了允许最终将调度器分离成自己的服务(即,Gantt 工作),需要更改这些过滤器。

问题描述

目前有三个调度器过滤器需要访问 nova 计算 API 或 nova 数据库才能工作

  • TypeAffinityFilter

  • SameHostFilter

  • DifferentHostFilter

第一个需要知道主机上的所有风味,而其他两个需要知道主机上所有实例的 UUID。它们当前 API/数据库访问方法阻止调度器被分离成一个独立的服務。需要更新这些过滤器,以使用调度器中的主机状态对象,而不是直接访问数据库或调用 nova 计算 API。

使情况复杂化的是,预计在部署升级其调度器到 Kilo 时,仍然会有许多计算节点运行较旧的、Kilo 之前的软件,并且无法使调度器及时了解其实例信息。我们需要一种方法来区分这些节点,以便我们知道对这些节点运行现有的数据库/API 调用,并识别它们何时已升级。

用例

Nova 贡献者希望扩展调度器的功能,并打算将其分离到 Gantt 项目中。为了有效地做到这一点,必须修改调度器及其过滤器周围的内部接口,以删除对 nova 数据库和 API 的直接访问。

项目优先级

此蓝图是 Kilo 版本的优先级 ‘scheduler’ 重构工作的一部分。

提议的变更

与其让过滤器进行数据库或 API 调用,我们将把实例信息添加到已经传递给过滤器的 host_state 对象中。这不是一项简单的任务,需要几个步骤。

概述

完成此操作的步骤是

  • 调度器在启动时查询数据库以获取当前的实例信息。

  • 调度器将 host:instance 信息存储在 HostManager 中。

  • ComputeManager 通过 RPC API 在发生重大更改时,将更新的实例信息发送到调度器。

  • 调度器使用收到的更新来更新其 HostManager。

  • ComputeManager 定期通过 RPC API 将其当前实例 UUID 列表发送到调度器。

  • 调度器将这些 UUID 与其实例视图进行比较。如果存在差异,则调度器会为该主机重新创建 InstanceList 并更新其 HostManager。

  • HostManager 将把实例信息添加到调度请求期间创建的 HostState 对象中

  • 当前直接访问通过直接调用 Nova 获取实例信息的过滤器现在将基于 HostState 对象中的信息来做出过滤决策。

  • 一些部署不使用需要实例信息的过滤器,并且需要能够关闭所有此行为。

  • 允许在滚动更新期间使用不同的行为。

调度器启动的更改

实例信息的初始填充将在 HostManager 初始化时完成。它将首先检索所有实例,使用一种新的方法 InstanceList.get_all()。然后它将处理这些实例,创建一个字典,其键是 host_name,其值是 2 个元素的字典。此字典将具有一个 ‘instances’ 键,其值将是该主机的 InstanceList,以及一个 ‘updated’ 键,其值默认为 False,但在 Scheduler 收到来自计算节点的同步或更新消息时将设置为 True。请参阅下面的部分 处理 计算 节点的 滚动 更新,了解有关为什么需要此信息以及如何使用它的更多详细信息。

此字典将存储在 HostManager 的 _instance_info 属性中,该属性表示调度器初始化时所有计算节点上实例的状态。 ‘updated’ 键默认为 False,并设置为 True

虽然一次检索所有实例可能是一个非常繁重的调用,但之前的建议是首先获取所有主机,然后为每个主机运行查询所有实例的查询,但由于所需的数据库调用数量,这被认为是一种较慢的方法。另外请记住,这仅在调度器启动时执行一次,因此它不会在正常操作期间发挥作用。

对 ComputeManager 操作的更改

当计算节点的任何实例发生重大更改(创建/销毁/调整大小),或者当新的计算节点上线时,计算节点将通知调度器。对于新的或调整大小的实例,将发送当前的 Instance 对象。对于已删除的实例,将仅发送该实例的 uuid。这将通过调度器的 2 个新的 RPC API 方法来完成。对于创建/更新,将添加以下方法

  • update_instance_info(context, host_name, instance_or_list)

对于已终止的实例,将添加以下方法

  • delete_instance_info(context, host_name, instance_uuid)

ComputeManager 将需要在几个地方进行这些调用

  • init_host() - 当新的主机上线时(发送完整

    InstanceList)

  • _build_and_run_instance() - 创建新实例时

  • _complete_deletion() - 销毁实例时

  • _finish_resize() - 调整实例大小时

  • shelve_offload_instance() - 卸载搁置实例时

  • _post_live_migration() - 实时迁移完成后

所有调用都将在这些方法的末尾进行,当我们确定操作已成功,并且 Instance 对象已填充所有其属性后,再进行调用以更新调度器。

计算节点将调用调度器客户端,然后通过 RPC fanout cast 将这些调用的信息发送到所有正在运行的调度器。在调用的调度器端,此信息将用于更新 HostManager 的 ‘_instance_info’ 属性。对于更新,HostManager 将找到指定 host_name 的 InstanceList,并尝试在 InstanceList 的对象中找到接收到的实例的 uuid,如果找到,它将删除旧对象。HostManager 然后将接收到的 Instance 添加到 InstanceList.objects 列表中。对于删除,HostManager 将在主机的 InstanceList 中找到 Instance 对象,并将其删除。

在 init_host() 更新的情况下,这将发送一个完整的 InstanceList 对象,HostManager 将用新的 InstanceList 替换 _instance_info 属性中该主机条目的 InstanceList(如果有)。如果主机键尚不存在于 _instance_info 字典中,则将添加它。

虽然传递整个 Instance 对象可能被认为是一种 ‘繁重’ 的方法,但它比仅传递 instance_type_id 和 uuid 更可取,原因有两个

  • 将来可能创建的依赖于特定主机上实例的过滤器将能够使用这些对象,而无需修改计算节点和调度器之间的整个报告系统来传递和存储新的实例信息。

  • 此设计将更接近于我们将调度器分离成自己的服务和自己的数据库时将采用的方式。将其简化为现在需要的数据将意味着以后需要更多的工作。

添加定期同步完整性检查

我们还必须考虑到由于消息传递层中的故障或其他异常问题,来自计算节点的消息偶尔可能会丢失,这将导致调度器对计算节点上实例的视图不准确。为了尽量减少计算节点上实例的实际状态与 HostManager 中保存的状态视图之间的差异,计算节点将定期创建一个实例 UUID 列表,并将其传递给调度器客户端。为此将添加以下 RPC API 方法

  • sync_instance_info(context, host_name, instance_uuids)

这将作为定期任务调用,并使用新的 CONF 设置来处理频率。

当调度器收到此同步通知时,它将构造 HostManager 的 _instance_info 属性中指定 host_name 的 UUID 列表,并将其与收到的列表进行比较。如果它们匹配,则无需采取进一步的操作。但是,如果存在任何差异,则必须假定正常更新过程发生了某种不寻常的干扰,并且对该 host_name 的实例的视图无效。最好简单地让调度器调用 InstanceList.get_by_host(),然后用检索到的值替换 HostManager._instance_info 中该主机的 InstanceList。也可以选择检索通知中不在 HostManager 中的 UUID 的单个 Instance 对象,并删除 HostManager 中但在通知中不存在的实例,但如果处于明显的错误状态,最好从头开始并确保两个版本同步。

由于主机可以在 HostManager 重建 InstanceList 时继续发送更新,因此所有可以更改主机视图的方法都将用信号量锁进行装饰,以避免争用。

请注意,这两种方法都无法帮助实例已调整大小但发送到调度器的消息丢失的情况。由于同步列表和 HostManager 列表中的 UUID 将匹配,因此不会检测到任何差异。可以更改同步以发送 (instance_uuid, instance_type_id) 元组,但此选项在年中会议上讨论过,并被认为是不必要的。

调度请求流程的更改

HostManager.get_all_host_states() 方法将扩展为将每个主机的 InstanceList 添加到 host_state 对象中。这些 host_state 对象将传递给过滤器,然后过滤器将直接从 host_state 对象访问有关实例的信息,而不是进行数据库/API 调用。

选择退出实例通知

许多部署不使用任何这些过滤器,因此它们不需要调度器拥有有关实例的当前信息。让它们不断发送永远不会使用信息将是浪费的,因此我们将添加一个新的 CONF 设置,该设置默认为 True。如果部署者知道他们的设置不使用任何这些过滤器,他们可以将该设置更改为 False。计算节点将在启动时读取此设置,如果为 False,则不会在实例更改时更新调度器,也不会发送定期同步消息。同样,HostManager 将看到此设置并知道不要在启动时打扰创建实例缓存,也不要在 HostState 对象中添加实例信息。

处理计算节点的滚动更新

由于我们不能假定所有计算节点在调度器更新到 Kilo 时都会更新,因此还需要处理我们没有关于计算节点上实例的当前信息的情况,因为 Kilo 之前的节点不会执行上述实例信息更新。在没有这些更新的情况下,我们不能依赖 HostManager 中 InstanceList 的版本,因此必须在每次调度请求时查询数据库。为了跟踪这一点,_instance_info 属性中的每个主机的条目将具有一个值,该值是一个两元素字典:一个用于保存 InstanceList,另一个用于保存状态指示器。此值最初设置为 False,表示我们尚未知道计算节点是否正在运行将正确更新调度器实例信息的软件版本。一旦我们从主机收到更新/删除/同步消息,我们就知道它至少正在运行一个最小版本以信任调度器的实例视图,并且我们可以将其用于过滤器。

此设计将意味着在调度器首次启动时需要进行一些额外的调用数据库以获取 InstanceList,因为在收到来自该主机的至少一个更新或同步消息之前,它将无法信任任何主机的 InstanceList 信息。但是,这比调度器基于不正确的信息做出不当决策更好。

备选方案

另一种更简洁的设计是将 InstanceList 添加为 ComputeNode 对象的延迟加载属性。当调度器启动时,将检索并存储在 HostManager 中内存中的 ComputeNodeList,而不是仅存储 (host_name: InstanceList) 值的字典。将不再需要调用 _get_all_host_states(),但必须小心,以便将对主机本身的任何更改也传播到调度器。但虽然这总体上是一种更简洁的方法,并且更符合我们希望调度器发展的方向,但认为这超出了当前蓝图的范围。

数据模型影响

无。

REST API 影响

无。

安全影响

无。

通知影响

无。

其他最终用户影响

无。

性能影响

由于三个过滤器都不需要对每个主机进行数据库或 API 调用,因此会进行一些改进,但这很小,并不是进行这些更改的驱动力。

其他部署者影响

部署者必须评估他们对需要主机实例信息的这些过滤器的使用情况,并更新他们的配置文件以禁用调度器对实例的跟踪(如果不需要)。

开发人员影响

无。

实现

负责人

主要负责人

edleafe

其他贡献者

无。

工作项

  • 添加一个新的 CONF 选项来关闭来自计算管理器的实例更新,以及关闭调度器收集实例信息并将其添加到 HostState 对象中用于过滤器。

  • 更新调度器,使其在启动时获取计算节点上的实例的初始状态,除非为此行为关闭了 CONF 标志。

  • 修改计算管理器中“建议更改”部分中列出的方法,使其在成功后调用适当的方法将更改传递给调度器,除非为此行为关闭了 CONF 标志。

  • 添加一个新的周期性任务方法到计算管理器,以将实例 UUID 列表发送到 _sync_scheduler_instance_info() 方法,除非为此行为关闭了 CONF 标志。

  • 添加一个新的 CONF 设置来控制上述周期性任务的间隔。

  • 在计算管理器类中创建方法,以接收各种方法传递的参数,并将其传递给调度器客户端。

  • 将 RPC API 调用添加到 SchedulerAPI 类中,以便调度器客户端在接收到关于实例更改的计算通知时调用。

  • 在调度器中添加方法,这些方法将接受 RPC API 调用传递的信息,并正确更新 HostManager 对给定主机的 InstanceList 的视图。

  • 在 HostManager 中添加一个调和方法,以比较主机在其 _instance_info 属性中的 uuid 值与主机同步调用传递的值,如果它们不匹配,则重新创建 InstanceList。

  • 更新 HostManager 的 _get_all_host_states(),以将 InstanceList 信息添加到支持此版本的每个主机,除非为此行为关闭了 CONF 标志。对于运行旧版本的 host,调用 InstanceList.get_by_host() 来获取信息,并将该信息添加到 HostState 对象中。

  • 删除过滤器中的当前 db/api 调用,并修改代码以查找 HostState 对象中的 InstanceList 信息。

  • 添加测试,以验证用于开启/关闭实例更新的 CONF 设置是否被尊重。

  • 添加测试,以验证更改计算节点的版本会如何改变 HostManager 处理将实例信息添加到 HostState 对象的方式。

依赖项

无。

测试

过滤器已经具有足够的测试覆盖率,但这些测试当前会模拟 db/api 调用。它们需要更新以反映新的实现。

ComputeNode 对象的测试需要更新,以测试在所需方法中是否进行了正确的调用,以及是否正确尊重 CONF 标志。

HostManager 中的新行为也需要添加新的测试来覆盖这些更改。

文档影响

需要为新的 CONF 设置提供适当的文档,这些设置将用于关闭调度器对实例的跟踪,以及设置计算节点的同步周期。

参考资料

这项工作是本规范中概述工作的一部分

https://review.openstack.org/#/c/89893/