迁移学习心得,OpenStack

本文深入探讨了OpenStack中的迁移机制,包括migration和live migration两种类型,并详细分析了它们的实现方式及区别。通过代码示例说明了OpenStack如何利用libvirt和QEMU完成实例迁移。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近一直在看有关OpenStack里迁移方面的情况,也顺便往下看了下QEMU里关于migration的实现。在这里分享一下自己学习的体会。
我看的OpenStack为g版本,不久将至的h版本在migration方面应该没有太大的改变。(至少没有从f到g来的大迁移学习心得,从OpenStack到QEMU
OpenStack关于migration可以分为两类,migration和livemigration,migration进入dashboard的管理员模式就使用了,livemigration因为dashboard还没加进这个功能,所以需要敲命令行实现(nova live-migrationinstance_ID)。
migration和livemigration无论在功能上还是实现上有很大的不同。migration可以说完全是openstack一手操控的,而livemigration实现大部分依赖了libvirt以及qemu。
我们先来看看migration的代码:

\nova\compute\manager.py
   @exception.wrap_exception(notifier=notifier,publisher_id=publisher_id())
   @reverts_task_state
   @wrap_instance_event
   @wrap_instance_fault
    defresize_instance(self, context, instance, image,
                    reservations=None, migration=None, migration_id=None,
                    instance_type=None):
       """Starts the migration of a running instance toanother host."""
       if not migration:
           migration= self.conductor_api.migration_get(context, migration_id)
       withself._error_out_instance_on_exception(context,instance['uuid'],
                                           reservations):
           if notinstance_type:
              instance_type =self.conductor_api.instance_type_get(context,
                    migration['new_instance_type_id'])

          network_info = self._get_instance_nw_info(context, instance)

           migration= self.conductor_api.migration_update(context,
                 migration, 'migrating')

           instance =self._instance_update(context, instance['uuid'],
                 task_state=task_states.RESIZE_MIGRATING,
                expected_task_state=task_states.RESIZE_PREP)

          self._notify_about_instance_usage(
              context, instance,"resize.start", network_info=network_info)

          block_device_info =self._get_instance_volume_block_device_info(
                           context, instance)

           disk_info= self.driver.migrate_disk_and_power_off(
                 context, instance, migration['dest_host'],
                 instance_type, network_info,
                 block_device_info)

          self._terminate_volume_connections(context, instance)

          self.conductor_api.network_migrate_instance_start(context,
                                                    instance,
                                                    migration)

           migration= self.conductor_api.migration_update(context,
                 migration, 'post-migrating')

           instance =self._instance_update(context, instance['uuid'],
                 host=migration['dest_compute'],
                 node=migration['dest_node'],
                 task_state=task_states.RESIZE_MIGRATED,
                 expected_task_state=task_states.
                 RESIZE_MIGRATING)

          self.compute_rpcapi.finish_resize(context, instance,
                 migration, image, disk_info,
                 migration['dest_compute'], reservations)

          self._notify_about_instance_usage(context, instance,"resize.end",
                                      network_info=network_info)

非常明显,OpenStack中是通过resize这个命令来实现migration的,那很容易想到migration的实现过程和resize就非常相近了:先shutdown,然后(换个flavor)换个host,接着配置个网络,更新个信息,最后重新启动instance。
然后我们看看OpenStack怎么通过libvirt实现migration的。
\nova\virt\libvirt\driver.py
    defmigrate_disk_and_power_off(self, context, instance, dest,
                             instance_type, network_info,
                             block_device_info=None):
       LOG.debug(_("Startingmigrate_disk_and_power_off"),
                instance=instance)
       disk_info_text =self.get_instance_disk_info(instance['name'],
             block_device_info=block_device_info)
       disk_info =jsonutils.loads(disk_info_text)

       # copy disks to destination
       # rename instance dir to +_resize at first forusing
       # shared storage for instance dir (eg.NFS).
       inst_base =libvirt_utils.get_instance_path(instance)
       inst_base_resize = inst_base + "_resize"
       shared_storage =self._is_storage_shared_with(dest, inst_base)

       # try to create the directory on the remotecompute node
       # if this fails we pass the exception up thestack so we can catch
       # failures here earlier
       if not shared_storage:
          utils.execute('ssh', dest, 'mkdir', '-p', inst_base)

       self.power_off(instance)

       block_device_mapping =driver.block_device_info_get_mapping(
          block_device_info)
       for vol in block_device_mapping:
          connection_info = vol['connection_info']
           disk_dev =vol['mount_device'].rpartition("/")[2]
          self.volume_driver_method('disconnect_volume',
                                connection_info,
                                disk_dev)

       try:
          utils.execute('mv', inst_base, inst_base_resize)
           # if weare migrating the instance with shared storage then
           # createthe directory.  If it is a remote node thedirectory
           # hasalready been created
           ifshared_storage:
              dest = None
              utils.execute('mkdir', '-p',inst_base)
           for infoin disk_info:
              # assume inst_base ==dirname(info['path'])
              img_path = info['path']
              fname =os.path.basename(img_path)
              from_path =os.path.join(inst_base_resize, fname)
              if info['type'] == 'qcow2'and info['backing_file']:
                 tmp_path = from_path + "_rbase"
                 # merge backing file
                 utils.execute('qemu-img', 'convert', '-f','qcow2',
                             '-O', 'qcow2', from_path,tmp_path)

                 if shared_storage:
                    utils.execute('mv', tmp_path, img_path)
                 else:
                    libvirt_utils.copy_image(tmp_path, img_path, host=dest)
                    utils.execute('rm', '-f', tmp_path)

              else:  # rawor qcow2 with no backing file
                 libvirt_utils.copy_image(from_path, img_path,host=dest)
       except Exception:
           withexcutils.save_and_reraise_exception():
             self._cleanup_remote_migration(dest, inst_base,
                                       inst_base_resize,
                                       shared_storage)

       return disk_info_text

可以看到migration的操作就是poweroff该instance,然后建立ssh链接,将image从源主机拷贝到目的主机。是否为共享存储的区别不过在于是否真的将image“拷贝”还是仅仅改变一个地址。
所以migration在openstack里面的操作还是简单的,相比之下livemigration操作要复杂得多。
livemigration很大的一个变化是g版本把这个操作从nova-compute挪到了nova-conductor里了。废话不多说,上代码。

\nova\compute\api.py

@check_instance_state(vm_state=[vm_states.ACTIVE])
def live_migrate(self, context, instance,block_migration,
disk_over_commit, host_name):
"""Migrate a server lively to a new host."""
LOG.debug(_("Going to try to live migrate instance to%s"),
 host_name or "another host",instance=instance)

instance = self.update(context, instance,
  task_state=task_states.MIGRATING,
  expected_task_state=None)

self.compute_task_api.migrate_server(context, instance,
scheduler_hint={'host': host_name},
live=True, rebuild=False, flavor=None,
block_migration=block_migration,
disk_over_commit=disk_over_commit)

 这里的compute_task_api就是指conductor,我们接着看conductor。

\nova\conductor\manager.py
def migrate_server(self, context, instance, scheduler_hint,live, rebuild,
 flavor, block_migration,disk_over_commit):
if not live or rebuild or (flavor != None):
raise NotImplementedError()

destination = scheduler_hint.get("host")
self.scheduler_rpcapi.live_migration(context,block_migration,
disk_over_commit, instance, destination)

好吧,conductor把皮球踢给了scheduler,在这里限于篇幅,我们简单看一下scheduler里面做的是什么。
try:
self._schedule_live_migration(context, instance, dest,
block_migration, disk_over_commit)
except (exception.NoValidHost,
exception.ComputeServiceUnavailable,
exception.InvalidHypervisorType,
exception.UnableToMigrateToSelf,
exception.DestinationHypervisorTooOld,
exception.InvalidLocalStorage,
exception.InvalidSharedStorage,
exception.MigrationPreCheckError) as ex:
request_spec = {'instance_properties': {
'uuid': instance['uuid'], },
}
scheduler无非检查一下destination的情况,storage的情况等,然后接着把皮球踢还conductor。
\conductor\tasks\live_migrate.py
class LiveMigrationTask(object):
def __init__(self, context, instance, destination,
block_migration, disk_over_commit,
select_hosts_callback):
self.context = context
self.instance = instance
self.destination = destination
self.block_migration = block_migration
self.disk_over_commit = disk_over_commit
self.select_hosts_callback = select_hosts_callback
self.source = instance['host']
self.migrate_data = None
self.compute_rpcapi = compute_rpcapi.ComputeAPI()
self.servicegroup_api = servicegroup.API()
self.image_service = glance.get_default_image_service()

def execute(self):
self._check_instance_is_running()
self._check_host_is_up(self.source)

if not self.destination:
self.destination = self._find_destination()
else:
self._check_requested_destination()

#TODO(johngarbutt) need to move complexity out of computemanager
return self.compute_rpcapi.live_migration(self.context,
host=self.source,
instance=self.instance,
dest=self.destination,
block_migration=self.block_migration,
migrate_data=self.migrate_data)

终于干正事了,conductor通过MQ把livemigration的消息告诉了源主机的nova-compute。咱们开始迁移吧!老规矩,还是让libvirt来干活~
\nova\virt\libvirt\driver.py
def live_migration(self, context, instance, dest,
  post_method, recover_method,block_migration=False,
  migrate_data=None):
"""Spawning live_migration operation for distributinghigh-load.

:params context: security context
:params instance:
nova.db.sqlalchemy.models.Instance object
instance object that is migrated.
:params dest: destination host
:params block_migration: destination host
:params post_method:
post operation method.
expected nova.compute.manager.post_live_migration.
:params recover_method:
recovery method when any exception occurs.
expected nova.compute.manager.recover_live_migration.
:params block_migration: if true, do block migration.
:params migrate_data: implementation specific params

"""

greenthread.spawn(self._live_migration, context, instance,dest,
 post_method, recover_method,block_migration,
 migrate_data)

def _live_migration(self, context, instance, dest,post_method,
recover_method, block_migration=False,
migrate_data=None):
"""Do live migration.

:params context: security context
:params instance:
nova.db.sqlalchemy.models.Instance object
instance object that is migrated.
:params dest: destination host
:params post_method:
post operation method.
expected nova.compute.manager.post_live_migration.
:params recover_method:
recovery method when any exception occurs.
expected nova.compute.manager.recover_live_migration.
:params migrate_data: implementation specific params
"""

# Do live migration.
try:
if block_migration:
flaglist = CONF.block_migration_flag.split(',')
else:
flaglist = CONF.live_migration_flag.split(',')
flagvals = [getattr(libvirt, x.strip()) for x inflaglist]
logical_sum = reduce(lambda x, y: x | y, flagvals)

dom = self._lookup_by_name(instance["name"])
dom.migrateToURI(CONF.live_migration_uri % dest,
logical_sum,
None,
CONF.live_migration_bandwidth)

except Exception as e:
with excutils.save_and_reraise_exception():
LOG.error(_("Live Migration failure: %s"), e,
 instance=instance)
recover_method(context, instance, dest, block_migration)

# Waiting for completion of live_migration.
timer = loopingcall.FixedIntervalLoopingCall(f=None)

接着就要调用libvirt以及他下面的QEMU了,也就是dom.migrateToURI()这个函数。
如果玩过类似virtmanager的东西,对于这个命令里的参数应该不会陌生。事实上,OpenStack里面的live-migration和在virtmanager里做migration没什么本质的区别。
到这里总结一下,OpenStack首先在qemu上面找了个libvirt这样的函数库,封装他的API。毕竟OpenStack除了KVM外,还要支持Xen,ESX这样的虚拟化工具。然后相比于单机上的虚拟化,作为云平台的管理工具,OpenStack当然要负责完成把instance的信息在数据库里更新(也就是conductor的任务)、搭建起网络、选择一个目的主机等一系列工序。至于是不是sharedstorage,其实OpenStack不会太关注,因为libvirt或者更准确的说,QEMU完全能自己搞定这件事了。OpenStack通知他一声就成~
另外,值得一提的是,OpenStack默认是不支持live-migration的,因为OpenStack认为live-migration会因为内存中dirtypagerate大于网络的brandwidth而不能停止(详细解释请关注之后QEMU的部分)。因此如果不进行相关设置的话,OpenStack默认会用migration代替(live-migration是不是有种需要打开隐藏关卡才能看到的赶脚)。
总体感觉OpenStack在livemigration方面做得还是挺不错的,我在实践中也没发现什么问题。毕竟OpenStack也就是个管理工具,livemigration活怎么样(downtime多少,总迁移时间多少,性能下降多少等),和他也没什么直接关系。
反正写到这里OpenStack方面关于迁移的事情我觉得交代的差不多了
一、Virtual Box的安装 1、安装Virtual Box 2、 Virtual Box网络设定 3、 安装操作系统 二、 环境预配置 1、 网路设置 2、 分别修改三个虚拟机的主机名 3、 主机地址映射配置 4、 禁用selinux 5、 CentOS6本地yum源配置 注:先加载镜像 6、 NTP安装服务 三、 本地源制作 1. Centos6.6本地base、extra源制作 2. 本地base、extra源制作 3. 本地epel、openstack源制作 4. ftp服务安装 5. 修改yum源仓库指向文件 四、 keystone安装(上) 1. 安装Mysql服务 2. 安装rabbitmq消息队列 3. 为nova,neutron,cinder.heat创建用户并授权 五、 keystone安装(下) 1. 创建库和授权 2. 创建库和授权 3. 生成PKI认证所需要的证书文件 4. 同步keystone数据库,生成keystone所需的表 5. 启动keystone服务和校验服务状态 6. 创cron任务,配置定期清理过期的token 7. keystone创建user,tenant,role和endpoint 六、 Glance安装 1. Glance的安装 2. 配置glance-api服务 3. 配置glance-registry服务 4. 启动并校验glance服务 57 七、 Nova安装 59 1. nova的安装与配置 59 2. 安装和配置nova 60 八、 Neutron安装 63 1. neutron的安装与配置 63 2.Neutron使用二层组件 66 3. 配置OVS二层插件 67 4. 配置nova支持neutron 67 5. 启动neutron-server服务 68 6. 重启nova服务和neutron联动 69 7. 重启neutron-server 69 8. controller0上校验neutron的配置 69 九、 Horizon安装 71 1. Horizon组件的安装与配置 71 十、 Compute0安装nova 74 1. nova的安装与配置(compute0-10.20.0.30) 74 2. nova的安装与配置 75 十一、 Compute0安装neutron 78 1. neutron的安装与配置(compute0) 78 十二、 Network0安装neutron 83 1. neutron的安装与配置 83 十三、 新建网络 91 1. 配置安全组规则 91 2. 新建网络 92 3 .创建云主机 99 4 .分配浮动ip 101 十四、 心得体会 107
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值