OpenStack Create Snapshot源碼流程分析,理解創(chuàng)建快照的本質(zhì)
【編者的話】
筆者以一個(gè)普通用戶的角度,在實(shí)際應(yīng)用的過程中不斷深入對看OpenStack的理解。在遇到故障時(shí),通過對源碼流程的分析,來探求問題出現(xiàn)的原因是知道在操作上還是系統(tǒng)上。本篇博文主要針對作者對創(chuàng)建快照過程(Create Snapshot)的源代碼流程的分析,走出了之前的認(rèn)知誤區(qū),理解了目前OpenStack創(chuàng)建快照過程的實(shí)質(zhì)。
背景:
一直以為OpenStack的創(chuàng)建快照的操作是在線創(chuàng)建快照(live snapshot), 并且應(yīng)該是增量的快照,即利用virsh或者qemu的live snapshot來實(shí)現(xiàn)的:
virsh snapshot-create-as --live ....
后來發(fā)現(xiàn)快照和原始鏡像之間并沒有依賴關(guān)系,感覺OpenStack還做的挺好的,自動解決了增量快照和原始鏡像之間的依賴關(guān)系;
但是后來又發(fā)現(xiàn)做快照的時(shí)候虛擬機(jī)竟然會shutoff, 就感覺不對了,于是分析了下源碼。
源代碼流程分析
1 nova/compute/api.py
- # NOTE(melwitt): We don't check instance lock for snapshot because lock is
- # intended to prevent accidental change/delete of instances
- @wrap_check_policy
- @check_instance_cell
- @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED,
- vm_states.PAUSED, vm_states.SUSPENDED])
- def snapshot(self, context, instance, name, extra_properties=None):
- """Snapshot the given instance.
- :param instance: nova.db.sqlalchemy.models.Instance
- :param name: name of the snapshot
- :param extra_properties: dict of extra image properties to include
- when creating the image.
- :returns: A dict containing image metadata
- """
- #調(diào)用glance api創(chuàng)建image entry,為后將snapshot上傳為鏡像做準(zhǔn)備,雖然鏡像和snapshot在可以上傳到glance作為鏡像啟動虛擬機(jī),
- #但是為了區(qū)分二者的不同,glance將鏡像和snapshot標(biāo)記衛(wèi)不同的類型:type=image 和 type=snapshot
- image_meta = self._create_image(context, instance, name,
- 'snapshot',
- extra_properties=extra_properties)
- # NOTE(comstud): Any changes to this method should also be made
- # to the snapshot_instance() method in nova/cells/messaging.py
- # 將任務(wù)狀態(tài)(task state) 設(shè)置為:image_snapshot_pending
- instance.task_state = task_states.IMAGE_SNAPSHOT_PENDING
- instance.save(expected_task_state=[None])
- #通過rpc調(diào)用nova/compute/rpcapi.py的snapshot_instance函數(shù)
- self.compute_rpcapi.snapshot_instance(context, instance,
- image_meta['id
2 nova/compute/rpcapi.py
- #梳理下流程: (1)用戶發(fā)起create snapshot的請求; (2)nova-api服務(wù)接收到這個(gè)請求并進(jìn)行前期處理,即3.1中代碼的處理流程;
- (3)真正的snapshot操作是需要在nova-compute節(jié)點(diǎn)上執(zhí)行的,所以nova-api需要向nova-compute發(fā)送message
- #由于OpenStack環(huán)境中會有很多個(gè)nova-compute,所以需要通過server=_compute_host(None, instance)來獲取虛擬機(jī)所在的host,并向其發(fā)送message。
- def snapshot_instance(self, ctxt, instance, image_id):
- version = '3.0'
- cctxt = self.client.prepare(server=_compute_host(None, instance),
- version=version)
- cctxt.cast(ctxt, 'snapshot_instance',
- instance=instance,
- image_id=image_id)
- 3 nova/virt/libvirt/driver.py
- def snapshot(self, context, instance, image_id, update_task_state):
- """Create snapshot from a running VM instance.
- This command only works with qemu 0.14+
- """
- try:
- virt_dom = self._get_domain(instance)
- except exception.InstanceNotFound:
- raise exception.InstanceNotRunning(instance_id=instance['uuid'])
- base_image_ref = instance['image_ref']
- base = compute_utils.get_image_metadata(
- context, self._image_api, base_image_ref, instance)
- snapshot = self._image_api.get(context, image_id)
- disk_path = libvirt_utils.find_disk(virt_dom)
- source_format = libvirt_utils.get_disk_type(disk_path)
- image_format = CONF.libvirt.snapshot_image_format or source_format
- # NOTE(bfilippov): save lvm and rbd as raw
- if image_format == 'lvm' or image_format == 'rbd':
- image_format = 'raw'
- metadata = self._create_snapshot_metadata(base,
- instance,
- image_format,
- snapshot['name'])
- snapshot_name = uuid.uuid4().hex
- state = LIBVIRT_POWER_STATE[virt_dom.info()[0]]
- # NOTE(rmk): Live snapshots require QEMU 1.3 and Libvirt 1.0.0.
- # These restrictions can be relaxed as other configurations
- # can be validated.
- # NOTE(dgenin): Instances with LVM encrypted ephemeral storage require
- # cold snapshots. Currently, checking for encryption is
- # redundant because LVM supports only cold snapshots.
- # It is necessary in case this situation changes in the
- # future.
- #這里需要注意,解釋了為啥現(xiàn)在是cold snapshot而不是live snapshot:
- # 有人提過live snapshot的bug,社區(qū)認(rèn)為live snapshot目前不穩(wěn)定,所以默認(rèn)條件下采用cold snapshot,并且是通過硬編碼來實(shí)現(xiàn)的
- # 看下面這個(gè)判斷條件,成立的時(shí)候?qū)ive_snapshot = true,其中MIN_LIBVIRT_LIVESNAPSHOT_VERSION=1.3.0, 其實(shí)現(xiàn)在libvirt的***版本
- # 才到1.2.11, 所以這個(gè)live_snapshot的條件不滿足,就變成了cold_snapshot
- if (self._host.has_min_version(MIN_LIBVIRT_LIVESNAPSHOT_VERSION,
- MIN_QEMU_LIVESNAPSHOT_VERSION,
- REQ_HYPERVISOR_LIVESNAPSHOT)
- and source_format not in ('lvm', 'rbd')
- and not CONF.ephemeral_storage_encryption.enabled):
- live_snapshot = True
- # Abort is an idempotent operation, so make sure any block
- # jobs which may have failed are ended. This operation also
- # confirms the running instance, as opposed to the system as a
- # whole, has a new enough version of the hypervisor (bug 1193146).
- try:
- virt_dom.blockJobAbort(disk_path, 0)
- except libvirt.libvirtError as ex:
- error_code = ex.get_error_code()
- if error_code == libvirt.VIR_ERR_CONFIG_UNSUPPORTED:
- live_snapshot = False
- else:
- pass
- else:
- live_snapshot = False
- # NOTE(rmk): We cannot perform live snapshots when a managedSave
- # file is present, so we will use the cold/legacy method
- # for instances which are shutdown.
- if state == power_state.SHUTDOWN:
- live_snapshot = False
- # NOTE(dkang): managedSave does not work for LXC
- #注意這里,如果live_snashot目前是false,所以在做snapshot之前先要執(zhí)行:
- #(1)_detach_pci_devices, 卸載虛擬機(jī)掛載的pci設(shè)備,比如數(shù)據(jù)盤
- #(2) self._detach_sriov_ports, 卸載虛擬機(jī)掛載的SRIOV設(shè)備,比如支持SRIOV的網(wǎng)卡設(shè)備
- if CONF.libvirt.virt_type != 'lxc' and not live_snapshot:
- if state == power_state.RUNNING or state == power_state.PAUSED:
- self._detach_pci_devices(virt_dom,
- pci_manager.get_instance_pci_devs(instance))
- self._detach_sriov_ports(instance, virt_dom)
- virt_dom.managedSave(0)
- #判斷虛擬機(jī)的后端存儲是什么,不同的后端存儲做snapshot是不同的,本地文件系統(tǒng)的化,默認(rèn)qcow2
- snapshot_backend = self.image_backend.snapshot(instance,
- disk_path,
- image_type=source_format)
- if live_snapshot:
- LOG.info(_LI("Beginning live snapshot process"),
- instance=instance)
- else:
- LOG.info(_LI("Beginning cold snapshot process"),
- instance=instance)
- #更新任務(wù)的狀態(tài)為:image_pending_upload, 大家都知道做完snapshot要上傳
- update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD)
- #目前做快照的過程是:
- #(1)現(xiàn)在../data/nova/instance/snapshots目錄下生成臨時(shí)目錄,比如nova/instances/snapshots/tmptHr585
- #(2)然后將快照生成到這個(gè)目錄,具體參見snapshot_backend.snapshot_extract(out_path, image_format)這個(gè)函數(shù)
- #(3)生成完成后,通過glance api上傳,具體參見 self._image_api.update
- snapshot_directory = CONF.libvirt.snapshots_directory
- fileutils.ensure_tree(snapshot_directory)
- with utils.tempdir(dir=snapshot_directory) as tmpdir:
- try:
- out_path = os.path.join(tmpdir, snapshot_name)
- if live_snapshot:
- # NOTE(xqueralt): libvirt needs o+x in the temp directory
- os.chmod(tmpdir, 0o701)
- self._live_snapshot(context, instance, virt_dom, disk_path,
- out_path, image_format, base)
- else:
- #這個(gè)函數(shù)實(shí)際執(zhí)行了一條命令: qemu-img convert -f qcow2 -O qcow2 disk_path out_path,算是生成了快照
- snapshot_backend.snapshot_extract(out_path, image_format)
- finally:
- new_dom = None
- # NOTE(dkang): because previous managedSave is not called
- # for LXC, _create_domain must not be called.
- if CONF.libvirt.virt_type != 'lxc' and not live_snapshot:
- if state == power_state.RUNNING:
- new_dom = self._create_domain(domain=virt_dom) ##恢復(fù)做快照之前虛擬機(jī)的狀態(tài)
- elif state == power_state.PAUSED:
- new_dom = self._create_domain(domain=virt_dom,
- launch_flags=libvirt.VIR_DOMAIN_START_PAUSED)
- if new_dom is not None:
- self._attach_pci_devices(new_dom,
- pci_manager.get_instance_pci_devs(instance))
- self._attach_sriov_ports(context, instance, new_dom)
- LOG.info(_LI("Snapshot extracted, beginning image upload"),
- instance=instance)
- # Upload that image to the image service
- update_task_state(task_state=task_states.IMAGE_UPLOADING,
- expected_state=task_states.IMAGE_PENDING_UPLOAD)
- with libvirt_utils.file_open(out_path) as image_file: ###將生成的快照上傳到glance
- self._image_api.update(context,
- image_id,
- metadata,
- image_file)
- LOG.info(_LI("Snapshot image upload complete"),
- instance=instance)
結(jié)論:
目前OpenStack默認(rèn)的快照方式都是cold snapshot, 首先先關(guān)機(jī),其次執(zhí)行如下命令生成一個(gè)鏡像文件,再次開機(jī),***再調(diào)用glance api將鏡像上傳。
- qemu-img convert -f qcow2 -O qcow2 <disk_path> <out_path>
所以目前并不是真正意義的快照,其實(shí)和關(guān)閉虛擬機(jī),拷貝一份,再上傳沒有本質(zhì)區(qū)別。