问题

openstack 中 nova 对于虚拟机的操作,有非常多种,在此不一一列举。普通的操作如 start,stop,suspend,resume 是很好理解的。然而像 shelve,unshelve 就比较新颖了。
而且从官方文档中也看不出来是干嘛的,官方文档是这样说的:

Shelving is useful if you have an instance that you are not using, but would like retain in your list of servers. For example, you can stop an instance at the end of a work week, and resume work again at the start of the next week. All associated data and resources are kept; however, anything still in memory is not retained. If a shelved instance is no longer needed, it can also be entirely removed.1

这只是说明了 shelve 命令的使用场合,但是从这里无法看出它跟 stop,pause 或者 suspend 的差异。看了之后让人一头雾水,而且显然我不是唯一觉得不明白的,有人在 stackoverflow 上也问起这个问题2

shelve_instance

既然如此,与其寻找帮助,不如自己翻代码。在 nova/compute/manager.py 中找到 ComputeManager 的方法 shelve_instance。可以看到其中有意义的操作基本上只有这么几行,剩下的是更新虚拟机状态之类:

class ComputeManager(manager.Manager):
    def shelve_instance(self, context, instance, image_id,
                        clean_shutdown):
        ... ...
        self._power_off_instance(context, instance, clean_shutdown)
        self.driver.snapshot(context, instance, image_id, update_task_state)
        ... ...
        if CONF.shelved_offload_time == 0:
            self.shelve_offload_instance(context, instance,
                                         clean_shutdown=False)

可以看到,shelve 虚拟机只是做了三件事情:

  • 关闭虚拟机
  • 为虚拟机做快照
  • 根据配置选择性地进行 shelve_offload 操作

感觉似乎没有什么用,按照开头 openstack 文档所说的,周五离开办公室的时候关闭虚拟机,周一再来打开虚拟机,完全是一样的。 干嘛要再做个快照。也许是 shelve_offload 有什么神奇作用。
根据上面代码,我们知道 shelve 虚拟机的时候,会查看配置文件中 shelved_offload_time 的值,如果是 0,则立即对虚拟机执行 shelve_offload 操作。那么如果 shelved_offload_time 的配置不是 0 呢。

同样是 nova/compute/manager.py,在开头部分注册配置项目时,有这一句:

    cfg.IntOpt('shelved_offload_time',
               default=0,
               help='Time in seconds before a shelved instance is eligible '
                    'for removing from a host. -1 never offload, 0 offload '
                    'immediately when shelved'),

就是说,如果 shelved_offload_time 值为 -1 则永远不会对虚拟机执行 shelve_offload 操作;如果是非 0 或 -1 的值,则在相应值的秒之后执行 shelve_offload 操作。

shelve_offload_instance

查看一下 shelve_offload_instance 到底做了什么事情:

    def shelve_offload_instance(self, context, instance, clean_shutdown):
        ... ...
        self._power_off_instance(context, instance, clean_shutdown)
        self.network_api.cleanup_instance_network_on_host(context, instance,
                                                          instance.host)
        self.driver.destroy(context, instance, network_info,
                block_device_info)
  • 首先还是 power_off_time,可能是确认一下,power_off 会先尝试优雅地关闭虚拟机,不行再强行断电,即(1) virsh shutdown <instance> (2) virsh destroy <instance>;
  • 接着,这个可能是比较有意义的事情,清理虚拟机在计算节点上的网络资源。
    注意:这一步在 juno 版本时还没有,所以 juno 版本的 shelve 更为单薄。
  • 销毁虚拟机,destroy 操作中,(1)强制关闭虚拟机并断开可能的远程磁盘,(2)删除虚拟机文件路径(/var/lib/nova/instances/xxxxx),(3) virsh undefine <instance>

纵观这几个步骤,其实只比关闭虚拟机,多了点资源清理的工作,如果是在 juno 之后的话,清理网络资源并断开磁盘连接,还是有一些意义的。但在 juno 之前,基本上只有断开磁盘连接跟删除虚拟机目录。虚拟机目录算不上什么可贵的资源吧。

unshelve_instance

了解一下 unshelve_instance 的内容吧。

   def _unshelve_instance(self, context, instance, image, filter_properties,
                           node):
       ... ...
       block_device_info = self._prep_block_device(context, instance, bdms,
                                                    do_check_attach=False)
       ... ...
       self.network_api.migrate_instance_finish(context, instance,
            {'source_compute': '', 'dest_compute': self.host})
       ... ...
       self.driver.spawn(context, instance, image, injected_files=[],
                                  admin_password=None,
                                  network_info=network_info,
                                  block_device_info=block_device_info)
       ... ...
       if image:
            instance.image_ref = shelved_image_ref
            self.image_api.delete(context, image['id'])

概括来说,就是重新启动虚拟机并删除 shelve_instance 时创建的镜像(snapshot)。
这一部分的流程,与新建虚拟机的流程相似,由于在 shelve_offload_instance 时将一些虚拟机附属资源清理了,现在需要重新准备。正如 shelve_offload_instance 方法的注释中所说的,单纯 shelve 过而没有 shelve_offload 的虚拟机,执行 unshelve 会比较快:

This frees up those resources for use by other instances, but may lead
to slower unshelve times for this instance.

注意: 为虚拟机创建快照,即是基于虚拟机创建一个 glance 镜像。

基于卷或是基于镜像

shelve 对于基于卷和基于镜像启动的虚拟机,行为是不同的。
基于卷的时候,当执行 shelve 操作的时候,请求直接由 shelve-offload 来执行,所以不会有创建快照的动作。

这个可以在 nova/compute/api.py 中 API 的 shelve 方法下看到:

        if not self.is_volume_backed_instance(context, instance):
            name = '%s-shelved' % instance['display_name']
            image_meta = self._create_image(context, instance, name,
                    'snapshot')
            image_id = image_meta['id']
            self.compute_rpcapi.shelve_instance(context, instance=instance,
                    image_id=image_id)
        else:
            self.compute_rpcapi.shelve_offload_instance(context,
                    instance=instance)

结论

纵上,基于镜像启动的虚拟机:

shelve = stop + snapshot [+ shelve-offload]
shelve-offload = stop + destroy

基于卷启动的虚拟机

shelve = shelve-offload

由于 shelve 最后调用了 destroy,其用意是比较明确的,destroy 是删除虚拟机的一个流程,shelve 也算是半个删除虚拟机吧,数据库记录与磁盘等留下,其它一些资源回收回来。