经过2周的实现终于实现了KVM虚拟机动态迁移,实现一个服务器下的虚拟机迁移到另一台服务器上
以下是项目的主要的实现方法,步骤大致如下:
1.检测base池的存在,也就是虚拟机模板镜像是否存在;
2.先创建空的磁盘文件;
3.创建虚拟机filter;
4.虚拟机启动状态检测;
5.最后调用libvirt函数实现在线迁移;
6.当然还有最后的清理操作,包括:源虚拟机的删除、磁盘文件的删除、数据库的改变等操作;
# 执行迁移操作的service
def excuteVmMigrateService(host_id, vname, moveNodeId,vmType):
log_debug.debug('excuteVmMigrateService begin!')
result = 'error'
try:
compute = Compute.objects.get(id=host_id)
conn = conn_wvmInstance(compute, vname)
status = conn.get_status()
except:
log_debug.debug('no host or inst')
return result
try:
# 连接数据库,根据节点id获取虚拟机对象
instance = Instance.objects.get(node_id=host_id, vm_name=vname)
except:
log_debug.debug('conn vm sql error')
return result
# ----------------1.获取虚拟机xml配置文件、磁盘配置路径以及检测base池 开始--------------
try:
log_debug.debug('get vm xml,path info start')
inst_xml = conn._XMLDesc(VIR_DOMAIN_XML_SECURE)
# 获取指定节点的配置项
jsonPath= json.loads(getMigratePathByVmType(vmType))
add_file_path = jsonPath['addFilePath']
volumPath = jsonPath['volumPath']
basePath = jsonPath['basePath']
origpath = jsonPath['origpath']
basePathVm = instance.baseimg
# 从数据库中得到虚拟机base镜像名称
image_name = basePathVm.split('/')[-1]
log_debug.debug('vm basePath=%s,image_name=%s' % (basePathVm,image_name))
if image_name == 'NONE' or image_name == '':
if vmType == '2':
image_name = sys_config['df_teaimg']
elif vmType == '3':
image_name = sys_config['df_serverimg']
# 检测迁移的目标节点base池是否存在镜像,不存在则从当前模板镜像目录进行拷贝
if basePath:
is_base_exist(moveNodeId, origpath, basePath, image_name)
moveNode = Compute.objects.get(id=moveNodeId)
moveIp = moveNode.hostname
virconn = connection_manager.get_connection(moveNode.hostname, moveNode.login, moveNode.password, moveNode.type)
except Exception, ex: # 所有异常
print Exception, ":", ex
log_debug.debug('migrated domain happen exception!')
result = 'exception'
return result
# ----------------获取虚拟机磁盘配置路径以及检测base池 结束--------------
# ----------------2.获取虚拟机磁盘信息开始--------------
try:
log_debug.debug('get vm disk info start')
disk_devices = conn.get_disk_device() # 取得虚拟机的磁盘信息
disk_json = [] # 用来存放base以及增量文件,一一对应
for disk_device in disk_devices:
disk_add_name = disk_device['image'].split('/')[-1]
if '-' in disk_device['image']:
disk_base_name = (disk_device['image']).split('-', 1)[-1]
elif '_' in disk_device['image']:
disk_base_name = (disk_device['image']).split('_', 3)[-1]
disk_json.append({'disk_add_name': disk_add_name, 'disk_base_name': disk_base_name})
log_debug.debug('disk_json=%s' % disk_json)
disk_amount = len(disk_devices) # 磁盘数量
log_debug.debug('vm disk_amount=%s' % disk_amount)
except:
log_debug.debug('get vm disk info except!')
result = 'exception'
return result
# ----------------获取虚拟机磁盘信息 结束--------------
# --------------------3.创建虚拟机磁盘 开始---------------------------
try:
log_debug.debug('create vm disk start')
if disk_amount == 1:
log_debug.debug('------------------------ disk amout == 1')
# 得到c盘base名称以及增量文件名称
c_add_name = disk_json[0]['disk_add_name']
c_base_name = disk_json[0]['disk_base_name']
rel = create_volum_by_qemu_img(moveNode.hostname, basePath, c_base_name, add_file_path, c_add_name)
if rel != 'success':
result = 'error'
return result
else:
log_debug.debug('------------------------- disk amout > 1')
# 得到c盘base名称以及增量文件名称
c_add_name = disk_json[0]['disk_add_name']
c_base_name = disk_json[0]['disk_base_name']
# 创建c盘
rel = create_volum_by_qemu_img(moveNode.hostname, basePath, c_base_name, add_file_path, c_add_name)
if rel == 'success':
disk_json.pop(0) # 过滤c盘
for ex_json in disk_json:
disk_base_ex = ex_json['disk_base_name']
add_name_ex = ex_json['disk_add_name']
# 创建扩展盘,包含d盘在内
rel_ex = create_volum_by_qemu_img(moveNode.hostname, volumPath, disk_base_ex, volumPath, add_name_ex)
refresh_pool(moveNode)
if rel_ex == 'success':
continue
else:
result = 'error'
return result
else:
result = 'error'
return result
except:
log_debug.debug('create vm disk except!')
result = 'exception'
return result
# ----------------------------创建磁盘 结束-----------------------
# ----------------4.定义虚拟机的fileter 开始------------------
try:
log_debug.debug('define vm fileter start')
filter_uuid = virconn.nwfilterLookupByName('%s_filter' % vname).UUIDString()
except:
log_debug.debug('get filter_uuid happen except!')
filter_uuid = util.randomUUID()
single_nwfilter = getVm2AndVm3FilterXml(vname, filter_uuid)
try:
virconn.nwfilterDefineXML(single_nwfilter[1])
except:
log_debug.debug('create filter error!')
result = 'exception'
return result
# ----------------定义虚拟机的fileter 结束------------------
# ----------------5.判断虚拟机启动状态 开始-------------------
try:
log_debug.debug('check vm status=%s' % status)
# 迁移要求虚拟机是启动状态,否则报错
if status:
if status == 5:
conn.start()
elif status == 3:
conn.resume()
# return HttpResponse('success')
else:
log_debug.debug('start vm fail')
result = 'startFail'
return result
except:
log_debug.debug('check vm status except!')
result = 'exception'
return result
# ----------------判断虚拟机启动状态 结束-------------------
# ----------------6.最后执行虚拟机迁移操作 开始-----------------------
try:
log_debug.debug('excute migrate vm start')
#启动状态,执行迁移操作
source_conn = libvirt.open('qemu+tcp://%s/system' % conn.host)
dom = source_conn.lookupByName(vname)
dest_conn = libvirt.open('qemu+tcp://%s/system' % moveIp)
flg = 'tcp://%s' % moveIp
new_dom = dom.migrate2(dest_conn, inst_xml, libvirt.VIR_MIGRATE_PERSIST_DEST | libvirt.VIR_MIGRATE_LIVE | libvirt.VIR_MIGRATE_NON_SHARED_INC, None, flg, 0)
refresh_pool(moveNode)
log_debug.debug('refresh pool success')
# 注:迁移成功后自动在/etc/libvirt/qemu/目录下生成xml配置文件
if new_dom != None:
dest_conn.close()
conn.close()
log_debug.debug('migrated vm success')
result = 'success'
return result
else:
log_debug.debug('migrated domain error')
result = 'error'
return result
except Exception, ex: # 所有异常
print Exception, ":", ex
log_debug.debug('excute vm migrated exception!')
result = 'exception'
return result
# ----------------执行虚拟机迁移操作 结束-----------------------
return result
# 通过命令的方式创建磁盘增量文件
def create_volum_by_qemu_img(nodeIp,basePath,c_base_name,add_file_path,c_add_name):
result = 'success'
cmd = "ssh %s 'qemu-img create -f qcow2 -b %s/%s %s/%s'" % (nodeIp, basePath, c_base_name, add_file_path, c_add_name)
log_debug.debug('create qemu-img shell cmd=%s' % cmd)
try:
subprocess.call(cmd, shell=True)
log_debug.debug('create qemu-img success')
return result
except:
log_debug.debug('create qemu-img error')
result = 'error'
return result
return result
# 迁移虚拟机确认的service,包括删除之前节点的虚拟机以及修改数据库
def migrateConfirmService(host_id, vname,moveNodeId):
log_debug.debug('migrateConfirmService begin')
result = 'error'
try:
compute = Compute.objects.get(id=host_id)
conn = conn_wvmInstance(compute, vname)
except:
log_debug.debug('no host or inst')
return result
try:
moveNode = Compute.objects.get(id=moveNodeId)
moveConn = conn_wvmInstance(moveNode, vname)
except:
log_debug.debug('no move_host or move_inst')
return result
try:
# 连接数据库,根据节点id获取虚拟机对象
instance = Instance.objects.get(node_id=host_id, vm_name=vname)
except:
log_debug.debug('conn vm sql error')
return result
if conn.get_status() == 1:
conn.force_shutdown()
try:
# 删除磁盘,包含C盘以及扩展盘
conn.delete_disk()
# 迁移成功,修改数据库的虚拟机所在的节点
instance.node_id = moveNodeId
# 修改端口 以及虚拟机的运行状态
port = moveConn.get_console_port()
log_debug.debug('vm new port=%s' % port)
instance.vm_port = port
instance.vm_status = "running"
instance.save()
# 删除2类型以及3类型自定义的filter
if instance.vm_type == '2' or instance.vm_type == '3':
virconn = connection_manager.get_connection(compute.hostname, compute.login,compute.password,compute.type)
try:
virconn.nwfilterLookupByName('%s_filter' % vname).undefine()
except:
log_debug.debug("undeine %s_filter faild" % vname)
# 最后删除虚拟机
conn.delete()
result = 'success'
log_debug.debug('delete vm success')
except Exception, ex:
print Exception, ":", ex
log_debug.debug('delete migrate vm fail')
result = 'exception'
return result
总结:1.虚拟机迁移要求磁盘文件为增量文件的方式,也就是说创建磁盘的方式要是增量文件的方式,通过克隆的方式进过测试不支持迁移;
2.迁移之前要求虚拟机没有创建快照并且虚拟机是启动的状态,否则迁移失败;
3.迁移的过程实际上是磁盘文件的拷贝过程,因此从源节点到目的节点上迁移之前中,源节点要先创建虚拟机空的磁盘文件,在进行迁移操作;
4.迁移完成之后虚拟机的状态为启动状态并且端口号也发生的变化;