openstack ice 生成虚拟机磁盘文件详解

感谢朋友支持本博客,欢迎共同探讨交流,由于能力和时间有限,错误之处在所难免,欢迎指正!

如有转载,请保留源作者博客信息。

Better Me的博客:blog.csdn.net/tantexian

如需交流,欢迎大家博客留言。



首先上图生成一个虚拟机在后台中的文件如何保存:
虚拟机生成选择类型表单:
openstack ice 生成虚拟机磁盘文件详解_第1张图片
生成成功后的虚拟机截图:

linux后台:
openstack ice 生成虚拟机磁盘文件详解_第2张图片

(1)、其中_base目录保存着虚拟机镜像模板文件(即cirros-0.3.2-x86_64镜像文件):
格式为raw,即不可变文件,相对于qcow2性能更高,更多详细请自行查找相关资料

该文件也即前端页面的镜像:
openstack ice 生成虚拟机磁盘文件详解_第3张图片
比较纳闷的是为啥两个数据不一致(disk size: 17M != 13167616K),猜测是获取OS-EXT-IMG-SIZE:size时,做了一些处理。具体没仔细研究,有兴趣请自行研究。

(2)、其中一连串数字的目录(4184b91d-ef74-4c74-b88f-2f28048ec89b)下面保存了特定虚拟机自己的文件信息:

disk详情:
openstack ice 生成虚拟机磁盘文件详解_第4张图片
其中格式为qcow2,virtual size为前台flavor分配的root disk大小。disk size为目前实际占用的磁盘空间大小(因为qcow2是实际使用多少分配多少)
backing file:指明此虚拟机的后端文件,因此实例变化时候,只会改变差异信息到disk文件,_base下面的backing file镜像文件不会改变。

disk.info保存该实例的基本信息:

libvirt.xml启动虚拟机的整个配置文件:
openstack ice 生成虚拟机磁盘文件详解_第5张图片
以上讲解了整个实例创建完毕后,虚拟机实例文件是如何保存的。(注,本例中用的本地存储,假若用ceph作为后端,操作会有些许不同,请自行实验)


接下来对上述虚拟机文件的生成源码进行解析:


1、创建虚拟机前序流程不再重复,具体自行参考博文: create instance 生成虚拟机(pycharm debug)流程:
直接从virt/libvird/driver.py开始分析:
附上源码,详解:
     def _create_image(self, context, instance,
                      disk_mapping, suffix='',
                      disk_images=None, network_info=None,
                      block_device_info=None, files=None,
                      admin_pass=None, inject_files=True):
        import pydevd  #远程debug断点
        pydevd.settrace('192.168.10.10', port=51234, stdoutToServer=True, stderrToServer=True)
        if not suffix:
            suffix = ''
        booted_from_volume = self._is_booted_from_volume(  #判断是否为卷启动,本示例此处值为false,见图1-1
            instance, disk_mapping)
        def image(fname, image_type=CONF.libvirt.images_type):
            return self.image_backend.image(instance,
                                            fname + suffix, image_type)
        def raw(fname):
            return image(fname, image_type='raw')
        # ensure directories exist and are writable
        fileutils.ensure_tree(libvirt_utils.get_instance_path(instance))  #确认目录存在,且具有可写权限,变量值看图1-2
        LOG.info(_('Creating image'), instance=instance)
        # NOTE(dprince): for rescue console.log may already exist... chown it.
        self._chown_console_log_for_instance(instance)  #控制台日志输出
        # NOTE(yaguang): For evacuate disk.config already exist in shared
        # storage, chown it.
        self._chown_disk_config_for_instance(instance)  #修改disk保存路径的权限,具体参考图1-3
        # NOTE(vish): No need add the suffix to console.log
        libvirt_utils.write_to_file( #写空字符串''到控制台路径文件中
            self._get_console_log_path(instance), '', 7)
        if not disk_images: #获取一个disk%Ew��信息字典,本示例此处kernel_id,ramdisk_id都为空‘’
            disk_images = {'image_id': instance['image_ref'],
                           'kernel_id': instance['kernel_id'],
                           'ramdisk_id': instance['ramdisk_id']}
        if disk_images['kernel_id']:
            fname = imagecache.get_cache_fname(disk_images, 'kernel_id')
            raw('kernel').cache(fetch_func=libvirt_utils.fetch_image,
                                context=context,
                                filename=fname,
                                image_id=disk_images['kernel_id'],
                                user_id=instance['user_id'],
                                project_id=instance['project_id'])
            if disk_images['ramdisk_id']:
                fname = imagecache.get_cache_fname(disk_images, 'ramdisk_id')
                raw('ramdisk').cache(fetch_func=libvirt_utils.fetch_image,
                                     context=context,
                                     filename=fname,
                                     image_id=disk_images['ramdisk_id'],
                                     user_id=instance['user_id'],
                                     project_id=instance['project_id'])
        inst_type = flavors.extract_flavor(instance) #获取本次生成实例的类型即规格,见图1-4
        # NOTE(ndipanov): Even if disk_mapping was passed in, which
        # currently happens only on rescue - we still don't want to
        # create a base image.
#是否从卷启动,不是从卷启动,则不需要创建base image,即共用_base文件夹下面镜像,作为后端文件backfile
        if not booted_from_volume:
            root_fname = imagecache.get_cache_fname(disk_images, 'image_id')#获取image_id的一个hash值
            size = instance['root_gb'] * units.Gi #获取页面选择的Root disk值
            if size == 0 or suffix == '.rescue':
                size = None
            image('disk').cache(fetch_func=libvirt_utils.fetch_image, #跟进:1.1
                                context=context,
                                filename=root_fname,
                                size=size,
                                image_id=disk_images['image_id'],
                                user_id=instance['user_id'],
                                project_id=instance['project_id'])
        # Lookup the filesystem type if required
        os_type_with_default = disk.get_fs_type_for_os_type( #从instance参数中获取os_type,没有的话,则为default
                                                          instance['os_type'])
        ephemeral_gb = instance['ephemeral_gb'] #获取页面的flavor中的ephemeral_gb 值
        if 'disk.local' in disk_mapping: #判断是否存在local分区,本示例不存在
            disk_image = image('disk.local')
            fn = functools.partial(self._create_ephemeral,
                                   fs_label='ephemeral0',
                                   os_type=instance["os_type"],
                                   is_block_dev=disk_image.is_block_dev)
            fname = "ephemeral_%s_%s" % (ephemeral_gb, os_type_with_default)
            size = ephemeral_gb * units.Gi
            disk_image.cache(fetch_func=fn,
                             filename=fname,
                             size=size,
                             ephemeral_size=ephemeral_gb)
        for idx, eph in enumerate(driver.block_device_info_get_ephemerals(
                block_device_info)):
            disk_image = image(blockinfo.get_eph_disk(idx))
            fn = functools.partial(self._create_ephemeral,
                                   fs_label='ephemeral%d' % idx,
                                   os_type=instance["os_type"],
                                   is_block_dev=disk_image.is_block_dev)
            size = eph['size'] * units.Gi
            fname = "ephemeral_%s_%s" % (eph['size'], os_type_with_default)
            disk_image.cache(
                             fetch_func=fn,
                             filename=fname,
                             size=size,
                             ephemeral_size=eph['size'])
        if 'disk.swap' in disk_mapping: #判断是否存在swap分区,本示例不存在
            mapping = disk_mapping['disk.swap']
            swap_mb = 0
            swap = driver.block_device_info_get_swap(block_device_info)
            if driver.swap_is_usable(swap):
                swap_mb = swap['swap_size']
            elif (inst_type['swap'] > 0 and
                  not block_device.volume_in_mapping(
                    mapping['dev'], block_device_info)):
                swap_mb = inst_type['swap']
            if swap_mb > 0:
                size = swap_mb * units.Mi
                image('disk.swap').cache(fetch_func=self._create_swap,
                                         filename="swap_%s" % swap_mb,
                                         size=size,
                                         swap_mb=swap_mb)
        # Config drive
        if configdrive.required_by(instance): #判断是否存在configdrive,本示例不存在。具体见图1-1-2:
            LOG.info(_('Using config drive'), instance=instance)
            extra_md = {}
            if admin_pass:
                extra_md['admin_pass'] = admin_pass
            inst_md = instance_metadata.InstanceMetadata(instance,
                content=files, extra_md=extra_md, network_info=network_info)
            with configdrive.ConfigDriveBuilder(instance_md=inst_md) as cdb:
                configdrive_path = self._get_disk_config_path(instance)
                LOG.info(_('Creating config drive at %(path)s'),
                         {'path': configdrive_path}, instance=instance)
                try:
                    cdb.make_drive(configdrive_path)
                except processutils.ProcessExecutionError as e:
                    with excutils.save_and_reraise_exception():
                        LOG.error(_('Creating config drive failed '
                                  'with error: %s'),
                                  e, instance=instance)
        # File injection only if needed
#如果inject_files ==True且inject_partition  != -2则执行文件注入
        elif inject_files and CONF.libvirt.inject_partition != -2:
            if booted_from_volume:
                LOG.warn(_('File injection into a boot from volume '
                           'instance is not supported'), instance=instance)
            self._inject_data(
                instance, network_info, admin_pass, files, suffix)
        if%r0CONF.libvirt.virt_type == 'uml': #如果配置文件配置为uml,则将disk的权限修改为root
            libvirt_utils.chown(i-age('disk').path, 'root')

图1-1-2:判断是%5��存在config_drive:
def required_by(instance):
    return instance.get('config_drive') or CONF.force_config_drive
图1-1:
openstack ice 生成虚拟机磁盘文件详解_第6张图片

图1-2

图1-3:
openstack ice 生成虚拟机磁盘文件详解_第7张图片


图1-4:


1.1:image('disk').cache(fetch_func=libvirt_utils.fetch_image,
def cache(self, fetch_func, filename, size=None, *args, **kwargs):
        """Creates image from template. #根据template创建image 
        Ensures that template and image not already exists.
        Ensures that base directory exists.
        Synchronizes on template fetching.
        :fetch_func: Function that creates the base image
                     Should accept `target` argument.
        :filename: Name of the file in the image directory
        :size: Size of created image in bytes (optional)
        """
        @utils.synchronized(filename, external=True, lock_path=self.lock_path)
        def fetch_func_sync(target, *args, **kwargs):
            fetch_func(target=target, *args, **kwargs)
        base_dir = os.path.join(CONF.instances_path,  #获取base镜像的目录路径,见图1.1-1
                                CONF.image_cache_subdirectory_name)
        if not os.path.exists(base_dir):
            fileutils.ensure_tree(base_dir)
        base = os.path.join(base_dir, filename)  #获取镜像文件路径,见图1.1-2
        if not self.check_image_exists() or not os.path.exists(base):
            self.create_image(fetch_func_sync, base, size,  #镜像和路径存在,创建镜像,跟进到1.1.1(此处为生成虚拟机文件核心代码)
                              *args, **kwargs)
        if (size and self.preallocate and self._can_fallocate() and
                os.access(self.path, os.W_OK)):
            utils.execute('fallocate', '-n', '-l', size, self.path) #linux命令:为文件预分配物理空间,本示例此处代码不执行
注:上述的 create_image函数拼装执行一个qemu-img create命令,最后一行代码则拼装执行一个 fallocate命令。

图1.1-1
openstack ice 生成虚拟机磁盘文件详解_第8张图片

图1.1-2
openstack ice 生成虚拟机磁盘文件详解_第9张图片

1.1.1:def create_image(self, prepare_template, base, size, *args, **kwargs):
在imagebackend.py文件中有四个create_image函数存在:
openstack ice 生成虚拟机磁盘文件详解_第10张图片

虚拟机镜像后端配置backend:
openstack ice 生成虚拟机磁盘文件详解_第11张图片

通过类和名字可以知道,不同的配置调用不同的create_image函数。Lvm、Qcow2、Raw、Rbd都继承了Image类,实现了对应的create_image函数。本示例中执行的为Qcow2类中create_image函数。(注:ceph对应为Rbd)

openstack ice 生成虚拟机磁盘文件详解_第12张图片
def create_image(self, prepare_template, base, size, *args, **kwargs):
        filename = os.path.split(base)[-1]  #获取镜像文件名
        @utils.synchronized(filename, external=True, lock_path=self.lock_path)
        def copy_qcow2_image(base, target, size):
            # TODO(pbrady): Consider copying the cow image here
            # with preallocation=metadata set for performance reasons.
            # This would be keyed on a 'preallocate_images' setting.
            libvirt_utils.create_cow_image(base, target)
            if size:
                disk.extend(target, size, use_cow=True)
        # Download the unmodified base image unless we already have a copy.
        if not os.path.exists(base):
            prepare_template(target=base, max_size=size, *args, **kwargs)
        else:
#验证base的size是否满足小于size(其中base的size为'/var/lib/nova/instances/_base/70ec07731557ccdb9287d902cf15ec3d26ab9b41'文件的大小,第二个size为flovar中的root disk大小即1GB),如果不满足则说明,该镜像不能在小于自己容量的flaver中生成,报错
            self.verify_base_size(base, size)
        legacy_backing_size = None
        legacy_base = base
#以下针对qcow2格式disk进行逻辑处理,因为qcow2文件占用空间是按需动态增长的
        # Determine whether an existing qcow2 disk uses a legacy backing by
        # actually looking at the image itself and parsing the output of the
        # backing file it expects to be using.
        if os.path.exists(self.path):  #判断路径是否存在,见图1.1.1-1
            backing_path = libvirt_utils.get_disk_backing_file(self.path)
            if backing_path is not None:
                backing_file = os.path.basename(backing_path)
                backing_parts = backing_file.rpartition('_')
                if backing_file != backing_parts[-1] and \
                        backing_parts[-1].isdigit():
                    legacy_backing_size = int(backing_parts[-1])
                    legacy_base += '_%d' % legacy_backing_size
                    legacy_backing_size *= units.Gi
        # Create the legacy backing file if necessary.
        if legacy_backing_size:
            if not os.path.exists(legacy_base):
                with fileutils.remove_path_on_error(legacy_base):
                    libvirt_utils.copy_image(base, legacy_base)#跟进代码1.1.2
                    disk.extend(legacy_base, legacy_backing_size, use_cow=True)
        if not os.path.exists(self.path):  #代码同上,同样可以参考图1.1.1-1
            with fileutils.remove_path_on_error(self.path):
                copy_qcow2_image(base, self.path, size) #此处跟进代码1.1.4

图1.1.1-1:
openstack ice 生成虚拟机磁盘文件详解_第13张图片

1.1.2:copy_image(此函数作用为使用cp或者scp或者rsync命令复制disk image文件到指定目录)

1.1.4:def copy_qcow2_image(base, target, size):
  @utils.synchronized(filename, external=True, lock_path=self.lock_path)
        def copy_qcow2_image(base, target, size):
            # TODO(pbrady): Consider copying the cow image here
            # with preallocation=metadata set for performance reasons.
            # This would be keyed on a 'preallocate_images' setting.
            libvirt_utils.create_cow_image(base, target)  #跟进到1.1.5:
            if size:
                disk.extend(target, size, use_cow=True)
1.1.5:libvirt_utils.create_cow_image(base, target)
def create_cow_image(backing_file, path, size=None):  #拼装qemu-img 创建命令
"""Create COW image 

Creates a COW image with the given backing file 

:param backing_file: Existing image on which to base the COW image 
:param path: Desired location of the COW image 
""" 
base_cmd = ['qemu-img', 'create', '-f', 'qcow2'] 
cow_opts = [] 
if backing_file: 
cow_opts += ['backing_file=%s' % backing_file] 
base_details = images.qemu_img_info(backing_file) 
else: 
base_details = None 
# This doesn't seem to get inherited so force it to... 
http://paste.ubuntu.com/1213295/ 
# TODO(harlowja) probably file a bug against qemu-img/qemu 
if base_details and base_details.cluster_size is not None: 
cow_opts += ['cluster_size=%s' % base_details.cluster_size] 
# For now don't inherit this due the following discussion... 
# See:  http://www.gossamer-threads.com/lists/openstack/dev/10592 
# if 'preallocation' in base_details: 
# cow_opts += ['preallocation=%s' % base_details['preallocation']] 
if base_details and base_details.encryption: 
cow_opts += ['encryption=%s' % base_details.encryption] 
if size is not None: 
cow_opts += ['size=%s' % size] 
if cow_opts: 
# Format as a comma separated list 
csv_opts = ",".join(cow_opts) 
cow_opts = ['-o', csv_opts] 
cmd = base_cmd + cow_opts + [path] 
execute(*cmd) #直接拼装出来的命令,本示例具体值,见图1.1.5-1(此处即拼装处生成虚拟机文件的命令)
图1.1.5-1:

注:

如果“-o”选项中使用了backing_file这个选项来指定其后端镜像文件,那么这个创建的镜像文件仅记录与后端镜像文件的差异部分。后端镜像文件不会被修改,除非在QEMU monitor中使用“commit”命令或者使用“qemu-img commit”命令去手动提交这些改动。这种情况下,size参数不是必须需的,其值默认为后端镜像文件的大小。另外,直接使用“-b backfile”参数也与“-o backing_file=backfile”效果相同。

size选项用于指定镜像文件的大小,其默认单位是字节(bytes),也可以支持k(或K)、M、G、T来分别表示KB、MB、GB、TB大小。另外,镜像文件的大小(size)也并非必须写在命令的最后,它也可以被写在“-o”选项中作为其中一个选项。

更多请参考博文: qemu-img命令详解

上述命令执行完在生成disk文件:
跟进讲解到此结束。后续生成xml文件,请自行参考其他博文,此处不再重复讲解。


你可能感兴趣的:(openstack ice 生成虚拟机磁盘文件详解)