您的位置:首页 > 运维架构

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

2016-02-29 00:00 941 查看
感谢朋友支持本博客,欢迎共同探讨交流,由于能力和时间有限,错误之处在所难免,欢迎指正!
如有转载,请保留源作者博客信息。

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

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

首先上图生成一个虚拟机在后台中的文件如何保存:

虚拟机生成选择类型表单:



生成成功后的虚拟机截图:



linux后台:



(1)、其中_base目录保存着虚拟机镜像模板文件(即cirros-0.3.2-x86_64镜像文件):



格式为raw,即不可变文件,相对于qcow2性能更高,更多详细请自行查找相关资料

该文件也即前端页面的镜像:



比较纳闷的是为啥两个数据不一致(disk size: 17M != 13167616K),猜测是获取OS-EXT-IMG-SIZE:size时,做了一些处理。具体没仔细研究,有兴趣请自行研究。

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



disk详情:



其中格式为qcow2,virtual size为前台flavor分配的root disk大小。disk size为目前实际占用的磁盘空间大小(因为qcow2是实际使用多少分配多少)

backing file:指明此虚拟机的后端文件,因此实例变化时候,只会改变差异信息到disk文件,_base下面的backing file镜像文件不会改变。

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



libvirt.xml启动虚拟机的整个配置文件:



以上讲解了整个实例创建完毕后,虚拟机实例文件是如何保存的。(注,本例中用的本地存储,假若用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:



图1-2



图1-3:



图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



图1.1-2



1.1.1:def create_image(self, prepare_template, base, size, *args, **kwargs):

在imagebackend.py文件中有四个create_image函数存在:



虚拟机镜像后端配置backend:



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



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:



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文件,请自行参考其他博文,此处不再重复讲解。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: