热迁移虚拟机
总体介绍
概述
当虚拟机在物理机上运行时,物理机可能存在资源分配不均,造成负载过重或过轻的情况。另外,物理机存在硬件更换、软件升级、组网调整、故障处理等操作,如何在不中断业务的情况下完成这些操作十分重要。虚拟机热迁移技术可以在业务连续前提下,完成负载均衡或上述操作,提升用户体验和工作效率。虚拟机热迁移通常是将整个虚拟机的运行状态完整保存下来,同时可以快速恢复到原有的甚至不同的硬件平台上。虚拟机恢复后,仍然可以平滑运行,用户感知不到任何差异。根据虚拟机数据存储在当前主机还是远端存储设备(共享存储)的不同,openEuler支持共享存储热迁移和非共享存储热迁移两种方式。
应用场景
共享存储和非共享存储热迁移的共同应用场景有:
- 当物理机故障或者负载过重时,可以将运行的虚拟机迁移到另一台物理机上,以避免业务中断,保证业务的正常运行。
- 当多数的物理机负载过轻时,可以将虚拟机迁移整合,以减少物理机数量,提高资源的利用率。
- 当物理服务器硬件设备成为瓶颈,比如CPU、内存、硬盘等,需要更换性能更好的硬件,或者需要增加设备,但是又不能关闭虚拟机或者停止业务。
- 服务器软件升级,比如虚拟化平台升级,就可以把虚拟机从旧版本虚拟化平台热迁移到新版本虚拟化平台。
对于非共享存储热迁移,还可以应用在如下场景:
- 当物理机故障存储空间不足,需要将运行的虚拟机迁移到另一台物理机上,可以避免业务中断,保证业务的正常运行。
- 当物理机存储设备老化,性能不能支撑当前业务数据处理,成为系统性能的瓶颈,需要更换性能更强的存储,但是又不能关闭虚拟机或者停止虚拟机,这需要将运行的虚拟机迁移到一个具有更好性能的物理机上。
注意事项和约束限制
热迁移过程中,需要保证网络状态良好。如果发生网络中断,热迁移会暂停,直到网络恢复后才会继续,当发生超时,热迁移会失败。
迁移过程中,不允许对虚拟机进行生命周期和管理虚拟机硬件设备等操作。
虚拟机正在迁移的过程中,应尽可能保证源端、目的端服务器不被意外下电或重启,否则会导致热迁移失败,甚至可能导致虚拟机被下电。
虚拟机正在迁移的过程中,不允许对虚拟机做关机、重启或恢复操作,否则可能会导致热迁移失败,当执行ACPI方式重启时,再执行热迁移会导致虚拟机关闭。
只支持同构热迁移,即源端和目的端CPU型号需要相同。
跨业务网段虚拟机迁移可以成功,但是到目的端后会出现网络异常,为了防止该情况发生,需要用户保证迁移业务网段一致。
如果源端虚拟机vCPU数大于目的端的物理机CPU核数,则迁移后将会影响到虚拟机的性能,应保证目的端物理机CPU核数大于等于源端虚拟机vCPU数。
非共享存储热迁移过程中的额外注意事项:
- 不支持迁移源端和目的端为同一个磁盘镜像文件的迁移,用户需要对该类迁移进行特殊处理,提防覆盖写坏数据而导致镜像损坏。
- 不支持对共享磁盘的迁移,用户需要对该类迁移进行防呆处理。
- 迁移的目的端镜像只支持文件,不支持裸设备,用户需要对目的端是裸设备的迁移进行防呆处理。
- 目的端需要创建与源端大小、数量相同的磁盘镜像,否则迁移失败。
- 混合迁移场景,需要传入迁移的磁盘,不能包括共享和只读的磁盘。
热迁移操作
前提条件
- 进行热迁移之前要确保源端和目的端主机之间的网络是互通的,并且源端和目的端获得资源权限是对等的,即两端同时能够访问到相同的存储资源和网络资源。
- 在执行虚拟机热迁移前应当对虚拟机进行健康检查,并确保目的端主机有足够的CPU、内存和存储资源。
热迁移脏页率预测(可选)
用户在迁移前可以使用dirtyrate功能,获取热迁移的内存脏页变化速率,根据虚拟机内存使用情况评估虚拟机是否适合迁移或配置合理的迁移参数。
使用方法:
例如,指定名为openEulerVM的虚拟机,计算时间为1s:
virsh qemu-monitor-command openEulerVM '{"execute":"calc-dirty-rate", "arguments": {"calc-time": 1}}'
间隔1s后,查询脏页变化速率:
virsh qemu-monitor-command openEulerVM '{"execute":"query-dirty-rate"}'
设置热迁移参数(可选)
在执行热迁移之前,可以通过使用 virsh migrate-setmaxdowntime命令来指定虚拟机热迁移过程中能够容忍的最大停机时间,这是一个可选的配置项。
例如,指定名为openEulerVM的虚拟机最大停机时间为500ms:
# virsh migrate-setmaxdowntime openEulerVM 500
同时,可以通过调用virsh migrate-setspeed来限制虚拟机热迁移占用的带宽大小,防止当前虚拟机热迁移的时候占用带宽过大,对主机上的其他虚拟机或者业务造成影响,这个选择同样也是热迁移的一个可选项。
例如,指定名为openEulerVM的虚拟机热迁带宽为500Mbps:
# virsh migrate-setspeed openEulerVM --bandwidth 500
用户可以使用migrate-getspeed来查询虚拟机热迁移过程中的最大带宽。
# virsh migrate-getspeed openEulerVM
500
用户可以使用migrate-set-parameters来设置热迁移时相关的参数,与热迁移压缩的参数如下所示:
- compress-level: 压缩级别,缺省值:1
- compress-threads: 压缩线程数目,缺省值:8
- compress-wait-thread: 是否等待压缩线程,缺省值:true
- decompress-threads: 解压缩线程数目,缺省值:2
- compress-method: 压缩算法选择(zlib、zstd),缺省值:zlib
例如,指定名为openEulerVM的虚拟机热迁算法为zstd,其余参数采用默认设置。
# virsh qemu-monitor-command openeulerVM '{ "execute": "migrate-set-parameters", "arguments": {"compress-method": "zstd"}}'
用户可以使用query-migrate-parameters来查询热迁移时相关的参数。
# virsh qemu-monitor-command openeulerVM '{ "execute": "query-migrate-parameters"}' --pretty
{
"return": {
"xbzrle-cache-size": 67108864,
"cpu-throttle-initial": 20,
"announce-max": 550,
"decompress-threads": 2,
"compress-threads": 8,
"compress-level": 1,
"compress-method": "zstd",
"multifd-channels": 2,
"announce-initial": 50,
"block-incremental": false,
"compress-wait-thread": true,
"downtime-limit": 300,
"tls-authz": "",
"announce-rounds": 5,
"announce-step": 100,
"tls-creds": "",
"max-cpu-throttle": 99,
"max-postcopy-bandwidth": 0,
"tls-hostname": "",
"max-bandwidth": 33554432,
"x-checkpoint-delay": 20000,
"cpu-throttle-increment": 10
},
"id": "libvirt-18"
}
热迁移操作(共享存储场景)
确认是否为共享存储。
# virsh domblklist <VMInstance> Target Source -------------------------------------------- sda /dev/mapper/open_euleros_disk sdb /mnt/nfs/images/openeuler-test.qcow2
首先,使用virsh domblklist命令查询虚拟机的存储设备信息,例如上面的查询结果显示虚拟机配置有2个存储设备:sda盘和sdb盘,然后再分别查询一下这两个设备对应后端存储是本地存储还是远端存储,如果虚拟机的所有存储设备都在远端共享存储之上,则说明该虚拟机为共享存储虚拟机,否则为非共享存储虚拟机。
执行如下命令,进行虚拟机热迁移。
例如,将虚拟机openEulerVM迁移到目的主机上使用virsh migrate命令。
# virsh migrate --live --unsafe openEulerVM qemu+ssh://<destination-host-ip>/system
其中,<destination-host-ip>为目的主机IP地址,热迁移之前需要进行ssh认证以获取目的端主机管理员权限。
另外,virsh migrate命令还有--auto-converge和--timeout子选项来保证迁移的顺利完成。
其中,相关子选项:
--unsafe命令会强制进行热迁移,忽略安全检查步骤。
--auto-converge命令会对CPU进行降频限速,确保热迁移流程能够收敛。
--timeout选项会指定一个热迁移超时时间,热迁移超过指定时间后会强制挂起虚拟机让热迁移得以收敛。
热迁移完成后命令返回,虚拟机在目的端主机正常运行。
热迁移操作(非共享存储场景)
首先,先查询虚拟机存储设备列表,确保虚拟机使用的是非共享存储。
例如,通过virsh domblklist查询到准备迁移的虚拟机有一个qcow2格式的磁盘sda,sda的xml配置为:
<disk type='file' device='disk'> <driver name='qemu' type='qcow2' cache='none' io='native'/> <source file='/mnt/sdb/openeuler/openEulerVM.qcow2'/> <target dev='sda' bus='scsi'/> <address type='drive' controller='0' bus='0' target='0' unit='0'/> </disk>
执行热迁移之前需要在目的端主机相同磁盘目录下创建一个虚拟磁盘文件,注意磁盘的格式和大小必须保持一致。
# qemu-img create -f qcow2 /mnt/sdb/openeuler/openEulerVM.qcow2 20G
在源端使用virsh migrate命令来执行热迁移,迁移的时候会将存储也一并迁移到目的端。
# virsh migrate --live --unsafe --copy-storage-all --migrate-disks sda \ openEulerVM qemu+ssh://<dest-host-ip>/system
热迁移完成后命令返回,虚拟机在目的端主机行正常运行,存储设备也被迁移到目的主机上。
热迁移操作(加密传输)
- 简介
为了能够更好的对虚拟机热迁移过程中数据的加密,openEuler提供了使用TLS对迁移数据进行加密的特性。几乎QEMU中所有的网络服务都能够使用TLS对会话数据进行加密操作,同时也可以使用X509证书对客户端进行简单的身份认证。
- 应用场景
典型应用场景为要求热迁移过程中虚拟机数据在源端和目的端进行传输时保证数据的安全性。
- 注意事项
在使用TLS对虚拟机进行热迁移前,需要申请证书,然后在源端和目的端分别设置证书。使用TLS功能前需要打开对端认证配置项,需在/etc/libvirt/qemu.conf文件中设置migrate_tls_x509_verify = 1。
单通道TLS热迁移的业务中断时长、迁移时长会有明显增长,迁移带宽上限100~200MB/s,可能导致迁移失败。
支持使用multiFd进行多通道TLS迁移,但会增加CPU开销(多开2个迁移线程),可能影响虚拟机运行;建议通过设置热迁移线程CPU亲和性将热迁移线程享受的CPU资源与虚拟机进程绑定的CPU资源隔离,迁移每台虚拟机建议绑定2个CPU。
- 使用方法
单通道热迁移加密传输命令
virsh migrate --live --unsafe --tls --domain openEulerVM --desturi qemu+tcp://<destination-host-ip>/system --migrateuri tcp://<destionation-host-ip>
多通道热迁移加密传输命令
virsh migrate --live --unsafe --parallel --tls --domain openEulerVM --desturi qemu+tcp://<destination-host-ip>/system --migrateuri tcp://<destionation-host-ip>
热迁移操作(迁移绑核)
- 简介
为了在特定情况下提升迁移性能,需要提供设置热迁移线程CPU亲和性的功能。在默认情况下,热迁移线程与虚拟机进程共享CPU资源,通过该功能,用户可以将热迁移线程运行的CPU资源设置到虚拟机进程绑定的CPU范围之外。
- 应用场景
典型应用场景为要求热迁移过程中可以控制迁移线程绑定的CPU,防止热迁移过程中因竞争CPU资源而对其他业务造成影响。
- 注意事项
当前迁移绑核需要运行对应的脚本来设置迁移绑核的参数,脚本内容如下:
#encoding=UTF-8
import libvirt
import sys
conn = None
domain = None
default_bandwidth = 30
default_flags = 27 + 512 # 要进行multifd迁移,则将flags再增加131072
if len(sys.argv) < 4:
print("usage: python migrateToURI3.py domain_name dst_ip xml")
sys.exit(1)
else:
try:
domain_name = sys.argv[1]
dst_ip = sys.argv[2]
xml_file = sys.argv[3]
f = open(xml_file, 'r')
dst_xml = f.read()
f.close()
disks = []
dconnuri = "qemu+tcp://" +dst_ip + "/system"
conn = libvirt.open("qemu:///system")
domain = conn.lookupByName(domain_name)
params = {}
params["migrate_uri"] = "tcp://" + dst_ip
params["destination_xml"] = dst_xml
params["persistent_xml"] = dst_xml
params["bandwidth"] = default_bandwidth
params["migration.pin"] = "0-1"
flags = default_flags
if len(sys.argv) > 4 and sys.argv[4].isdigit():
flags = int(sys.argv[4])
start = 5
else:
start = 4
for i in range(start, len(sys.argv)):
disks.append(sys.argv[i])
if len(disks) > 0:
params["migrate_disks"] = disks
domain.migrateToURI3(dconnuri, params, flags)
except:
error = libvirt.virGetLastError()
if error == None:
print ("error_number is NULL.")
raise
else:
print ('%s err_code %s' %(domain_name, error[0]))
print ('%s err_domain %s' %(domain_name, error[1]))
print ('%s err_message %s' %(domain_name, error[2]))
print ('%s err_level %s' %(domain_name, error[3]))
print ('%s err_str1 %s' %(domain_name, error[4]))
print ('%s err_str2 %s' %(domain_name, error[5]))
print ('%s err_str3 %s' %(domain_name, error[6]))
print ('%s err_int1 %s' %(domain_name, error[7]))
print ('%s err_int2 %s' %(domain_name, error[8]))
print ('Done')
if domain is not None:
domain.__del__()
if conn is not None:
conn.close()
- 使用方法
执行迁移绑核脚本
python migrateToURI3.py domain_name dst_ip xml