OpenStack内存迁移简介

迁移命令

  • OpenStack Nova组件负责提供计算资源,包括虚机的创建销毁,启停,迁移等,对于在线迁移,迁移的内容包括虚机的内存,QEMU维护的设备状态,如果是磁盘迁移还有磁盘内容。普通的迁移就是将这些内容通过网络从源端传输到目的端,加密迁移顾名思义,将内容加密后再传输。本章对于迁移的介绍,会分加密迁移和非加密迁移来讲。

普通迁移

OpenStack

  1. 创建网络
    openstack network create --provider-network-type vxlan --internal $NET
  2. 创建网络的子网,指定其网段
    openstack subnet create $SUBNET --network $NET --subnet-range 192.168.2.0/24
  3. 查看并选取虚机可用的规格
    openstack flavor list
  4. 查看并选取虚机可以用的镜像
    openstack image list
  5. 创建虚机
    openstack server create --flavor $FLAVOR --image $IMAGE --network $NET $VMNAME
  6. 启动虚机
    openstack server start $VMNAME
  7. 查看虚机所在的计算节点
    openstack server show $VMNAME
  8. 查询所有计算节点,选定要迁移的目的节点
    openstack compute service list
  9. 确认Nova配置文件的内容/etc/nova/nova.conf
live_migration_with_native_tls=false —— 非加密迁移
live_migration_tunnelled=false —— 非隧道迁移
live_migration_permit_auto_converge=true —— 迁移收敛
live_migration_permit_post_copy=false —— 非post-copy
  1. 重启计算节点的Nova服务使配置生效
    systemctl restart openstack-nova-compute
  2. 迁移虚机
    openstack server migrate $VMNAME --live $HOSTNAME
  3. 检查虚机是否迁移成功
openstack server show $VMNAME —— 查看虚机是否再目的主机上
/var/log/nova/nova-compute.log —— 如果出错,查看计算节点的Nova日志

Libvirt

  1. 编辑并定义虚机xml,虚机使用共享磁盘
    virsh define vm.xml
  2. 启动虚机
    virsh start $VMNAM
  3. 迁移虚拟机
    virsh migrate $VMNAM --live --p2p --undefinesource --persistent --auto-converge --persistent-xml /path/to/dst_persistent.xml --migrateuri tcp://$MIGRATE_NET_IP qemu+tcp://$DST_IP/system
--live: 在线迁移
--p2p: 点对点方式迁移,发起迁移的libvirt客户端只连接源端libvirtd服务,源端会控制整个迁移的过程,它直连目的端的libvirtd服务,源端和目的端点对点迁移,不需要发起迁移的libvirt客户端参与迁移流程,因此称为点对点迁移
--undefinesouce: 如果迁移成功了,将源端虚机的xml undefine掉
--persistent: 如果迁移成功了,用指定的xml定义虚机
--migrateuri: 指定虚机迁移使用的网络,当libvirt对迁移目的端的IP解析不正确,或者主机侧有多张网卡,或者目的端使用unix方式迁移的时候,都可以通过migrateuri显示指定迁移网络,迁移网络$MIGRATE_NET_IP可以和目的端网络$DST_IP相同,也可以不同

注意:libvirt在迁移时,源端虚机的vnc server监听的IP不能和OpenStack定义的虚机一样 —— 设置为本机的IP,需要设置成0.0.0.0,这样迁移前后vnc server绑定端口才能通用,否则在迁移过程中,目的端启动qemu进程时会绑定源端IP以及对应的端口号,用作vnc server的端口监听。这种操作会导致迁移报错: Cannot assign requested address。

加密迁移

证书准备

  • 加密迁移分许多类型,这里(Openstack/Libvirt/Qemu)使用非对称加密。主流的非对称加密框架就是PKI —— Public Key Infrastructure ,TLS是按照PKI框架基于SSL实现的加密传输协议。X.509 certificate是PKI框架中按照RFC 5280标准定义的用于实现加密传输的证书。
  • 拽了这么多名词,其实加密迁移我们会涉及以下文件
  1. 根密钥:cakey.pem,用于制作根证书,独一份,私钥,保存在证书服务器上
    certtool --generate-privkey > cakey.pem
  2. 根证书模板文件:ca.info,用于制作根证书,通过根密钥,以该文件为模制作根证书
cn = Name of your organization
ca
cert_signing_key
  1. 根证书:cacert.pem,用于制作加密迁移证书,独一份,它包含公钥信息,保存在证书服务器上
    certtool --generate-self-signed --load-privkey cakey.pem --template ca.info --outfile cacert.pem
  2. 客户端密钥:clientkey.pem,用于制作客户端证书,私钥
    certtool --generate-privkey > clientkey.pem
  3. 客户端模板文件:client.info,包含的关键信息是客户端的节点名cn = hostname,用于制作客户端证书
country = CN
state = SiChuang
locality = ChengDu
organization = Libvirt Project
cn = client1
tls_www_client
encryption_key
signing_key
  1. 客户端证书:clientcert.pem,包含客户端的公钥信息以及客户端本身信息,当服务端要确认客户端身份时,会检查此证书,每个计算节点都有一份
    certtool --generate-certificate --load-privkey clientkey.pem --load-ca-certificate cacert.pem --load-ca-privkey cakey.pem --template client.info --outfile clientcert.pem
  2. 服务端密钥:serverkey.pem,用于制作服务端证书,私钥
    certtool --generate-privkey > serverkey.pem
  3. 服务端模板文件:server.info,包含的关键信息是服务端的节点名cn = hostname,以及IP地址,用于制作服务端证书
organization = Name of your organization
cn = compute1.libvirt.org   
signing_key
  1. 服务端证书:servercert.pem,包含服务端的公钥信息以及服务端本身信息,当客户端要服务端身份时,会检查此证书,每个计算节点都有一份
    certtool --generate-certificate --load-privkey serverkey.pem --load-ca-certificate cacert.pem --load-ca-privkey cakey.pem --template server.info --outfile servercert.pem
  • TODO:非对称加密证书历史及作用

命令

  • OpenStack
    修改nova配置文件的加密迁移选项live_migration_with_native_tls=true,重启openstack-nova-compute服务,迁移命令不变
    openstack server migrate $VMNAME --live $HOSTNAME
  • Libvirt
    客户端增加tls参数,其他选项不变
    virsh migrate $VMNAM --live --p2p --undefinesource --persistent --auto-converge --tls --persistent-xml /path/to/dst_persistent.xml --migrateuri tcp://$MIGRATE_NET_IP qemu+tcp://$DST_IP/system

流程浅析

Http Request -> Nova API

  • OpenStack在线迁移流程由Nova负责,客户端通过向控制节点的nova-api服务发送rest api请求,url格式如下:
    http://$host_ip:$nova-api-port/servers/{server_id}/action
host_ip: nova-api服务所在节点的IP,通常任意控制节点都提供nova-api service
nova-api-port: nova-api服务监听的端口号,通过配置文件配置文件/etc/nova/nova.conf的osapi_compute_listen_port选项可以指定
server_id: 通过openstack server list查到的要迁移虚机的ID

注意:$host_ip:$nova-api-port如果不确定,可以根据openstack或nova等http客户端工具的debug选项查看到。比如nova list --debug可以跟踪nova客户端向nova-api发起的所有请求的详细信息。

  • 以curl工具为例,介绍客户端发起迁移后的流程
  1. 客户端根据自己的认证信息(域、用户名、密码),向keystone请求token获得认证,之后的Request请求中,服务端检查到http头部添加了TOKEN,不再拒绝请求。否则会报无权限的错误。
格式:
curl -v -s -X POST $OS_AUTH_URL/auth/tokens   -H "Content-Type: application/json"   -d '{ "auth": { "identity": { "methods": ["password"],"password": {"user": {"domain": {"name": "'"$OS_USER_DOMAIN_NAME"'"},"name": "'"$OS_USERNAME"'", "password": "'"$OS_PASSWORD"'"} } }, "scope": { "project": { "domain": { "name": "'"$OS_PROJECT_DOMAIN_NAME"'" }, "name":  "'"$OS_PROJECT_NAME"'" } } }}' \
| python -m json.tool

示例:
curl -v -i \
  -H "Content-Type: application/json" \
  -d '
{ "auth": {
    "identity": {
      "methods": ["password"],
      "password": {
        "user": {
          "name": "admin",
          "domain": { "id": "default" },
          "password": "ADMIN_PASS"
        }
      }
    },
    "scope": {
      "project": {
        "name": "admin",
        "domain": { "id": "default" }
      }
    }
  }
}' \
  "http://keystone-admin.cty.os:10006/v3/auth/tokens"
  
服务端响应后如果成功,返回的状态为201 Created,将其头部的X-Subject-Token字段取出作为之后rest请求的token值:
export OS_TOKEN=$X-Subject-Token
  1. 迁移命令需要指定目的host,设置迁移的各种参数,因此是POST方法,POST格式为JSON,字段定义参考OpenStack Compute API中对在线迁移的介绍。格式如下:
URL: /servers/{server_id}/action
Request Body:
{
    "os-migrateLive": {
        "host": "$hostname",
        "block_migration": false,
        "disk_over_commit": false,
        "force": false
    }
}
  1. 客户端发起迁移,假设我们现在将一个uuid为811d9313-02d7-4c72-ba88-422e18c3004f的虚拟机迁移到主机hb02-compute-10e114e194e14上,示例如下:
curl -g -i -X POST http://nova-api.cty.os:10010/v2.1/servers/811d9313-02d7-4c72-ba88-422e18c3004f/action -H "User-Agent: python-novaclient" -H "Content-Type: application/json" -H "Accept: application/json" -H "X-Auth-Token: $OS_TOKEN" -d '{"os-migrateLive": {"disk_over_commit": false, "block_migration": false, "host": "hb02-compute-10e114e194e14"}}'

Nova API -> Nova Compute

Http Request -> WSGI Server

WSGI Server -> WSGI Application

WSGI Application -> Nova Compute

  • 对于RESTful风格的互联网软件框架,核心概念就是资源,网络中任何东西都是资源,每个资源对应一个特定的URI,并用它来标识,访问URI的各种方法就是操作资源的各种方法。OpenStack定义了很多资源,并实现了针对这些资源的各种操作函数。路由就是将请求的URL映射到对应资源,然后可以对资源进行操作。
  • OpenStack引入WSGI(Web Server Gateway Interface)用来将HTTP到后端对应操作的映射以一种标准的接口实现,在WSGI中,每个资源被抽象成一个Controller对象,它包含很多操作,每个操作对应一个HTTP请求和响应。当HTTP请求到达时,WSGI首先将其路由到对应的Controller,调用Controller对应的操作函数,这是一个通用的操作。对于想要添加一个API的开发者来说,它会将其对应的实现封装成Controller的子类,在WSGI框架的调用流程中,会根据HTTP请求的不同最终调用到开发者自己提供的接口。
  • 以迁移举例,服务端接收HTTP情求到最后的Hypervisor发起,可以分成以下阶段:
  1. HTTP请求 -> WSGI Controller
    WSGI框架根据HTTP请求的内容,将其路由到对应的Controller,对于迁移来说,就是nova.api.openstack.compute.migrate_server:MigrateServerController,MigrateServerController初始化migrate相关的所有操作,包括迁移操作。
服务启动:
nova.cmd.api:main													/* nova-api服务入口 */
	server = service.WSGIService(api, use_ssl=should_use_ssl)		/* 初始化WSGI Server,加载配置文件中指定的应用osapi_compute */
	/* 启动WSGI Server,但Http Request到达时,由osapi_compute进行处理 */
	launcher.launch_service(server, workers=server.workers or 1)	
	/* 根据/etc/nova/api-past.ini的配置,可以逐步找到最终建立路由信息的操作实现 */
	osapi_compute	->	openstack_compute_api_v21	->	
		osapi_compute_app_v21	->	nova.api.openstack.compute:APIRouterV21.factory	->
			nova.api.openstack.compute.routes:APIRouterV21
				APIRouterV21.__init__								/* APIRouterV21类在初始化的时候,会建立URL和Resource的映射关系 */
					create_route
- [TODO]:如何建立路由?
路由请求:
当Request到达时,nova.api.wsgi.Router:__call__会被触发,最终将Request路由到对应的Controller上
	nova.api.wsgi.Router:__call__	->	Router:_router
		......
		ServerMigrationsController.__init__
			self.compute_api = compute.API()						/* 注册所有的computerAPI */
  1. WSGI Controller -> MigrateServerController
  • TODO
  1. MigrateServerController -> Nova Computer服务本地迁移
  • 这个阶段MigrateServerController._migrate_live被触发,函数调用链如下:
/* 该函数解析Request中的body信息,设置对应的迁移参数 */
nova.api.openstack.compute.migrate_server.MigrateServerController:_migrate_live
	nova.compute.api.API:live_migrate					
		compute_task_api.live_migrate_instance		<=>					
			nova.conductor.api.ComputeTaskAPI:live_migrate_instance	/* 调用conductor.ComputeTaskAPI的live_migrate_instance方法 */
				cctxt = self.client.prepare(version=version)			/* self.client是一个过程调用的句柄,用于实现远程过程调用 */
				cctxt.cast(context, 'live_migrate_instance', **kw)		/* 调用RPC Server注册的live_migrate_instance接口 */
  • Nova中一个四个组件,nova-api,nova-compute,nova-conductor,nova-scheduler,他们之间互相的通信使用了基于AMQP实现的RPC机制,其中compute,conductor,scheduler在启动时都会注册一个RPC Server,api因为内部没有服务会调用它提供的接口,因此无需注册。
  • 上面的liva_migrate_instance就是conductor的RPC Server注册的供其它组件调用的接口,Conductor RPC Server中有一个nova.conductor.manager.ComputeTaskManager类,它包含了nova-conductor对外提供的live_migrate_instance接口,因此conductor组件使用RPC远程调用live_migrate_instance方法,最终调用它本身提供的live_migrate_instance接口。这里其实是conductor组件自己调用自己的RPC接口。所以可以看到在获取调用句柄的时候,并没有指明目标host,它不是跨节点的RPC调用。
  • 这段代码比较难奇怪,nova-api绕来绕去,执行迁移最终交给了conductor来做,似乎并没有交给compute组件。这里之所以这样写,和conductor组件定位有关系,API依据请求时间长短,将请求发送给conductor或者compute,对于长时任务(比如这里的迁移),请求会发送给condutor,conductor负责全程跟踪和调度,并且它除了负责长时任务还负责代理其它节点的DB访问。最终对于虚拟机的操作,比如迁移,还是都会发送到compute组件完成。接下来继续分析conductor组件中的live_migrate_instance接口
nova.conductor.manager.ComputeTaskManager:live_migrate_instance
	nova.conductor.manager.ComputeTaskManager:_live_migrate
		nova.conductor.task.live_migrate.LiveMigrationTask:_execute					/* 创建迁移任务并开始执行 */
			self.compute_rpcapi.live_migration	<=>	nova.compute.rpcapi.ComputeAPI:live_migration
				client = self.router.client(ctxt)
				cctxt = client.prepare(server=host, version=version)				/* 创建RPC调用的句柄 */
				cctxt.cast(ctxt, 'live_migration', instance=instance, dest=dest, block_migration=block_migration, 
					migrate_data=migrate_data, migration=migration)					/* RPC调用,迁移请求发送到迁移源主机的nova-compute服务 */
					nova.computer.manager.ComputeManager:live_migration				/* 执行nova-computer服务本地迁移 */
  • 至此,流程走到了本地的nova-compute服务的迁移接口。关于nova-api服务响应流程的细节,可以通过控制节点的日志/var/log/nova/nova-api.log分析。

Nova Compute -> Libvirt Daemon

  • nova-compute执行迁移的核心就是调用Libvirt接口发起迁移,流程如下:
nova.computer.manager.ComputeManager:live_migration
	self._live_migration_executor.submit(self._do_live_migration, context, dest, instance,
  		block_migration, migration, migrate_data)
  		nova.computer.manager.ComputeManager:_do_live_migration
  			nova.computer.manager.ComputeManager:_do_pre_live_migration_from_source     
  			/* 加载对应虚拟化驱动的迁移函数,driver是一个可以配置的驱动参数
  			   nova.conf的compute_driver可以设置,这里通常都是libvirt的driver,即nova-compute调用的是Libvirt接口实现迁移
  			 */
  			self.driver.live_migration		<=>	nova.virt.libvirt.driver.LibvirtDriver:live_migration
  				nova.virt.libvirt.driver.LibvirtDriver:_live_migration
  				nova.virt.libvirt.driver.LibvirtDriver:_live_migration_operation
  					/* 这里会调用Libvirt Python的库获取libvirt.virDomain对象,该对象就是一个虚拟机抽象
  					   包含了libvirt提供的迁移接口migrate 
  					  */
           			guest.migrate	<=>	libvirt.virDomain.migrate
  • 至此,调用路径到了libvirt,关于nova-compute服务响应流程的细节,可以通过计算节点的日志/var/log/nova/nova-compute.log分析
  • 这里再稍微分析下libvirt的迁移接口。libvirt项目C/S架构,对于服务端,就是我们熟悉的libvirtd守护进程,它完成虚机管理的所有接口实现,对于客户端,除提供virsh工具外,也提供java,python的库接口。openstack使用libvirt-python接口进行虚机管理。对于客户端到服务端流程,python接口和virsh命令类似,因此直接从virsh工具的migrate命令分析内存迁移。
    virsh migrate $VMNAM --live --p2p --undefinesource --persistent --auto-converge --persistent-xml /path/to/dst_persistent.xml --migrateuri tcp://$MIGRATE_NET_IP qemu+tcp://$DST_IP/system
客户端:
/* virsh 工具命令入口 */
cmdMigrate                        
    doMigrate
        if (flags & VIR_MIGRATE_PEER2PEER) {
            /* 如果指定了p2p参数,使用该接口,从服务端被调用的接口分析
               Openstack指定了p2p选项,因此走的是virDomainMigrateToURI3函数
             */
            virDomainMigrateToURI3(dom, desturi, params, nparams, flags)     
        }
            virDomainMigrateUnmanagedParams
                if (flags & VIR_MIGRATE_PEER2PEER) {
                    domain->conn->driver->domainMigratePerform3Params
                        (domain, dconnuri, params, nparams,
                        NULL, 0, NULL, NULL, flags);
                }
remote服务端接口:
/* libvirt实现中,client和具体的虚拟化driver接口之间
   有一层remote driver,用作适配不同的虚拟化driver,
   因此这里首先连接的是remote driver的domainMigratePerform3Params
*/
remoteDispatchDomainMigratePerform3ParamsHelper
    remoteDispatchDomainMigratePerform3Params
        virDomainMigratePerform3Params
            conn->driver->domainMigratePerform3Params(
                domain, dconnuri, params, nparams, cookiein, cookieinlen,
                cookieout, cookieoutlen, flags);
qemu服务端接口:
/* 这里真正的调用qemu实现的driver接口
   对于lxc或者xen,相应的调用他们对应的接口 
*/     
qemuDomainMigratePerform3Params
    qemuMigrationSrcPerform
        if (flags & VIR_MIGRATE_PEER2PEER) {
            qemuMigrationSrcPerformJob
        }
            qemuMigrationSrcPerformPeer2Peer
                /* 这里,libvirtd服务连接目的端libvirtd
                   这是Peer2Peer与普通迁移在控制方式上的本质区别
                   普通迁移,会在virsh、libvirt-java、libvirt-python
                   等客户端流程中去连接目的端
                 */
                dconn = virConnectOpenAuth(dconnuri, &virConnectAuthConfig, 0);
                qemuMigrationSrcPerformPeer2Peer3
                    /* 源端开始发起迁移 */
                    qemuMigrationSrcBeginPhase
                    /* 调用目的端libvirtd的qemu Driver Prepare接口,让目的端准备迁移 */
                    dconn->driver->domainMigratePrepare3Params
                        (dconn, params, nparams, cookiein, cookieinlen,
                        &cookieout, &cookieoutlen, &uri_out, destflags);
                        <=>   qemuDomainMigratePrepare3Params
                    /* 源端开始执行迁移 */
                    qemuMigrationSrcPerformNative
                    /* 调用目的端libvirtd的qemu Driver Finish接口 */
                    dconn->driver->domainMigrateFinish3Params
                        (dconn, params, nparams, cookiein, cookieinlen,
                         &cookieout, &cookieoutlen, destflags, cancelled);
                          <=>   qemuDomainMigrateFinish3Params
                    /* 源端确认迁移完成 */
                    qemuMigrationSrcConfirmPhase
整个迁移过程,源码中解释如下:
/*
 * Sequence v3:
 *
 *  Src: Begin
 *        - Generate XML to pass to dst
 *        - Generate optional cookie to pass to dst
 *
 *  Dst: Prepare
 *        - Get ready to accept incoming VM
 *        - Generate optional cookie to pass to src
 *
 *  Src: Perform
 *        - Start migration and wait for send completion
 *        - Generate optional cookie to pass to dst
 *
 *  Dst: Finish
 *        - Wait for recv completion and check status
 *        - Kill off VM if failed, resume if success
 *        - Generate optional cookie to pass to src
 *
 *  Src: Confirm
 *        - Kill off VM if success, resume if failed
 *
  * If useParams is true, params and nparams contain migration parameters and
  * we know it's safe to call the API which supports extensible parameters.
  * Otherwise, we have to use xmlin, dname, uri, and bandwidth and pass them
  * to the old-style APIs.
 */	

流程图

在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

享乐主

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值