1. 基本概念
热迁移,又叫动态迁移,相对应的,就有冷迁移或者静态迁移,那么热迁移相比于冷迁移,热迁移的特点是什么?
热迁移和冷迁移最大的区别就是,冷迁移过程中有一段明显的时间VM的服务不可用,而热迁移则没有明显的服务暂停时间。热迁移的过程中,无需关闭或者长时间暂停VM,VM的应用保持正常运行,只有在热迁移临近结束时,才会有一个非常短暂的停机切换时间,这样保证了VM服务的可用性,客户在这个过程中,感觉不到正在使用的VM的停机时间,提升了业务的连续性和用户体验。
2. 热迁移总体流程
当使用libvirt管理虚拟机并执行热迁移命令时, 源端libvirt与目的端libvirt会建立socket连接,并检测和控制整个热迁移过程状态, 如果热迁移过程中有错误发生,libvirt可以进行热迁移回退,防止虚拟机发生异常。
3. 热迁移基本原理
一般来说,一个VM的正常运行只需要以下几个组成部分:虚拟机内存、vCPU、block IO设备、net网络设备以及其它配置信息。热迁移要做的事情就是要把VM从一个物理机热迁移到另外一个物理机。以内存拷贝方式的不同, 热迁移分为预拷贝(pre-copy)和后拷贝(post-copy)两种方式。以预拷贝(pre-copy)为例,热迁移大致可以分成以下几个步骤:
步骤一:把虚拟机的配置同步到目的物理机;
步骤二:把VM的内存拷贝至目的端;
步骤三:把VCPU、存储和网络设备的状态拷贝到目的端。
步骤四:把VM在目的端物理机上恢复运行。
3.1. 内存迁移
我们知道在热迁移的时候,VM是持续的保持运行的,或者说VM持续对内存进行着读写操作。所以热迁移中在进行内存备份的时候需要有一个明确的标志位来标志哪些内存是已经被拷贝过,哪些内存在拷贝过后,又再次发生了修改。这些都是一个叫做log dirty的机制来实现的。
即在热迁移一开始的时候,hypervisor会把VM的所有内存在stage2页表中都先标记为写保护,然后开始将VM的内存从源端物理机拷贝到目的端物理机。在这个拷贝的过程中,不可避免的会发生VM对某个页进行写操作情况,这时就会触发page fault,产生VM Exit,退回到KVM。KVM在处理stage2页表的缺页异常时,会重新把这个页面标记为可读写从而允许VM继续进行写操作, 并在脏页位图中把该页面标记为脏页。qemu会从KVM中获取脏页信息,并将这些脏页拷贝到目的端。一轮脏页拷贝流程结束。
这一轮内存拷贝结束后,KVM会做一个校验,看看哪些内存页面属性被重新设置了可写标记,并把这些页面属性又重新设置为写保护,并开始下一轮的内存脏页跟踪和拷贝,这一过程将一直迭代,直到剩余脏页达到我们的预期。
当剩余的脏页数目达到预期之后,即qemu判断在当前物理机网络带宽以及虚拟机停止时间符合设定要求的情况下能够将剩余的脏页和设备状态信息发送到目的端,就可以开始最后一轮的脏页拷贝和设备状态拷贝。在最后一轮拷贝开始之前,会将源端的VM设置成停机状态,将剩余的脏页和设备状态都拷贝到目的端,目的端将内存和设备状态恢复之后,恢复VM的运行。
需要注意的是, Intel对脏页跟踪有PML (Page Modification Logging)的硬件支持。 在开启脏页跟踪功能后,vCPU写页面之后,硬件自动将脏页信息记录在PML Buffer中,并在PML Buffer满时触发VM Exit退出到KVM,KVM将PML Buffer中的脏页信息同步到脏页位图中。PML可以很大程度上减小热迁移过程中的内存写性能下降。ARM硬件没有类似的机制,当开启脏页同步功能时,需要在KVM中将内存全部设置为写保护状态。在一轮脏页迭代过程中,vCPU首次写一个页面时会VM Exit退出到KVM中处理。
4. 热迁移流程梳理
4.1. qemu热迁移架构
qemu中的内存和各种设备, 在热迁移开始之前都会将需要save和load的数据、回调函数注册到热迁移相关的全局变量列表中。
static SaveState savevm_state = { .handlers = QTAILQ_HEAD_INITIALIZER(savevm_state.handlers), .global_section_id = 0, };
注册的方式有两种:
- VMState方式注册(推荐方式):定义VMStateDescription,其中包含热迁移想要保存的数据,也可定义对应的pre_save/pre_load/post_save/post_load回调函数。定义完成后通过vmstate_register 函数添加到全局链表savevm_state.handlers中。
例如RootPort设备定义的VMStateDescription如下:
static const VMStateDescription vmstate_rp_dev = { .name = "pcie-root-port", .priority = MIG_PRI_PCI_BUS, .version_id = 1, .minimum_version_id = 1, .post_load = pcie_cap_slot_post_load, .fields = (VMStateField[]) { VMSTATE_PCI_DEVICE(parent_obj.parent_obj.parent_obj, PCIESlot), VMSTATE_STRUCT(parent_obj.parent_obj.parent_obj.exp.aer_log, PCIESlot, 0, vmstate_pcie_aer_log, PCIEAERLog), VMSTATE_MSIX_TEST(parent_obj.parent_obj.parent_obj.parent_obj, GenPCIERootPort, gen_rp_test_migrate_msix), VMSTATE_END_OF_LIST() } };
- legacy方式:调用register_savevm_live函数注册save_state/load_state回调函数,用来保存和恢复该设备的状态。
4.2. qemu热迁移流程
以qemu v4.2.0版本为准,热迁移分为以下几个阶段:准备阶段,迭代拷贝阶段,结束拷贝和状态迁移阶段。源端执行这三个步骤的函数是migration_thread,对应的流程如下图所示:
相对应的是目的端qemu中的内存和设备恢复流程,目的端qemu会根据源端传输的数据,按照约定的格式进行解析和状态恢复。这里不再赘述。
5、小结
本文主要介绍了热迁移的基本原理,并对qemu中的热迁移代码流程进行了梳理。