nova热快照关键流程解析

来源:互联网 发布:乡镇 网络舆情监控 编辑:程序博客网 时间:2024/05/04 00:15
Nova snapshot 目前只提供一种创造系统盘镜像的方法,不支持内存快照,不支持回滚至快照点,只能采用该快照镜像创建一个新的虚拟机。
也就是对当前的系统盘做一个快照生成 qcow2 格式的文件,然后上传到 glance 里面,其作用也往往是方便使用快照生成的镜像来部署新的虚机。
而Live Snapshot 则允许用户在虚机处于运行状态时不停机做快照。

下面我们通过pdb调试工具,剖析一下nova创建虚机热快照的具体过程:
工欲善其事必先利其器,在阅读源码的过程中如果能够有效的结合gdb/pdb等调试工具, 能够让你的学习事倍功半。
  1. 首先通过命令行创建一个虚机
# nova boot zhuozh-2 --image bcec-bclinux-7.1-amd64-server-10G-2015.08.15 --flavor 2 --nic net-id=00ea69c0-fa14-4311-be4d-bf07a73b3568 --availability-zone nova:node-35.domain.bcec
# nova show zhuozh-2并确认虚机正常启动,处于ACTIVE状态
...
| OS-EXT-SRV-ATTR:instance_name        | instance-000084ea
| OS-EXT-STS:vm_state                  | active
...
在计算节点上查看虚机的ID号:
# virsh list
 Id    Name                           State
----------------------------------------------------
 7     instance-000084ea              running

在计算节点上查看盘,有两块盘,一块系统盘,一块CDROM
# virsh domblklist 7
Target     Source
------------------------------------------------
vda        /var/lib/nova/instances/50aa05e6-04e8-4807-9c9f-59d477b66b0c/disk
vdz        /var/lib/nova/instances/50aa05e6-04e8-4807-9c9f-59d477b66b0c/disk.config

      
  1. 首先停掉计算节点的nova-compute服务:
          # service openstack-nova-compute stop

          在调试之前,在快照创建的必经之路上上加入调试断点:
    import pdb
    pdb.set_trace()
         如下:
# vim /usr/lib/python2.6/site-packages/nova/virt/libvirt/driver.py
    def snapshot(self, context, instance, image_href, update_task_state):
        """Create snapshot from a running VM instance.
        This command only works with qemu 0.14+
        """
        import pdb
        pdb.set_trace()

     手动启动nova-compute服务:
     # usr/bin/nova-compute --config-file=/etc/nova/nova.conf --log-file=/var/log/nova/compute.log
     这样创建快照的时候,就会在 snapshot()入口函数这里停住,接下来,可以像gdb一样进行调试(l查看源码,s下一句,n下一函数, p打印)
     
  1. 接下来利用nova image-create对系统盘创建快照,并添加--debug查看快照的创建过程:
# nova --debug image-create 2e096559-6187-45a8-b3d8-b787e3da2e2a "zhuozh-1-ss" --show
...
REQ: curl -i 'http://10.133.1.2:8774/v2/1f6364c9386b4cc687a659a466339cb8/servers/6efd608b-2ed0-477d-b770-7e05eb71fd04/action' -X POST -H "Accept: application/json" -H "Content-Type: application/json" -H "User-Agent: python-novaclient" -H "X-Auth-Project-Id: admin" -H "X-Auth-Token: {SHA1}9f718579f6e5045fbd533eea58e1f3d497fb183f" -d '{"createImage": {"name": "zhuozh-1-ss", "metadata": {}}}'
...
可以看到调用的API为/usr/lib/python2.7/dist-packages/nova/api/openstack/compute/servers.py中的
def _action_create_image(self, req, id, body)

nova调用栈大致如下(|-->):
# compute/servers.py
          def _action_create_image(self, req, id, body)
        #如果是volume则调用volume snapshot
        image = self.compute_api.snapshot_volume_backed(context, instance, image_meta, image_name,  extra_properties=props)
        #如果是普通的instance则进行普通的snapshot,这里我们是第二种
|--> image = self.compute_api.snapshot(context, instance, image_name, extra_properties=props)

 # nova/compute/api.py
def snapshot(self, context, instance, name, extra_properties=None) 
     |--> image_meta = self._create_image(context, instance, name,  'snapshot', extra_properties=extra_properties)     //在glance中创建一个image的记录
          |--> self.compute_rpcapi.snapshot_instance(context, instance, image_meta['id'])  //创建snapshot

# nova/compute/manager.py
def snapshot_instance(self, context, image_id, instance)
               |--> self._snapshot_instance(context, image_id, instance,  task_states.IMAGE_SNAPSHOT)

def _snapshot_instance(self, context, image_id, instance,  expected_task_state)    
          |--> self.driver.snapshot(context, instance, image_id, update_task_state)  //真正的snapshot就是从这里开始的

# nova/virt/libvirt/driver.py
 def snapshot(self, context, instance, image_href, update_task_state)
                         |--> virt_dom = self._lookup_by_name(instance['name'])  //获取libvirt的domain,与前面通过命令查到的虚机名吻合。
                              (Pdb) p instance['name']
                              'instance-000084ea'
                              (Pdb) p virt_dom
                              <libvirt.virDomain object at 0x37cd850>

                         |-->  (image_service, image_id) = glance.get_remote_image_service( context, instance['image_ref'])
                              (Pdb) p image_service
<nova.image.glance.GlanceImageService object at 0x3948d50>
(Pdb) p image_id
u'f77678a9-14eb-4677-956e-abc472dfa78f' //

                         |--> base = compute_utils.get_image_metadata( context, image_service, image_id, instance)
                              (Pdb) p base
{u'min_disk': 20, u'container_format': u'bare', u'min_ram': 0, u'disk_format': u'qcow2', 'properties': {u'instance_type_memory_mb': u'2048', u'instance_type_swap': u'0', u'instance_type_root_gb': u'20', u'instance_type_name': u'm1.small', u'instance_type_id': u'5', u'instance_type_ephemeral_gb': u'0', u'instance_type_rxtx_factor': u'1.0', u'network_allocated': u'True', u'instance_type_flavorid': u'2', u'instance_type_vcpus': u'1', u'base_image_ref': u'f77678a9-14eb-4677-956e-abc472dfa78f'}}

                         |--> snapshot = snapshot_image_service.show(context, snapshot_image_id)
                              (Pdb) p snapshot
{'status': u'queued', 'name': u'zhuozh-2-ss', 'deleted': False, 'container_format': u'bare', 'created_at': datetime.datetime(2016, 9, 9, 9, 18, 5, tzinfo=<iso8601.iso8601.Utc object at 0x3b2ef90>), 'disk_format': u'qcow2', 'updated_at': datetime.datetime(2016, 9, 9, 9, 18, 5, tzinfo=<iso8601.iso8601.Utc object at 0x3b2ef90>), 'id': u'f77f836b-c705-4ca5-80a6-4a18c8c5652a', 'owner': u'1f6364c9386b4cc687a659a466339cb8', 'min_ram': 0, 'checksum': None, 'min_disk': 20, 'is_public': False, 'deleted_at': None, 'properties': {u'instance_uuid': u'50aa05e6-04e8-4807-9c9f-59d477b66b0c', u'instance_type_memory_mb': u'2048', u'user_id': u'3db37facaa724cd89e96a2486382f9f0', u'image_type': u'snapshot', u'instance_type_id': u'5', u'instance_type_name': u'm1.small', u'instance_type_ephemeral_gb': u'0', u'instance_type_rxtx_factor': u'1.0', u'instance_type_root_gb': u'20', u'network_allocated': u'True', u'instance_type_flavorid': u'2', u'instance_type_vcpus': u'1', u'instance_type_swap': u'0', u'base_image_ref': u'f77678a9-14eb-4677-956e-abc472dfa78f'}, 'size': 0}
                       
                         |--> disk_path = libvirt_utils.find_disk(virt_dom)
                         |--> source_format = libvirt_utils.get_disk_type(disk_path)
                              (Pdb) p disk_path
'/var/lib/nova/instances/50aa05e6-04e8-4807-9c9f-59d477b66b0c/disk'
(Pdb) p source_format
'qcow2'

                         |--> metadata = self._create_snapshot_metadata(instance.image_meta,  instance,  image_format,  snapshot['name'])  //创建snapshot的metadata
(Pdb) p metadata
{'status': 'active', 'name': u'zhuozh-2-ss', 'container_format': u'bare', 'disk_format': 'qcow2', 'is_public': False, 'properties': {'kernel_id': u'', 'image_location': 'snapshot', 'image_state': 'available', 'ramdisk_id': u'', 'owner_id': u'1f6364c9386b4cc687a659a466339cb8'}}

     |--> snapshot_name = uuid.uuid4().hex     //为snapshot生成一个ID
                              (Pdb) p snapshot_name
'a04fb356eb2a423696d18d0ed01c7801'

               |--> if live_snapshot:
                           LOG.info(_("Beginning live snapshot process"),  instance=instance)
             update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD)
               打印log:Beginning live snapshot process,并更新任务状态为image_pending_upload

               |--> snapshot_directory = CONF.libvirt.snapshots_directory
                     (Pdb) p snapshot_directory
                    '/var/lib/nova/instances/snapshots'
                    该快照保存路径是通过/etc/nova/nova.conf定义的
                         
                         -->  out_path = os.path.join(tmpdir, snapshot_name)
                              (Pdb) p out_path
'/var/lib/nova/instances/snapshots/tmpDqNf6j/a04fb356eb2a423696d18d0ed01c7801'

                         |--> self._live_snapshot(virt_dom, disk_path, out_path, image_format) //live snapshot

def _live_snapshot(self, domain, disk_path, out_path, image_format)
                              |--> xml = domain.XMLDesc(0) //保存虚机的定义到xml文件,感兴趣可以打印出来看一下,xml内容与virsh dumpxml instance-000084ea一致
                                   (Pdb) p xml
                              |--> domain.blockJobAbort(disk_path, 0) //调用libvirt的virDomainBlockJobAbortcancel当前正在执行的blockjob,如果有的话。
                                   相当于执行 virsh blockjob instance-000084ea vda --abort

                     |--> src_disk_size = libvirt_utils.get_disk_size(disk_path)
         src_back_path = libvirt_utils.get_disk_backing_file(disk_path,  basename=False)
         disk_delta = out_path + '.delta'
         libvirt_utils.create_cow_image(src_back_path, disk_delta,  src_disk_size)     //根据源disk的backing_file,生成目标disk
(Pdb) p disk_path
'/var/lib/nova/instances/50aa05e6-04e8-4807-9c9f-59d477b66b0c/disk'
(Pdb) p src_disk_size
21474836480 
(Pdb) p src_back_path
'/var/lib/nova/instances/_base/92be8e0c782da5dbd229be23a6c0b98dd18fbff6'
(Pdb) p disk_delta
'/var/lib/nova/instances/snapshots/tmpDqNf6j/a04fb356eb2a423696d18d0ed01c7801.delta'
相当于执行:
          qemu-img info /var/lib/nova/instances/50aa05e6-04e8-4807-9c9f-59d477b66b0c/disk
qemu-img create -f qcow2 -o backing_file=/var/lib/nova/instances/_base/92be8e0c782da5dbd229be23a6c0b98dd18fbff6 ,size=21474836480 /var/lib/nova/instances/snapshots/tmpDqNf6j/a04fb356eb2a423696d18d0ed01c7801.delta
经过这一步,我们可以看到目标image已经在计算节点上生成:
# qemu-img info /var/lib/nova/instances/snapshots/tmpDqNf6j/a04fb356eb2a423696d18d0ed01c7801.delta
image: /var/lib/nova/instances/snapshots/tmpDqNf6j/a04fb356eb2a423696d18d0ed01c7801.delta
file format: qcow2
virtual size: 20G (21474836480 bytes)
disk size: 196K
cluster_size: 65536
backing file: /var/lib/nova/instances/_base/92be8e0c782da5dbd229be23a6c0b98dd18fbff6
Format specific information:
    compat: 1.1
    lazy refcounts: false

|--> if domain.isPersistent() 
               domain.undefine()  //调用接口virDomainUndefine,将虚机undefine,undefine的虚机状态从persistent变成transient;
 相当于执行了virsh undefine instance-000084ea


什么是transient和persistent?请参考libvirt wiki章节:http://wiki.libvirt.org/page/VM_lifecycle#Transient_guest_domains_vs_Persistent_guest_domains
简言之:
- Transient状态的虚机,关机或者重启的时候虚机就消失了,是临时性的虚机。
- Persistent状态的虚机,即使关机或者重启,虚机仍然存在,是永久性的虚机。
至于为什么要通过undefine将虚机状态从persistent变成transient,请参考以下一段E文:
[*] Reasoning for making the domain transient: BlockRebase ('blockcopy')
jobs last forever until canceled, which implies that they should last
across domain restarts if the domain were persistent. But, QEMU doesn't
yet provide a way to restart a copy job on domain restart (while
mirroring is still intact). So the trick is to temporarily make the
domain transient.
                                        
|--> domain.blockRebase(disk_path, disk_delta, 0,
                               libvirt.VIR_DOMAIN_BLOCK_REBASE_COPY |
                               libvirt.VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT |
                               libvirt.VIR_DOMAIN_BLOCK_REBASE_SHALLOW)
相当于执行了virsh blockcopy instance-000084ea vda /var/lib/nova/instances/snapshots/tmpDqNf6j/a04fb356eb2a423696d18d0ed01c7801.delta --reuse-external --shallow     
实际上是调用了libvirt的virDomainBlockRebase接口

                              |--> while self._wait_for_block_job(domain, disk_path):
                                         time.sleep(0.5)
                                     domain.blockJobAbort(disk_path, 0)
                                     libvirt_utils.chown(disk_delta, os.getuid())
                              每间隔0.5s查询一下blockcopy是否达到100%并进入mirror状态,相当于执行virsh blockjob instance-000084ea vda --info
                              如果已经进入mirror状态,就执行abort,完成本次快照,相当于执行virsh blockjob instance-000084ea vda --abort

|--> self._conn.defineXML(xml)
最后,重新定义虚机,将虚机从transient置为persistent状态。

|--> libvirt_utils.extract_snapshot(disk_delta, 'qcow2', out_path, image_format)
将创建好的image进行压缩,相当于执行下面的命令:
qemu-img convert -c -f qcow2 -O qcow2 /var/lib/nova/instances/snapshots/tmpDqNf6j/a04fb356eb2a423696d18d0ed01c7801.delta  /var/lib/nova/instances/snapshots/tmpDqNf6j/a04fb356eb2a423696d18d0ed01c7801

|--> LOG.info(_LI("Snapshot extracted, beginning image upload"),  instance=instance)
打印如下log:"Snapshot extracted, beginning image upload"

|--> update_task_state(task_state=task_states.IMAGE_UPLOADING,  expected_state=task_states.IMAGE_PENDING_UPLOAD)
更新任务状态为image_pending_upload

|-->  image_service.update(context, image_href, metadata, image_file)
将image上传到glance

|-->  LOG.info(_("Snapshot image upload complete"), instance=instance)
打印log:"Snapshot image upload complete"

至此,一个完整的虚机热快照就已经生成并上传到glance服务器。
可以通过以下命令查看生成的快照:nova image-show zhuozh-1-ss

  1.  以上创建关键步骤对应的libvirt命令行操作可以总结为:
#!/bin/bash

name=$1
virsh dumpxml $name > $name.xml
virsh undefine $name
qemu-img create -f qcow2 -o backing_file=/var/lib/nova/instances/_base/955caa5565d159c94e4965af17d3e499280106af,size=40G /var/lib/nova/backup/$name.dlta

          virsh blockjob $name vda --abort
          virsh blockcopy $name vda /var/lib/nova/backup/$name.dlta --wait --reuse-external --verbose

virsh define $name.xml
0 0
原创粉丝点击