这一篇学习下nova-compute创建虚拟机的流程
首先说下,创建虚拟机实例时,我们需要给它分配磁盘设备block device,OpenStack中block device有4种类型: root device、swap device、ephemeral device和cinder volume, 前3种对应我们创建flavor时的 根磁盘、交换盘和临时磁盘,因为cinder我至今没有使用过,因此涉及到的时候会直接跳过,后面会单独学习。
root device: 提供boot loader,虚拟机会从这里启动,可以有多个,但是需要确定其中一个的boot_index = 0
swap device:作为交换分区,只能有1个,与虚拟内存有关
ephemeral device:额外提供的临时设备
针对每个block device在数据库里都包括一条block_device_mapping记录,顾名思义, 就是块设备的映射信息,里面包括device_type(设备类型,disk/cdrom/floppy/lun),device_name(设备名,例如/dev/vda, 没有指定的情况下使用默认值), boot_index(引导顺序), disk_bus(总线类型,没有指定的情况下使用虚拟化驱动支持的默认类型),source_type(image/snapshot/volume/blank,决定块设备产生的源头, 例如image, 那么块设备就是使用镜像创建的, 这种情况下还要记录对应的image_id),destination_type(local/volume,决定块设备的实际存储位置, 是在cinder提供的volume上, 还是在hypervisor本地)、guest_format
那怎么通过block_device_mapping中的信息判断block device是哪种类型呢?
swap device:source_type为blank,destination_type为local,guest_format为swap
ephemeral device: source_type为blank 且 不是swap device, 可见
root device: source_type不为blank 且 boot_index = 0
之前我们在nova-scheduler调度过程分析中提到过在调度完成后,由nova-scheduler通过调用目标主机nova-compute服务的run_instance RpcAPI来发起虚拟机实例的实际创建工作,那么就从run_instance这个RpcAPI开始进行分析,可以看出创建虚拟机实例的过程大致分为以下几步:
1.BUILDING阶段,验证实例组策略;
2.NETWORKING阶段,异步为实例分配网络资源;
3.BLOCK_DEVICE_MAPPING阶段,为实例的块设备获取默认的设备名
4.SPAWNING阶段,缓存镜像,为实例创建磁盘文件(包括文件注入),进行volume相关操作,配置实例的iptables防火墙规则,通过虚拟化驱动为实例创建虚拟机并启动,等待虚拟机的电源状态变为RUNNING,创建即宣告完成。
文件注入有2种方式:一种是通过cdrom挂载iso文件,另一种是将文件写入虚拟机的guest文件系统
# 验证给定的disk_bus在给定的virt_type下是否是有效的
def is_disk_bus_valid_for_virt(virt_type, disk_bus):
valid_bus = {
'qemu': ['virtio', 'scsi', 'ide', 'usb', 'fdc'],
'kvm': ['virtio', 'scsi', 'ide', 'usb', 'fdc'],
'xen': ['xen', 'ide'],
'uml': ['uml'],
'lxc': ['lxc'],
}
if virt_type not in valid_bus:
raise exception.UnsupportedVirtType(virt=virt_type)
return disk_bus in valid_bus[virt_type]
# 获取指定设备类型的总线
def get_disk_bus_for_device_type(virt_type, image_meta=None, device_type="disk"):
if image_meta:
key = "hw_" + device_type + "_bus"
disk_bus = image_meta.get('properties', {}).get(key)
# 首先尝试从镜像元数据中找出disk_bus
if disk_bus is not None:
# 如果镜像元数据中的disk_bus不为空, 那么验证disk_bus是否是有效的
if not is_disk_bus_valid_for_virt(virt_type, disk_bus):
raise exception.UnsupportedHardware(model=disk_bus,
virt=virt_type)
# 如果有效, 直接返回disk_bus
return disk_bus
# 然后尝试返回hypervisor默认的disk_bus
if virt_type == "uml":
if device_type == "disk":
return "uml"
elif virt_type == "lxc":
return "lxc"
elif virt_type == "xen":
if device_type == "cdrom":
return "ide"
elif device_type == "disk":
return "xen"
elif virt_type in ("qemu", "kvm"):
if device_type == "cdrom":
arch = libvirt_utils.get_arch(image_meta)
if arch in ("ppc", "ppc64"):
return "scsi"
else:
return "ide"
elif device_type == "disk":
return "virtio"
elif device_type == "floppy":
return "fdc"
return None
# 从设备名获取其总线
def get_disk_bus_for_disk_dev(virt_type, disk_dev):
if disk_dev[:2] == 'hd':
return "ide"
elif disk_dev[:2] == 'sd':
if virt_type == "xen":
return "xen"
else:
return "scsi"
elif disk_dev[:2] == 'vd':
return "virtio"
elif disk_dev[:2] == 'fd':
return "fdc"
elif disk_dev[:3] == 'xvd':
return "xen"
elif disk_dev[:3] == 'ubd':
return "uml"
else:
raise exception.NovaException(
_("Unable to determine disk bus for '%s'") %
disk_dev[:1])
# 从磁盘总线类型获取设备名前缀
def get_dev_prefix_for_disk_bus(disk_bus):
# 如果配置了disk_prefix, 那么直接返回即可
if CONF.libvirt.disk_prefix:
return CONF.libvirt.disk_prefix
if disk_bus == "ide":
return "hd"
elif disk_bus == "virtio":
return "vd"
elif disk_bus == "xen":
return "sd"
elif disk_bus == "scsi":
return "sd"
elif disk_bus == "usb":
return "sd"
elif disk_bus == "fdc":
return "fd"
elif disk_bus == "uml":
return "ubd"
elif disk_bus == "lxc":
return None
else:
raise exception.NovaException(
_("Unable to determine disk prefix for %s") %
disk_bus)
# 通过磁盘总线获取最多支持的设备数量
def get_dev_count_for_disk_bus(disk_bus):
# ide总线只支持4个设备
if disk_bus == "ide":
return 4
else:
return 26
# 通过总线类型获取设备名
def find_disk_dev_for_disk_bus(mapping, bus, last_device=False):
# 通过总线类型获取设备名前缀
dev_prefix = get_dev_prefix_for_disk_bus(bus)
if dev_prefix is None:
return None
# 通过总线类型获取最多支持的设备数量
max_dev = get_dev_count_for_disk_bus(bus)
# last_device用于指定是否是最后一个设备
if last_device:
devs = [max_dev - 1]
else:
devs = range(max_dev)
for idx in devs:
# Linux上的设备名是有规律的, 对于同种类型的设备, 它们的名称从"<设备名前缀>a"依次递增
disk_dev = dev_prefix + chr(ord('a') + idx)
# 还要判断设备名是否在已有的映射信息中, 不能重名
if not has_disk_dev(mapping, disk_dev):
return disk_dev
raise exception.NovaException(
_("No free disk device names for prefix '%s'"),
dev_prefix)
# 获取root块设备的信息
def get_root_info(virt_type, image_meta, root_bdm, disk_bus, cdrom_bus, root_device_name=None):
# source_type为image, destination_type为local?
no_root_bdm = (not root_bdm or (
root_bdm.get('source_type') == 'image' and
root_bdm.get('destination_type') == 'local'))
if no_root_bdm:
if (image_meta and image_meta.get('disk_format') == 'iso'):
# 如果镜像的disk_format是iso, 那说明我们要从cdrom启动
root_device_bus = cdrom_bus
root_device_type = 'cdrom'
else:
# 从硬盘启动
root_device_bus = disk_bus
root_device_type = 'disk'
if root_device_name:
# 通常我们可以设备名获取其设备类型继而了解其总线类型, 使用Linux的人应该有经验
# 从root设备名称获取其总线类型, 当设备名为vd*, 意味着我们要使用virtio总线
root_device_bus = get_disk_bus_for_disk_dev(virt_type,
root_device_name)
else:
# 反过来, 我们也可以根据总线类型获取到设备名, 当然设备名不能与已有设备重名
root_device_name = find_disk_dev_for_disk_bus({}, root_device_bus)
# 返回root块设备信息
return {'bus': root_device_bus,
'type': root_device_type,
'dev': block_device.strip_dev(root_device_name),
'boot_index': '1'}
else:
# 如果root块设备没有设备名且指定了root_device_name
if not get_device_name(root_bdm) and root_device_name:
# 因为要修改root_bdm, 对root_bdm进行拷贝, 避免对原始root_bdm进行污染
root_bdm = root_bdm.copy()
root_bdm['device_name'] = root_device_name
# 返回root_bdm的信息, 包括bus、dev、type、format和boot_index等
return get_info_from_bdm(virt_type, root_bdm, {}, disk_bus)
# 获取实例最终的块设备映射信息
def get_disk_mapping(virt_type, instance,
disk_bus, cdrom_bus,
block_device_info=None,
image_meta=None, rescue=False):
# 获取实例的云主机类型即flavor
inst_type = flavors.extract_flavor(instance)
mapping = {}
# 获取所有块设备的名称列表, 例如['sda', 'sdb']
pre_assigned_device_names = \
[block_device.strip_dev(get_device_name(bdm)) for bdm in itertools.chain(
driver.block_device_info_get_ephemerals(block_device_info), # DriverEphemeralBlockDevice实例列表
[driver.block_device_info_get_swap(block_device_info)], # DriverSwapBlockDevice实例列表, 只有一个元素
driver.block_device_info_get_mapping(block_device_info)) # DriverSnapshotBlockDevice和DriverVolumeBlockDevice实例列表
if get_device_name(bdm)]
if virt_type == "lxc":
root_disk_bus = disk_bus
root_device_type = 'disk'
# 获取总线类型上下个设备的设备信息作为root块设备信息
root_info = get_next_disk_info(mapping,
root_disk_bus,
root_device_type,
boot_index=1)
mapping['root'] = root_info
mapping['disk'] = root_info
return mapping
if rescue:
# 用于救援模式
# 获取总线类型上下个设备的设备信息作为root块设备信息和救援用的块设备信息
rescue_info = get_next_disk_info(mapping,
disk_bus, boot_index=1)
mapping['disk.rescue'] = rescue_info
mapping['root'] = rescue_info
os_info = get