1. QEMU/KVM 迁移的概念迁移(migration)包括系统整体的迁移和某个工作负载的迁移。系统整理迁移,是将系统上所有软件包括操作系统完全复制到另一个物理机硬件机器上。虚拟化环境中的迁移,可分为静态迁移(static migration,或者 冷迁移 cold migration,或者离线迁移 offline migration) 和 动态迁移 (live migration,或者 热迁移 hot migration 或者 在线迁移 online migration)。静态迁移和动态迁移的最大区别是,静态迁移有明显一段时间客户机中的服务不可用,而动态迁移则没有明显的服务暂停时间。 虚拟化环境中的静态迁移也可以分为两种,一种是关闭客户机后,将其硬盘镜像复制到另一台宿主机上然后恢复启动起来,这种迁移不能保留客户机中运行的工作负载;另一种是两台宿主机共享存储系统,这时候的迁移可以保持客户机迁移前的内存状态和系统运行的工作负载。 动态迁移,是指在保证客户机上应用服务正常运行的同时,让客户机在不同的宿主机之间进行迁移,其逻辑步骤和前面的静态迁移几乎一直,有硬盘存储和内存都复制的动态迁移,也有仅复制内存镜像的动态迁移。不同的是,为了保证迁移过程中客户机服务的可用性,迁移过程只能有非常短暂的停机时间。动态迁移允许系统管理员将客户机在不同物理机上迁移,同时不会断开访问客户机中服务的客户端或者应用程序的连接。一个成功的迁移,需要保证客户机的内存、硬盘存储和网络连接在迁移到目的主机后任然保持不变,而且迁移的过程的服务暂停时间较短。 1.1 迁移效率的衡量(1)整体迁移时间 (2)服务器停机时间:这时间是指源主机上的客户机已经暂停服务,而目的主机上客户机尚未恢复服务的时间。 (3)对服务性能的影响:客户机迁移前后性能的影响,以及目的主机上其它服务的性能影响。 其中,整体迁移时间受很多因素的影响,比如 Hypervisor 和迁移工具的种类、磁盘存储的大小(是否需要复制磁盘镜像)、内存大小及使用率、CPU 的性能和利用率、网络带宽大小及是否拥塞等,整体迁移时间一般分为几秒钟到几十分钟不等。动态迁移的服务停机时间,也有这些因素的影响,往往在几毫秒到几百毫秒。而静态迁移,其暂停时间较长。因此,静态迁移一般适合于对服务可用性要求不高的场景,而动态迁移适合于对可用性要求高的场景。 动态迁移的应用场景包括:负载均衡、解除硬件依赖、节约能源 和异地迁移。 1.2 KVM 迁移的原理1.2.1 静态迁移对于静态迁移,你可以在宿主机上某客户机的 QEMU monitor 中,用 savevm my_tag 命令保存一个完整的客户机镜像快照,然后在宿主机中关闭或者暂停该客户机,然后将该客户机的镜像文件复制到另一台宿主机中,使用在源主机中启动该客户机时的命令来启动复制过来的镜像,在其 QEMU monitor 中 loadvm my_tag 命令恢复刚才保存的快照即可完全加载保存快照时的客户机状态。savevm 命令可以保证完整的客户机状态,包括 CPU 状态、内存、设备状态、科协磁盘中的内存等。注意,这种方式需要 qcow2、qed 等格式的磁盘镜像文件的支持。 1.2.2 动态迁移如果源宿主机和目的宿主机共享存储系统,则只需要通过网络发送客户机的 vCPU 执行状态、内存中的内容、虚机设备的状态到目的主机上。否则,还需要将客户机的磁盘存储发到目的主机上。共享存储系统指的是源和目的虚机的镜像文件目录是在一个共享的存储上的。 在基于共享存储系统时,KVM 动态迁移的具体过程为:
注意,当客户机中内存使用率非常大而且修改频繁时,内存中数据不断被修改的速度大于KVM能够传输的内存速度时,动态迁移的过程是完成不了的,这时候只能静态迁移。 关于实时迁移的效率,业界不少人提出了改进的建议,比如通过使用内存压缩技术,减少需要传输的内存的大小。这篇文章比较了各种方法,还是值得一读。 1.3 使用命令行的方式做动态迁移1.3.1 使用 NFS 共享存储(1)在源宿主机上挂载 NFS 上的客户机镜像,并启动客户机 mount my-nfs:/raw-images/ /mnt/ kvm /mnt/rh1.img -smp 2 -m 2048 -net nic -net tap (2)在目的宿主机上也挂载镜像目录,并启动一个客户机用于接收动态迁移过来的内存内容 mount my-nfs:/raw-images/ /mnt/ kvm /mnt/rh1.img -smp 2 -m 2048 -net nic -net tap -incoming tcp:0:6666 注意:(1)NFS 挂载目录必须一致 (2)“-incoming tcp:0:6666” 参数表示在 6666 端口建立一个 TCP socket 连接用于接收来自源主机的动态迁移的内容,其中 0 表示运行来自任何主机的连接。“-incoming“ 使 qemu-kvm 进程进入到监听模式,而不是真正以命令行中的文件运行客户机。 (3)在源宿主机的客户机的 QEMU monitor 中,使用命令 ” migrate tcp:host2:6666" 即可进入动态迁移的流程。 1.3.2 不使用共享存储的动态迁移过程类似,包括使用相同backing file 的镜像的客户机迁移,以及完全不同镜像文件的客户机的迁移。唯一的区别是,migrate 命令中添加 “-b” 参数,它意味着传输块设备。 1.3.3 其它 QEMU monitor migrate 命令
2. OpenStack Nova QEMU/KVM 实例动态迁移的环境配置除了直接拷贝磁盘镜像文件的冷迁移,OpenStack 还支持下面几种虚机热迁移模式:
实时迁移的过程并不复杂,复杂在于环境配置。 2.1 基础环境配置2.1.1 SSH 权限配置这种方式需要配置源(compute1)和目的主机(compute2)之间能够通过 SSH 相互访问,以确保能通过 TCP 拷贝文件,已经可以通过 SSH 在目的主机建立目录。 使用 nova 用户在compute1 上执行操作: usermod -s /bin/bash nova su nova mkdir -p -m 700 .ssh #创建 config 文件如下 nova@compute2:~/.ssh$ cat config Host * StrictHostKeyChecking no UserKnownHostsFile=/dev/null #产生 key ssh-keygen -f id_rsa -b 1024 -P "" cat id_rsa.pub >> authorized_keys #将 id_rsa id_rsa.pub 拷贝到 compute2 上面 cat id_rsa.pub >> authorized_keys 使用 root 用户在每个主机上进行操作: root@compute1:/var/lib/nova/.ssh# chown -R nova:nova /var/lib/nova root@compute1:/var/lib/nova/.ssh# chmod 700 /var/lib/nova/.ssh root@compute1:/var/lib/nova/.ssh# chmod 600 /var/lib/nova/.ssh/authorized_keys 测试 SSH 无密码访问: nova@compute1:~/.ssh$ ssh nova@compute2 ls Warning: Permanently added 'compute2,192.168.1.29' (ECDSA) to the list of known hosts. ... nova@compute2:~/.ssh$ ssh nova@compute1 ls Warning: Permanently added 'compute1,192.168.1.15' (ECDSA) to the list of known hosts. ... 2.1.2 其它配置每个node 上的 DNS 或者 /etc/hosts,确保互联互通。 2.2 Live migration 环境配置2.2.1 libvirtd 配置在 compute1 和 compute2 上做如下配置: ->Edit /etc/libvirt/libvirtd.conf listen_tls = 0 listen_tcp = 1 auth_tcp = “none” ->Edit /etc/init/libvirt-bin.conf env libvirtd_opts="-d -l" ->Edit /etc/default/libvirt-bin
# options passed to libvirtd, add "-l" to listen on tcp ->Restart libvirtd service libvirt-bin restart root 12088 1 2 07:48 ? 00:00:00 /usr/sbin/libvirtd -d -l 做完上述操作后,可以使用如下命令来检查是否设置正确: root@compute2:~# virsh -c qemu+tcp://compute1/system list --all Id Name State ---------------------------------------------------- 4 instance-0000000d running 5 instance-00000006 running - instance-00000005 shut off root@compute1:~# virsh -c qemu+tcp://compute2/system list --all Id Name State ---------------------------------------------------- Nova 设置: ->Edit /etc/nova/nova.conf, add following line: [libvirt] block_migration_flag = VIR_MIGRATE_UNDEFINE_SOURCE,VIR_MIGRATE_PEER2PEER, VIR_MIGRATE_LIVE,VIR_MIGRATE_TUNNELLED,VIR_MIGRATE_NON_SHARED_INC live_migration_flag = VIR_MIGRATE_UNDEFINE_SOURCE, VIR_MIGRATE_PEER2PEER, VIR_MIGRATE_LIVE,VIR_MIGRATE_TUNNELLED live_migration_uri = qemu+tcp://%s/system 2.2.2 共享存储 Live migration 环境配置其实共享存储的实时迁移配置的要求和块拷贝的实时迁移的配置差不多,除了下面几点:
live_migration_flag=VIR_MIGRATE_UNDEFINE_SOURCE,VIR_MIGRATE_PEER2PEER,VIR_MIGRATE_LIVE,VIR_MIGRATE_TUNNELLED 注意:对于上面第二点,在 Kilo 版本中(前面版本的情况未知),当 Nova 使用 RBD 作为 image backend 时,Nova 会认为是在共享存储上: def check_instance_shared_storage_local(self, context, instance): """Check if instance files located on shared storage.""" if self.image_backend.backend().is_shared_block_storage(): return None 在 class Rbd(Image): 类中: @staticmethod def is_shared_block_storage(): """True if the backend puts images on a shared block storage.""" return True 目前,只有 RBD 作为 image backend 时,该函数才返回 true。对于其它类型的 backend,Nova 会在目的 host 上的 instance folder 创建一个临时文件,再在源 host 上查看该文件,通过判断是否该文件在共享存储上来判断是否在使用共享存储。 常见问题: (1)在源 host 上,出现 ”live Migration failure: operation failed: Failed to connect to remote libvirt URI qemu+tcp://compute2/system: unable to connect to server at 'compute2:16509': Connection refused“ 其原因是 2.1.1 部分的 libvirt 设置不正确。 (2)在目的 host 上,出现 ”libvirtError: internal error: process exited while connecting to monitor: 2015-09-21T14:17:31.840109Z qemu-system-x86_64: -drive file=rbd:vms/6bef8898-85f9-429d-9250-9291a2e4e5ac_disk:id=cinder:key=AQDaoPpVEDJZHhAAu8fuMR/OxHUV90Fm1MhONQ==:auth_supported=cephx\;none:mon_host=9.115.251.194\:6789\;9.115.251.195\:6789\;9.115.251.218\:6789,if=none,id=drive-virtio-disk0,format=raw,cache=writeback,discard=unmap: could not open disk image rbd:vms/6bef8898-85f9-429d-9250-9291a2e4e5ac_disk:id=cinder:key=AQDaoPpVEDJZHhAAu8fuMR/OxHUV90Fm1MhONQ==:auth_supported=cephx\;none:mon_host=9.115.251.194\:6789\;9.115.251.195\:6789\;9.115.251.218\:6789: Could not open 'rbd:vms/6bef8898-85f9-429d-9250-9291a2e4e5ac_disk:id=cinder:key=AQDaoPpVEDJZHhAAu8fuMR/OxHUV90Fm1MhONQ==:auth_supported=cephx\;none:mon_host=9.115.251.194\:6789\;9.115.251.195\:6789\;9.115.251.218\:6789': Operation not permitted“ 原因:目的 host 上的用户操作 RBD 的权限设置不正确,检查 secret 设置。 3. 迁移过程3.0 Nova 有关迁移的命令Nova 有三个与迁移有关的命令:migrate,live-migrate 和 resize。
3.1 静态迁移(migrate 或者 resize 不使用新的 flavor)s1@controller:~$ nova migrate --poll 9eee079e-0353-44cb-b76c-ecf9be61890d Server migrating... 100% complete Finished s1@controller:~$ nova list +--------------------------------------+-------+---------------+------------+-------------+------------------------------------------+ | ID | Name | Status | Task State | Power State | Networks | +--------------------------------------+-------+---------------+------------+-------------+------------------------------------------+ | 02699155-940f-4401-bc01-36220db80639 | vm10 | ACTIVE | - | Running | demo-net2=10.0.10.17; demo-net=10.0.0.39 | | 9eee079e-0353-44cb-b76c-ecf9be61890d | vm100 | VERIFY_RESIZE | - | Running | demo-net2=10.0.10.20 | +--------------------------------------+-------+---------------+------------+-------------+------------------------------------------+ s1@controller:~$ nova resize-confirm 9eee079e-0353-44cb-b76c-ecf9be61890d s1@controller:~$ nova list +--------------------------------------+-------+--------+------------+-------------+------------------------------------------+ | ID | Name | Status | Task State | Power State | Networks | +--------------------------------------+-------+--------+------------+-------------+------------------------------------------+ | 02699155-940f-4401-bc01-36220db80639 | vm10 | ACTIVE | - | Running | demo-net2=10.0.10.17; demo-net=10.0.0.39 | | 9eee079e-0353-44cb-b76c-ecf9be61890d | vm100 | ACTIVE | - | Running | demo-net2=10.0.10.20 | +--------------------------------------+-------+--------+------------+-------------+------------------------------------------+
3.1.1 迁移过程直接使用流程图来说明:
1. migrate 和 resize 都是执行静态迁移。 2. 静态迁移分为三个阶段: (1)调用 Scheduler 算法选择目的 node(步骤5),并通过 RPC 远程调用 prep_resize 做些迁移前的准备工作 (2)在源主机上,调用 libvirt driver 做一系列操作:
(3)通过 RPC,调用目的 node 上的 Nova 的 finish_resize 方法。该方法会在自己本机上设置网络、结束网络设置工作,并调用 libvirt driver 来:
至此,虚机已经被拷贝到目的主机上了。接下来,用户有两个选择:resize_confirm 和 resize_revert。 3.1.2 确认迁移 (resize_confirm)迁移确认后,在源主机上,虚机的文件会被删除,虚机被 undefine,虚机的 VIF 被从 OVS 上拔出,network filters 也会被删除。
3.1.3 取消迁移 (resize_revert)取消迁移的命令首先发到目的 node 上,依次 tear down network,删除 domain,断掉 volume connections,然后调用源主机上的方法来重建 network,删除临时文件,启动 domain。这样,虚机就会需要到 resize 之前的状态。
3.2 实时迁移 (Live migration)可以 Nova client 的 live-migration 命令来做实时迁移,除了要被迁移的 虚机 和 目的 node 外,它可以带两个额外的参数:
root@compute1:/home/s1# qemu-img info /var/lib/nova/instances/8352e969-0a25-4abf-978f-d9d0ec4de0cd/disk.local image: /var/lib/nova/instances/8352e969-0a25-4abf-978f-d9d0ec4de0cd/disk.local file format: qcow2 virtual size: 1.0G (1073741824 bytes) disk size: 324K cluster_size: 65536 backing file: /var/lib/nova/instances/_base/ephemeral_1_default Format specific information: compat: 1.1 lazy refcounts: false REST API request body 实例: {"os-migrateLive": {"disk_over_commit": false, "block_migration": true, "host": "compute2"}} 实时迁移的主要步骤如下:
其过程也可以分为三个阶段: 3.2.1 实时迁移前的准备工作 (步骤 2 - 7)Nova 通过 RPC 调用目的主机上 nova comute manager 的 pre_live_migration 方法,它依次: (1)准备 instance 目录: (1)创建 instance dir (2)如果源和目的虚机不共享 instance path:获取镜像类型,为每一个disk,如果不使用 backing file 的话则调用 “qemu-img create” 方法来创建空的磁盘镜像;否则,依次创建空的 Ephemeral disk 和 Swap disk,以及从 Glance 中获取 image 来创建 Root disk (3)如果不是 block migration 而且 不 is_shared_instance_path,则 _fetch_instance_kernel_ramdisk (2)调用 volumer driver api 为每一个volume 建立目的主机和 volume 的连接 (3)调用 plug_vifs(instance, network_info) 将每一个 vif plug 到 OVS 上 (4)调用 network_api.setup_networks_on_host 方法,该方法会为迁移过来的虚机准备 dhcp 和 gateway; (5)调用 libvirt driver 的 ensure_filtering_rules_for_instance 方法去准备 network filters。 3.2.2 调用 libvirt API 开始迁移虚机 (步骤 8 - 9)这部分的实现在 libvirt driver 代码中。因为 libvirt 的一个 bug (说明在这里),当 libvirt 带有 VIR_DOMAIN_XML_MIGRATABLE flag 时,Nova 会调用 libvirt 的 virDomainMigrateToURI2 API,否则调用 virDomainMigrateToURI API。 首先比较一下 block live migration 和 live migration 的 flags 的区别: #nova block live migration flags:VIR_MIGRATE_UNDEFINE_SOURCE, VIR_MIGRATE_PEER2PEER, VIR_MIGRATE_LIVE, VIR_MIGRATE_TUNNELLED, VIR_MIGRATE_NON_SHARED_INC #nova live migration flags: VIR_MIGRATE_UNDEFINE_SOURCE, VIR_MIGRATE_PEER2PEER, VIR_MIGRATE_LIVE, VIR_MIGRATE_TUNNELLED 各自的含义如下:
再看看两个 API 的参数: int virDomainMigrateToURI2 (virDomainPtr domain, const char * dconnuri, # 格式为 qemu+tcp://<desthost>/system const char * miguri, #为 none const char * dxml, #指定迁移后的虚机的 XML。Nova 对 “/devices/graphics” 部分做了一点点更改。 unsigned long flags, # nova.conf 中的配置 const char * dname, #none unsigned long bandwidth) # 由 CONF.libvirt.live_migration_bandwidth 指定,默认为 0 表示由 libvirt 自己选择合适的值 如果 libvirt 不带 VIR_DOMAIN_XML_MIGRATABLE flag,则调用的 API 是: int virDomainMigrateToURI (virDomainPtr domain, const char * duri, unsigned long flags, const char * dname, unsigned long bandwidth) 可见,两个 API 唯一的区别是不能指定新的虚机使用的 XML 配置。这时候你必须手动配置 VNC 或者 SPICE 地址为 0.0.0.0 or :: (接收全部)或者 127.0.0.1 or ::1 (只限本机)。 调用 API 后,接下来就是等待其完成。这其中的过程应该主要包括: (1)根据传入的 domain xml,启动一个虚机,它处于等待 TCP incoming 状态 (2)从源 node 上将 domain 的数据传过来 (3)快完成时,关闭源 node 上的虚机,传输最后一次数据,打开目的 node 上的虚机 (4)将源 node 上的虚机删除 Nova 每个0.5 秒检查源虚机的状态,直到它被删除。 迁移完成后,需要执行后续的操作(_post_live_migration)。 3.2.3 迁移完成后在源和目的主机上的后续操作(步骤 10 -29)在源主机上,依次执行下面的操作:
3.2.4 迁移过程中失败时的回滚迁移的三个步骤中,前面第一个和第二个步骤中出现失败的话,会调用 _rollback_live_migration 启动回滚操作。该方法 (1)将虚机的状态由 migrating 变为 running。 (2)调用 network_api.setup_networks_on_host 方法重做源主机上的网络设置 (3)通过 RPC 调用,去目的主机上将准备过程中建立的 volume 连接删除。 (4)通过 RPC 调用,去目的主机上调用 compute_rpcapi.rollback_live_migration_at_destination 函数,该方法会 (1)调用 network_api.setup_networks_on_host 方法去 tear down networks on destination host (2)调用 libvirt driver 的 driver.rollback_live_migration_at_destination 方法,它会将 domain 删除,并且清理它所使用的资源,包括 unplug vif,firewall_driver.unfilter_instance,_disconnect_volume, _delete_instance_files, _undefine_domain。 3.2.5 测试环境:准备两个虚机 vm1 和 vm2,操作系统为 cirros。打算将 vm1 迁移到另一个node 上。在 vm2 上查看 vm1 在迁移过程中的状态。 迁移前:在 vm1 中运行 “ping vm2”,并在 vm2 中 ssh 连接到 vm1。 结果:vm1 迁移过程中,vm2 上 ssh 的连接没有中断,vm1 中的 ping 命令继续执行。在另一次测试结果中,vm2 ping vm1 在整个迁移过程中 time 出现了一次 2ms 的时间增加。 3.3 遇到过的问题3.3.1 apparmor将虚机从 compute1 迁移到 compute2 成功,再从 compute2 迁移到 compute1 失败,报错如下: An error occurred trying to live migrate. Falling back to legacy live migrate flow. Error: unsupported configuration: Unable to find security driver for label apparmor 经比较迁移前后的虚机的 xml,发现 compute2 上的虚机的 xml 多了一项:<seclabel type='none' model='apparmor'/> 。 分别在 compute 1 和 2 上运行 “virsh capabilities”,发现 compute1 没有使用 apparmor,而 compute2 使用了 apparmor。 #compute 1 上 <secmodel> <model>none</model> <doi>0</doi> </secmodel> #compute2 上 <secmodel> <model>apparmor</model> <doi>0</doi> </secmodel> 最简单的方法是在两个 node 上都 disable apparmor(在 /etc/libvirt/qemu.conf 中添加 ‘security_driver = “none” 然后重启 libvirtd),然后 destroy/start 虚机后,它的 xml 配置中的 apparmor 就没有了。这篇文章 详细介绍了 apparmor。 3.3.2 当虚机是 boot from volume 时,live migration 失败。报错: Command: iscsiadm -m node -T iqn.2010-10.org.openstack:volume-26446902-5a56-4c79-b839-a8e13a66dc7a -p 10.0.2.41:3260 --rescan Exit code: 21 Stdout: u'' Stderr: u'iscsiadm: No session found.\n' to caller 原因是 cinder 代码中有 bug,导致目的主机无法建立和 volume 的连接。fix 在这里。
参考文档: https://www.mirantis.com/blog/tutorial-openstack-live-migration-with-kvm-hypervisor-and-nfs-shared-storage/ http://www.sebastien-han.fr/blog/2015/01/06/openstack-configure-vm-migrate-nova-ssh/ KVM 原理技术 实战与原理解析 任永杰、单海涛著 OpenStack 官网 (责任编辑:IT) |