NUMA 感知的实时迁移¶
https://blueprints.launchpad.net/nova/+spec/numa-aware-live-migration
当具有 NUMA 特性的实例进行实时迁移时,这些特性不会在目标计算主机上重新计算。在 CPU pinning 的情况下,在目标主机上使用源主机的 pin 映射可能导致多个实例被 pinned 到相同的 pCPUs。对于 hugepage 备份的实例,它们是 NUMA 本地化的,实例需要在实时迁移期间在目标计算主机上重新计算其 NUMA 映射。
问题描述¶
注意
在以下段落中,术语 NUMA 被错误地用于表示客体虚拟机所表现出的任何特征,这些特征都体现在 InstanceNUMATopology 对象中,例如 CPU pinning 和 hugepages。即使在没有客体 NUMA 拓扑的情况下也可以实现 CPU pinning,但不幸的是,在 Nova 中这两个概念紧密耦合,没有实例 NUMA 拓扑就无法进行实例 pinning。因此,NUMA 被用作一个统称。
注意
本规范主要关注 libvirt 驱动程序。任何更高级别的代码(计算管理器、conductor)都将尽可能地与驱动程序无关。
这个问题可以用三个例子来最好地描述。
第一个例子是带有 CPU pinning 的实时迁移。一个具有 hw:cpu_policy=dedicated extra spec 和 pinned CPUs 的实例被实时迁移。它的 pin 映射被简单地复制到目标主机。这会产生两个问题。首先,其 pinned pCPUs 在目标主机上没有被正确声明。这意味着,如果第二个具有 pinned CPUs 的实例降落在目标主机上,两个实例的 vCPUs 可能会被 pinned 到相同的 pCPUs。其次,目标主机上现有的 pin 映射被忽略。如果另一个实例已经存在于目标主机上,两个实例的 vCPUs 可能会被 pinned 到相同的 pCPUs。在两种情况下,dedicated CPU 策略被违反,可能导致不可预测的性能下降。
第二个例子是具有 hugepages 的实例。有两个主机,每个主机有两个 NUMA 节点和每个节点 8 个 1GB hugepages。两个相同的实例启动在两个主机上。它们的虚拟 NUMA topology 是一个虚拟 NUMA 节点和 8 个 1GB 内存页。它们降落在各自主机上的 NUMA 节点 0 上,消耗了它的所有 8 个页。一个实例被实时迁移到另一个主机。libvirt 驱动程序强制执行严格的 NUMA 亲和性,并且不会重新生成实例 XML。两个实例最终都位于主机的 NUMA 节点 0 上,并且实时迁移的实例无法运行。
第三个例子是一个具有虚拟 NUMA topology(但没有 hugepages)的实例。如果一个与主机 NUMA 节点 2 关联的实例被实时迁移到一个只有两个 NUMA 节点的主机,因此没有 NUMA 节点 2,它将无法运行。
用例¶
作为云管理员,我希望实时迁移具有 CPU pinning 的实例,而无需在目标计算主机上重叠 pin 映射。
作为云管理员,我希望 hugepage 备份的实例的实时迁移能够工作,并且实例能够在目标计算主机上成功运行。
作为云管理员,我希望具有显式 NUMA topology 的实例的实时迁移能够工作,并且实例能够在目标计算主机上成功运行。
提议的变更¶
支持 NUMA 实时迁移有五个方面。首先,需要重新计算实例的 NUMA 特征,使其适应新主机。其次,需要声明实例将在新主机上消耗的资源。第三,需要在目标主机上生成有关实例新的 NUMA 特征的信息(仅有 InstanceNUMATopolgy 对象是不够的,稍后会详细说明)。第四,需要将此信息从目标主机发送到源主机,以便源主机能够生成正确的 XML,使实例能够在目标主机上运行。最后,实例的资源声明需要“收敛”,以反映实时迁移的成功或失败。如果实时迁移成功,则需要在源主机上释放使用量。如果失败,则需要在目标主机上回滚声明。
资源声明¶
让我们首先解决资源声明方面的问题。已经开始努力在 placement 中支持 NUMA 资源提供程序 [3],并标准化 CPU 资源跟踪 [4]。但是,placement 只能跟踪资源的数量的清单和分配。它不会跟踪使用的特定资源。对于 NUMA 实时迁移,需要具体性。考虑一个实例,它在未来使用 4 个专用 CPU,而标准 CPU 资源跟踪规范 [4] 已经实现。在实时迁移期间,调度器会在目标 placement 中声明这 4 个 CPU。但是,我们需要防止其他实例使用这些特定的 CPU。因此,除了在 placement 中声明 CPU 的数量之外,我们还需要在计算主机上声明特定的 CPU。计算资源跟踪器已经存在,正是为了这个目的,即使在启用 NUMA 的 placement 的未来,它也将继续用于声明目标主机上的特定资源。
在调度器选择实时迁移的目标主机与两个计算主机之间的实际实时迁移 RPC 交互之间存在时间窗口。另一个实例可能会在此时间窗口期间降落在目标主机上,消耗掉调度器认为空闲的 NUMA 资源。这种竞争会导致目标主机上的资源声明失败。本规范建议使用现有的 MigrationPreCheckError 异常机制来处理此声明失败,从而导致调度器选择新的主机。
适应新主机¶
使用资源跟踪器的优势在于,它迫使我们使用 MoveClaim,从而免费获得新的实例 NUMA 拓扑 (Claim._test_numa_topology in nova/compute/claims.py)。
在目标主机上生成新的 NUMA 信息¶
然而,仅在声明中拥有新的实例 NUMA 拓扑不足以让源主机生成新的 XML。生成新的 XML 的最简单方法是从新的实例 NUMA 拓扑开始,就是调用 libvirt 驱动程序的 _get_guest_numa_config 方法(它方便地接受一个 instance_numa_topology 作为参数)。但是,这需要在目标主机上完成,因为它取决于主机 NUMA 拓扑。 _get_guest_numa_config 返回一个 LibvirtConfigObject 元组。其中包含的信息需要以某种方式通过网络发送到源主机。
一种简单的方法是直接发送对象,或者可能调用 to_xml 并发送生成的 XML 文本块。这将是未版本化的,并且没有模式。在例如,较新的 libvirt 驱动程序,它已经放弃了对特定元素或属性的支持,与仍然支持它的旧 libvirt 驱动程序通信时,这可能会导致问题。
由于这个原因,并且坚持 OpenStack 发送 oslo versionedobjects 的最佳实践,本规范建议将必要的 NUMA 相关信息编码为 Nova versioned 对象。这些新对象应该尽可能地与 virt 驱动程序无关,但由于用例仍然是 libvirt 与 libvirt 通信,因此为了抽象而抽象是不合适的。
发送新的 NUMA Nova 对象¶
一旦 superconductor 选择并/或验证了目标主机,当前实时迁移流程的相关部分就可以通过以下简化的伪序列图来概括。
+-----------+ +---------+ +-------------+ +---------+
| Conductor | | Source | | Destination | | Driver |
+-----------+ +---------+ +-------------+ +---------+
| | | |
| check_can_live_migrate_destination() | | |
|-------------------------------------------------------------------------->| |
| | | |
| | check_can_live_migrate_source() | |
| |<-----------------------------------| |
| | | |
| | migrate_data | |
| |----------------------------------->| |
| | | |
| | migrate_data | |
|<--------------------------------------------------------------------------| |
| | | |
| live_migration(migrate_data) | | |
|------------------------------------->| | |
| | | |
| | pre_live_migration(migrate_data) | |
| |----------------------------------->| |
| | | |
| | migrate_data | |
| |<-----------------------------------| |
| | | |
| | live_migration(migrate_data) | |
| |------------------------------------------------->|
| | | |
在建议的新流程中,目标计算管理器要求 libvirt 驱动程序使用从 move claim 获得的新实例 NUMA 拓扑来计算新的 LibvirtGuestConfig 对象。计算管理器将这些 LibvirtGuestConfig 对象转换为新的 NUMA Nova 对象,并将它们作为字段添加到 LibvirtLiveMigrateData migrate_data 对象中。后者最终到达源 libvirt 驱动程序,后者使用它来生成新的 XML。建议的流程在以下图中总结。
+-----------+ +---------+ +-------------+ +---------+
| Conductor | | Source | | Destination | | Driver |
+-----------+ +---------+ +-------------+ +---------+
| | | |
| check_can_live_migrate_destination() | | |
|------------------------------------------------------------------------------------------->| |
| | | |
| | check_can_live_migrate_source() | |
| |<----------------------------------| |
| | | |
| | migrate_data | |
| |---------------------------------->| |
| | | +-----------------------------------+ |
| | |-| Obtain new_instance_numa_topology | |
| | | | from claim | |
| | | +-----------------------------------+ |
| | | |
| | | _get_guest_numa_config(new_instance_numa_topology) |
| | | ---------------------------------------------------->|
| | | |
| | | LibvirtConfigGuest objects |
| | |<-----------------------------------------------------|
| | | |
| | | +----------------------------------+ |
| | |-| Build new NUMA Nova objects from | |
| | | | LibvirtConfigGuest objects | |
| | | | and add to migrate_data | |
| | | +----------------------------------+ |
| | | |
| migrate_data + new NUMA Nova objects | |
|<-------------------------------------------------------------------------------------------| |
| | | |
| live_migration(migrate_data + new NUMA Nova objects) | | |
|------------------------------------------------------->| | |
| | | |
| | pre_live_migration() | |
| |---------------------------------->| |
| |<----------------------------------| |
| | | |
| | live_migration(migrate_data + new NUMA Nova objects) |
| |----------------------------------------------------------------------------------------->|
| | | |
| | | +-----------------------------------+ |
| | | | generate NUMA XML for destination |-|
| | | +-----------------------------------+ |
| | | |
声明收敛¶
声明对象是一个上下文管理器,因此理论上,如果其上下文中的任何代码引发未处理的异常,它可以自行清理。但是,实时迁移涉及计算主机之间的 RPC 投递,因此使用声明作为上下文管理器是不切实际的。因此,如果实时迁移失败,则需要在回滚期间手动调用 drop_move_claim 以从目标主机删除声明。无论是在源主机上的 rollback_live_migration 还是在 rollback_live_migration_at_destination 中执行此操作,都留作实现细节。
同样,如果实时迁移成功,则需要调用 drop_move_claim 以从源主机删除声明,类似于计算管理器中的 _confirm_resize 所做的那样。无论是在源主机上的 post_live_migration 还是在 post_live_migration_at_destination 中执行此操作,都留作实现细节。
备选方案¶
使用 move claims 和在其中计算的新实例 NUMA 拓扑基本上决定了其余的实现。
当 superconductor 调用调度器的 select_destination 方法时,该调用最终会调用 numa_fit_instance_to_host (select_destinations -> _schedule -> _consume_selected_host -> consume_from_request -> _locked_consume_from_request -> numa_fit_instance_to_host)。可以重用该结果。但是,声明仍然会计算自己的新的实例 NUMA 拓扑。
数据模型影响¶
创建新的 version 对象,以从目标主机到源主机传输 cell、CPU、emulator thread 和 hugepage nodeset 映射。这些对象被添加到 LibvirtLiveMigrateData 中。
REST API 影响¶
无。
安全影响¶
无。
通知影响¶
无。
其他最终用户影响¶
无。
性能影响¶
无。
其他部署者影响¶
无。
开发人员影响¶
无。
升级影响¶
在混合 N/N+1 云的情况下,目标主机和源主机之间信息交换的可能性总结在下表中。其中,no 表示新代码不存在,old path 表示新代码存在但选择执行旧代码以实现向后兼容性,而 yes 表示使用了新功能。
旧目标主机 |
新目标主机 |
|||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
旧源主机 |
|
|
||||||||||||||||||||
新源主机 |
|
|
实现¶
负责人¶
- 主要负责人
notartom
工作项¶
在完全实现本规范之前,禁止迁移具有 NUMA 拓扑的实例 [5]。
添加 NUMA Nova 对象
将声明上下文添加到实时迁移
在目标主机上计算新的 NUMA 拓扑,并将其发送到源主机
源主机根据目标主机计算的新 NUMA 拓扑更新实例 XML
依赖项¶
无。
测试¶
在 gate 中使用的 libvirt/qemu 驱动程序当前不支持 NUMA 功能(尽管正在进行相关工作 [6])。因此,在 upstream gate 中测试 NUMA 感知实时迁移需要嵌套 virt。此外,NUMA 实时迁移测试的可断言结果(如果它最终成为可能)只是实时迁移成功。检查实例 XML 以断言有关其 NUMA 亲和性或 CPU pin 映射的内容明确不在 tempest 的范围内。出于这些原因,NUMA 感知实时迁移最好在第三方 CI [7] 或其他下游测试场景 [8] 中进行测试。
文档影响¶
当前的实时迁移文档没有在任何地方提及 NUMA 限制。因此,解释实时迁移的新 NUMA 功能的 release note 就足够了。
参考资料¶
历史¶
发布名称 |
描述 |
|---|---|
Rocky |
引入 |
Stein |
重新提出,并修改了与声明以及目标主机和源主机之间信息交换相关的内容。 |
Train |
重新提出,没有修改。 |