cloud-init是专为云计算环境中虚拟机实例/裸金属实例的初始化而开发的一个开源工具,它安装在虚拟机镜像/裸金属镜像中,创建实例时,通过nova组件的configdrive把预注入的数据打包成镜像,并挂载在实例的cdrom中,实例启动时,通过读取cdrom中的相关数据,对虚拟机进行初始化配置。
1,cloudinit安装
centos和ubuntu server最新官方源中已经包含cloudinit,也可以直接采用yum或者apt-get安装。
2,cloudinit使用场景
cloudinit安装在openstack虚拟机、裸金属的镜像中,只适用于linux操作系统镜像。
Windows镜像对应需要安装cloudbase-init。
OpenStack中如果要使用Config Drive实现元数据的注入,在制作image时一定要安装cloud-init软件,否则无法实现元数据注入。
Metadata只能注入主机的基本信息。
Userdata使用场景比较广,只要有配置云服务器的需求,尤其是在批量化模版化的情况下,使用userdata定制云主机是最好的选择。
3,链接
cloudinit官方文档
http://cloudinit.readthedocs.io/en/latest/
Cloud-init github仓库地址
https://github.com/cloud-init/cloud-init
4,cloudinit应用架构图
config drive 是一个特殊的文件系统,OpenStack 会将 metadata/userdata写到config drive,在虚拟机启动时,自动挂载给 instance。
如过instance镜像中安装了cloud-init,config drive会被自动 mount并从中读取 metadata/userdata,进而完成后续的初始化工作。
metadata就是虚拟机的元数据
1,什么是元数据
元数据是对数据资源的描述,英文名称是“metadata”, 通常被解释为data about data,即关于数据的数据。元数据是指从信息资源中抽取出来的用于说明其特征、内容的结构化的数据,用于组织、描述、检索、保存、管理信息和知识资源。
一个基本的元数据由元数据项目和元数据内容的构成。
比如,关于一本书的元数据如下,包括书名、作者、出版社等,描述的是这本书本来的属性,和书中的内容没有直接关系。
书名: OpenStack设计与实现
作者: 英特尔开源技术中心
出版社: 电子工业出版社
定价: ¥99
虚拟机的元数据,主要包括虚拟机自身的属性,如 hostname、网络配置信息、SSH 登陆秘钥等,以键值对的形式描述。例如:
Hostname: myhost1
IP: 192.168.1.100
密码: Passw0rd
2,metadata
metadata是云平台提供的云主机属性信息,在创建云主机的时候做默认配置,目前包括hostname、镜像名称、网络类型、ip地址等属性值,以键值对的形式给出。
2.1,虚拟机获取metadata的方式
在OpenStack中,虚拟机中的cloud-init获取metadata信息的方式有两种:
Config drive 和 metadata RESTful服务。
2.1.1 Config drive方式获取metadata
Config drive 机制是指OpenStack将metadata信息写入虚拟机的一个特殊的配置设备中,然后在虚拟机启动时,自动挂载并读取 metadata信息,从而达到获取metadata的目的。
在客户端操作系统中,存储 metadata 的设备需要是ISO9660或者VFAT文件系统。
默认是 iso9660,但这会导致 instance 无法在线迁移,必须设置成config_drive_format=vfat 才能在线迁移。配置完成后,重启 nova-compute 服务。
config-drive 其实就是 Metadata-Source 的 “本地” 版本,它不依赖虚拟机网络信息,客户机操作系统可以直接通过一个设备读取 Metadata 信息。
config drive的格式:config drive 的默认格式是一个"ISO 9660"的文件系统,可以在nova配置文件中指出:
vi /etc/nova/nova.conf
config_drive_format=iso9660
具体实现参考代码分析
要实现上述功能,需要宿主机和虚拟机镜像两者协同完成,它们需要各自满足一些条件:
(1)宿主机(OpenStack计算节点)
支持 config drive 机制的 Hypervisors 有:libvirt、hyper-v 和 VMware。
当使用 libvirt 和 VMware 作为 Hypervisor 时,需要确保宿主机上(即计算节点运行的系统)安装有 genisoimage 程序,并且设置 mkisofs_cmd 标志为 genisoimage 的位置。
当使用 hyper-v 作为 Hypervisor 时,需要设置 mkisofs_cmd 标志为 mkisofs.exe 的全路径,此外还需要在 hyper-v 的配置文件中设置 qume_img_cmd 为 qemu-img 命令的路径。
(2)虚拟机镜像
虚拟机镜像需要确保安装了 cloud-init。
OpenStack 提供了命令行参数--config-drive 用于配置是否在创建虚拟机时使用 config drive 机制。比如:(具体实现可参考代码分析)
# nova boot --config-drive=true --image image-name --key-name mykey --flavor 1 --user-data ./my-user-data.txt myinstance --file /etc/network/interfaces=/home/myuser/instance-interfaces
或者在/etc/nova/nova.conf中配置,直接使OpenStack计算服务在创建虚拟机时默认使用config drive 机制。
force_config_drive=true
2.1.2 Metadata RESTful 服务方式获取metadata
OpenStack提供了RESTful 接口,虚拟机可以通过 REST API 来获取 metadata 信息。提供该服务的组件为:nova-api-metadata。当然,要完成从虚拟机至网络节点的请求发送和相应,只有 nova-api-metadata 服务是不够的,此外共同完成这项任务的服务还有:Neutron-metadata-agent 和 Neutron-ns-metadata-proxy。
由于metadata service结构太复杂,建议使用config drive的方式获取metadata。
1,Userdata的优势
userdata即云主机启动时用户提供的自定义数据,用户可提供除metadata主机属性之外的自定义功能,例如,用自定义脚本的方式,实现云主机初始化时的自定义磁盘分区、网卡bonging、多网络的网关设置等功能,userdata是实现云主机个性化定制的基础。
userdata非常灵活,当然使用userdata也可以实现metadata可以实现的功能。
可以把metadata理解为标准模式,能配置的功能已经给定key值;而userdata可理解为定制模式,只要格式语法正确,完全可以自定义主机的初始化功能。metadata是报团旅游,userdata是定制旅游。
2,Userdata的注入方式
userdata注入方式有多种,常用的格式有:userdata-scripts和cloud-config。
userdata-scripts:适用于需要通过执行shell脚本初始化实例的用户,以“#!/bin/sh”开头,从用户数据来看目前大部分用户都是直接通过这种格式输入userdata的, 也适用于较复杂的部署场景。
cloud-config: 是cloud-init支持的特有格式,它把常用的个性化配置包装成YAML文件格式提供出来,通过这种形式可以更方便的完成常用配置,以“#cloud-config”为首行区分,紧随其后的是一个关联数组,提供的键包括ssh_authorized_keys、hostname、write_files、manage_etc_hosts等。
Metadata 主要包括虚拟机自身的一些常用属性,如 hostname、网络配置信息、SSH 登陆秘钥等,主要的形式为键值对。而 user data 主要包括一些命令、脚本等。User data 通过文件传递,并支持多种文件格式,包括 gzip 压缩文件、shell 脚本、cloud-init 配置文件等。虽然 metadata 和 user data 并不相同,但是 OpenStack 向虚拟机提供这两种信息的机制是一致的,只是虚拟机在获取到信息后,对两者的处理方式不同罢了。
在一个初始创建好的instance里面访问config drive,如果OS支持通过label访问磁盘,那么在instance里会有一个叫“config-2”的volume label,可以挂载它到instance本地:
mount /dev/disk/by-label/config-2 /mnt/config
如果OS没有使用udev将不会有/dev/disk/by-label目录,blkid可以发现它,并且同样可以被挂载:
# blkid -t LABEL="config-2" -odevice
/dev/vdb
# mount /dev/vdb /mnt/config
1,nova/virt/vmwareapi/vmops.py
spawn方法是创建实例的入口,由configdrive.py文件中的的required_by方法可知,当创建实例时,若实例对象中指定config_drive、或者配置文件中的force_config_drive为真,都会创建configdrive。
调用_configure_config_drive() --> _create_config_drive()--> make_drive()方法创建镜像文件。
class VMwareVMOps(object):
def spawn(self, context, instance, image_meta, injected_files,
admin_password, network_info, block_device_info=None):
metadata = self._get_instance_metadata(context, instance)
if configdrive.required_by(instance):
self._configure_config_drive(
instance, vm_ref, vi.dc_info, vi.datastore,
injected_files, admin_password, network_info)
def _configure_config_drive(self, instance, vm_ref, dc_info, datastore,
injected_files, admin_password, network_info):
session_vim = self._session.vim
cookies = session_vim.client.options.transport.cookiejar
dc_path = vutil.get_inventory_path(session_vim, dc_info.ref)
uploaded_iso_path = self._create_config_drive(instance,
injected_files,
admin_password,
network_info,
datastore.name,
dc_path,
instance.uuid,
cookies)
uploaded_iso_path = datastore.build_path(uploaded_iso_path)
self._attach_cdrom_to_vm(
vm_ref, instance,
datastore.ref,
str(uploaded_iso_path))
def _create_config_drive(self, instance, injected_files, admin_password,
network_info, data_store_name, dc_name,
upload_folder, cookies):
if CONF.config_drive_format != 'iso9660':
reason = (_('Invalid config_drive_format "%s"') %
CONF.config_drive_format)
raise exception.InstancePowerOnFailure(reason=reason)
LOG.info(_LI('Using config drive for instance'), instance=instance)
extra_md = {}
if admin_password:
extra_md['admin_pass'] = admin_password
inst_md = instance_metadata.InstanceMetadata(instance,
content=injected_files,
extra_md=extra_md,
network_info=network_info)
try:
with configdrive.ConfigDriveBuilder(instance_md=inst_md) as cdb:
with utils.tempdir() as tmp_path:
tmp_file = os.path.join(tmp_path, 'configdrive.iso')
cdb.make_drive(tmp_file)
upload_iso_path = "%s/configdrive.iso" % (
upload_folder)
images.upload_iso_to_datastore(
tmp_file, instance,
host=self._session._host,
port=self._session._port,
data_center_name=dc_name,
datastore_name=data_store_name,
cookies=cookies,
file_path=upload_iso_path)
return upload_iso_path
except Exception as e:
with excutils.save_and_reraise_exception():
LOG.error(_LE('Creating config drive failed with error: %s'),
e, instance=instance)
2,nova/virt/configdrive.py
配置文件中的'mkisofs_cmd'默认值为'genisoimage',即默认用genisoimage工具来创建数据打包的镜像。
def required_by (instance):
image_prop = instance.image_meta.properties.get(
"img_config_drive",
fields.ConfigDrivePolicy.OPTIONAL)
return (instance.config_drive or
CONF.force_config_drive or
image_prop == fields.ConfigDrivePolicy.MANDATORY
)
configdrive_opts = [
cfg.StrOpt('config_drive_format',
default='iso9660',
choices=('iso9660', 'vfat'),
help='Config drive format.'),
cfg.BoolOpt('force_config_drive',
help='Force injection to take place on a config drive',
default=False),
cfg.StrOpt('mkisofs_cmd',
default='genisoimage',
help='Name and optionally path of the tool used for '
'ISO image creation')
]
class ConfigDriveBuilder(object):
"""Build config drives, optionally as a context manager."""
def make_drive(self, path):
"""Make the config drive.
:param path: the path to place the config drive image at
:raises ProcessExecuteError if a helper process has failed.
"""
with utils.tempdir() as tmpdir:
self._write_md_files(tmpdir)
if CONF.config_drive_format == 'iso9660':
self._make_iso9660(path, tmpdir)
elif CONF.config_drive_format == 'vfat':
self._make_vfat(path, tmpdir)
else:
raise exception.ConfigDriveUnknownFormat(
format=CONF.config_drive_format)
def _make_iso9660(self, path, tmpdir):
publisher = "%(product)s %(version)s" % {
'product': version.product_string(),
'version': version.version_string_with_package()
}
utils.execute(CONF.mkisofs_cmd,
'-o', path,
'-ldots',
'-allow-lowercase',
'-allow-multidot',
'-l',
'-publisher',
publisher,
'-quiet',
'-J',
'-r',
'-V', 'config-2',
tmpdir,
attempts=1,
run_as_root=False)
参考:
https://www.ibm.com/developerworks/cn/cloud/library/1509_liukg_openstackmeta/