这篇博文静静的呆在草稿箱大半年了。假设不是由于某些原因被问到,以及由于忽略它而导致的损失,否则我也不知道什么时候会将它完毕。感谢这段时间经历的挫折,让我知道不足。希望你能给我更大的决心!
本文试图具体地描写叙述openstack创建虚拟机的完整过程。从用户发起请求到虚拟机成功执行,包含client请求的发出、keystone身份验证、nova-api接收请求、nova-scheduler调度、nova-computer创建、nova-network分配网络。对于每个模块在创建虚拟机的过程中所负责的功能和执行的操作,进行较为具体描写叙述和讨论。为了方便描写叙述,本文如果全部的服务ip地址为localhost。port使用服务的默认设置port。openstack为F版。下图为创建虚拟机的一个大概流程图,粗糙地表示下。接下来将对每个模块进行具体介绍。如有错误,欢迎拍砖!
client
创建虚拟机的第一步是须要client调用nova-api,发送创建虚拟机请求。眼下openstack提供两种client:
1 命令行指令nova,通过指定命令行參数。就能够请求nova-api创建虚拟机,一个最简单的创建虚拟机指令例如以下:
nova boot vm_name --flavor flavor_id --image image_uuid
2 网页交互页面horizon,这是通过web操作页面来调用nova-api创建虚拟机,比較简单易用。选定相关參数后。点击create就能够了。
这两种client除了UI不一样以外,功能方面基本都是一样。
就创建虚拟机来讲。它们须要完毕:
- 用户身份验证。client构造一个body格式例如以下:
{"auth": {
"passwordCredentials": {"username": self.user,
"password": self.password}}
"tenantName": "admin" }
- 向keystone发送HTTP请求(keystone的服务地址:nova命令一般通过环境变量OS_AUTH_URL设置,horizon则通过openstack_dashboard.local.local_settings.OPENSTACK_KEYSTONE_URL设置),url为http://localhost:5000/v2.0/tokens 。假设验证通过,keystone则返回token_id和serviceCatalog。身份验证通过后。client才可对nova-api发送创建请求。
由于client只配置了keystone的服务地址和port,对于nova-api的服务地址它是不知道的。所以须要先通过keystone验证,然后由keystone将nova-api的服务地址放在serviceCatalog中返回给它。事实上serviceCatalog中不只包括nova-api的服务地址,还有glance-api、cinder-api等服务的地址,这些地址在以后的镜像下载、块存储请求时会用到。token_id被放在请求nova-api的headers中。用作nova-api验证。
- 对传入flavor和image參数进行验证,以确定资源是否有效(对于一些非必须參数,此处省略讨论)。
对于nova boot,flavor_id和image_uuid须要通过命令行參数指定,novaclient.v1_1.shell._boot()分别向nova-api的"http://localhost:8774/v2/project_id/flavors/flavor_id"和"http://localhost:8774/v2/project_id/images/image_id"发送HTTP请求,验证资源是否存在。对于horizon,它首先运行horizon.api.nova.flavor_list()和horizon.api.glance.image_list_detailed(),获取全部可用的flavor ids和image ids,创建虚拟机时仅仅能从这些可用的id中选择。
注意这里nova boot和horizon对于image的验证有些不同,nova boot是请求nova-api,nova-api再请求glance-api以验证image uuid,而horizon是直接请求glance-api获取全部的image ids。 - 最后设置好请求參数后(除name、flavor和image外,其他可使用默认值),通过novaclient.v1_1.ServerManager.create()发送创建虚拟机请求,该函数主要是将请求參数构造成规定格式的body。然后向nova-api发送HTTP请求。
关于body的模样,可看看novaclient.v1_1.BootingManagerWithFind._boot() 。以上面的nova boot命令为例,body内容例如以下:
{'server': {'flavorRef': '1',
'hypervisor_type': 'QEMU',
'imageRef': 'd3670457-16f5-4c70-913f-6fc7b76706e4',
'max_count': 1,
'min_count': 1,
'name': 'test-ic'}}
- 这里的flavorRef相应的是数据库instance_types的flavorid字段(非id字段),上面命令行传入的flavor_id也是指数据库的flavorid字段。向nova-api的http://localhost:8774/v2/project_id/flavors/flavorid请求时,它通过nova.api.openstack.compute.views.flavors.ViewBuilder,将数据库的id字段作为instance_type_id。将flavorid作为id进行返回的。
能够看出,不管nova boot还是horizon,最后都是通过novaclient向nova-api发送请求的。
novaclient是针对nova-api做了一层封装,如获取验证token-id,以特定的格式构造HTTP请求headers和body,解析请求的结果等。事实上能够不用nova boot命令行和horizon,甚至novaclient都不须要,我们全然能够设定好HTTP请求的headers和body,直接请求nova-api。只是这三个工具将这些繁琐的工作替我们做了。我们仅仅须要填写參数就能够了。最后注意下。nova命令事实上仅仅是novaclient的一个entry point:novaclient.shell.main()。而nova boot实际上调用的是novaclient.v1_1.shell.do_boot()。
keystone
由上可知,在client发起创建虚拟机请求时,keystone须要对client的username和password进行验证。keystone-all与nova-api一样,api的公布没有採用不论什么框架,而是使用router、paste类库。从头写的,实现风格上又与nova-api有点差异。
keystone-all服务会监听两个port:localhost:5000,即publicURL。一般用户使用password能够訪问;localhost:35357。即adminURL,仅仅能使用admin账户和password訪问。
在创建虚拟机流程中,调用的keystone的api有两个(事实上,每次请求基本都会调用这两个api):
1 http://localhost:5000/v2.0/tokens,请求该api来获取token_id和serviceCatalog,由client调用。keystone会将该api的请求交给keystone.TokenController.authenticate()处理。该函数主要完毕:
- passlib.hash.sha512_crypt.verify(password, hashed)。
- 依据CONF.signing.token_format配置项,为client的每一次请求生成一个token_id。眼下支持的选择有两个UUID、PKI。默觉得UUID。所以在默认情况下,一个token_id就是一个随机产生的uuid。token_id有一个有效时间。从token_id的生成的时刻開始算起。有效时间可通过CONF.token.expiration配置项设置,默认值为86400s。
- 构建serviceCatalog。这里面就是一堆endpoints,包含nova-api、glance-api、cinder-api、keystone-api等。依据配置项CONF.catalog.driver。能够从数据库的endpoint表中获取,也能够从模板文件里获取。
2 http://localhost:35357/v2.0/tokens/token_id,对请求的token_id进行验证。由nova-api调用。nova-api接收到请求后。首先使用请求携带的token_id来訪问该api,以验证请求是否通过验证。
当然nova-api须要在body里加上admin的账户和password信息,这些信息须要在api-paste.ini中配置。还有keystone的服务地址,由于在验证没通过之前。不能使用client传过来的endpoints。glance-api、cinder-api等在接收到client请求后。都会调用该api对client的token_id进行验证。
该api的请求交给keystone.TokenController.validate_token()处理,事实上就是使用请求的token_id进行一次数据库查询。
nova-api
nova-api是一个统称,它是一类服务的集合。如openstack之nova-api服务流程分析所说,在默认配置下,它包括ec2(与EC2兼容的API)。osapi_compute(openstack compute自己风格的API),osapi_volume(openstack volume服务API)。metadata(metadata 服务API)等api服务。每一个服务提供不同的api,只是尽管ec2和osapi_compute的api不同。但功能是同样的。这里,创建虚拟机请求的api是:
它们主要完毕下面功能:
- 验证client的token_id。通过keystone.middleware.auth_token.AuthProtocol.__call__()进行验证,它是一个middleware,通过api-paste.ini配置。如上面keystone所讲,它是通过请求http://localhost:35357/v2.0/tokens/token_id实现的。
- 检查创建虚拟机參数是否有效与合法,由nova.api.openstack.compute.servers.Controller.create()实现。
如。检查虚拟机name是否符合命名规范。flavor_id是否在数据库中存在,image_uuid是否是正确的uuid格式等。 - 检查instance、vcpu、ram的数量是否超过限制,并预留所需资源,由nova.quota.QUOTAS管理。
每一个project拥有的资源都是有限的,如创建虚拟机的个数,vcpu个数,内存大小。volume个数等。
默认情况下。全部project的拥有的资源数量相等,由quota_instances、quota_cores、quota_ram、quota_volumes等配置项指定。使用admin账户。以PUT方法请求http://localhost:8774/v2/admin_project/os-quota-sets/project_id。可为特定的project设置配额。 - 检查metadata长度是否超过限制。inject file的个数是否超过限制,由nova.quota.QUOTAS管理检測。普通情况下。这些參数都为空。都能通过。
- 检查指定的network和fixed ip是否有效。如network是否存在,fixed ip是否属于该network,fixed ip有没有被分配等。
普通情况下,该參数也为空,由network自己主动分配。 - 检查image、disk、ramdisk是否存在且可用,这个是向glance-api(http://localhost:9292/v1/images/image_id)请求。获取返回数据进行推断的。
并推断flavor是否满足该image的最小配置需求,如内存,虚拟磁盘是否满足image的最小值。 - 当全部资源充足。而且全部传參都有效时。更新数据库。新建一条instance记录,vm_states设为BUILDING。task_state设为SCHEDULING。假设没有给虚拟机指定security group。那么将默认使用default。instance与security group的关联很隐蔽,在db.instance_create()中,注意观察。
- 提交预留资源,完毕资源分配。
通过rpc call,将全部參数传给nova-scheduler的nova.scheduler.manager.SchedulerManager.run_instance()。由它运行虚拟机调度。
在token_id验证通过的情况下。nova-api的主要任务是资源配额和參数检查,并创建数据库。
假设你使用dashboard,此时你在页面上将会看到虚拟机处于scheduling状态。只是该状态持续时间非常短,差点儿察觉不到,除非nova-scheduler出问题了。
nova-scheduler
与nova-api提供外部服务不同。nova各组件之间相互调用,使用的是以rabbitmq作为消息队列的RPC调用。这里忽略RPC的实现细节。仅仅需知道rcp call调用哪个的节点的哪个服务就能够了。nova-scheduler的run_instance()从nova-api接收到的參数中仅仅使用到了request_spec和filter_properties,其余參数将直接传递给nova-compute去创建虚拟机。request_spec包括虚拟机的type、number、uuids等信息,在调度中须要用这些信息作为參考。
filter_properties包括指定调度规则信息,如force_hosts指定调度到特定的节点,ignore_hosts不调度到某些节点等。nova-scheduler在接收到nova-api的请求后。由nova.scheduler.filter_scheduler.FilterScheduler.scheduler_run_instances(),它主要完毕下面任务:
- 获取现存全部计算节点及其信息。调用nova.scheduler.host_manager.HostManager.get_all_host_states()获取。这些信息包含compute_nodes表中的信息,及由每一个nova-compute通过定时任务_publish_service_capabilitites上传的capabilities信息。包含cpu、vcpu、内存、磁盘等大小和使用情况。
- 使用指定的filter对上面返回的节点进行过滤,调用nova.scheduler.filters.HostFilterHander.get_filtered_hosts()实现。这些filter可通过配置项scheduler_default_filters指定,它能够包含nova.scheduler.filters中不论什么已经实现的filter。如RamFilter,它用来过滤内存不足以创建虚拟机的host。ComputeFilter用来过滤一些service无效的host。也能够在这里加入自己定义的filter。然后加入到scheduler_default_filters配置项中即可了。只是这里须要注意一点,通过force_hosts和forces_nodes指定的hosts。将不经过filter过滤。直接返回。
- 使用weighers对经过过滤的host进行排序,调用nova.scheduler.weights.HostWeightHander.get_weighed_objects()实现,在F版中仅仅实现了一个RamWeigher,仅仅以内存大小对hosts做了一个排序。
后面的版本号可能认为使用Weigher。又使用WeigherHander。仅仅是对内存大小做排序。有点小题大做了,在2012.2.4中用一个函数nova.scheduler.least_cost.weighted_sum()就实现排序。但在2013.2中又回到原来的结构了。 - 从排名前n个hosts中。随机选取一个用于创建虚拟机。n可通过配置项scheduler_host_subset_size设置,默认值为1。即选取最优节点进行创建。并更新scheduler保存host的资源信息,如内存、磁盘、vcpu等。使得调度下一个虚拟机时,使用的资源信息及时更新。
- 更新数据库设置instance的host=NULL。scheduled_at=now(),然后通过RPC调用上面选取host的nova-compute进行创建虚拟机。
nova-scheduler相对而言,逻辑比較简单。代码也不难。只是须要注意在node-scheduler运行过程中,不会改变虚拟机的vm_state和tast_state。
所以在horizon上也不会有变化。
nova-compute
nova-scheduler选定host后,随即通过rpc调用,调用host的nova-compute服务的nova.compute.manager.ComputeManager.run_instance()。
由它运行真正的创建虚拟机操作。只是在介绍之前,须要简单提及一下nova-compute的两个定时任务:
- update_available_resource。该任务首先获取当前计算节点的cpu个数、总内存大小、总磁盘大小,数据由nova.virt.libvirt..driver.LibvirtDriver.get_available_resource()获取。
然后从数据库中查找到执行在该节点上的全部虚拟机信息,统计出这些虚拟机所使用的vcpu个数、内存大小、磁盘大小。并将计算节点的总资源数量减去虚拟机使用的数量,得到空暇内存大小和空暇磁盘大小。然后更新数据库compute_node信息。这里须要注意从数据库中获取的虚拟机使用资源数量并非一定是计算节点消耗的资源数量,如1)虚拟机使用磁盘镜像为qcow2格式时,它的大小要小于或等于实际分配的大小。2)也有可能由于数据不同步,使得统计的数据与实际的资源使用不一致。 - _report_driver_status。
该任务是用于定时更新当前计算节点的capabilities信息,相同也是通过LibvirtDriver来获取资源数据的,只是在计算已使用资源方面,是直接使用通过调用multiprocessing、libvirt、os等返回的cpu个数、内存、磁盘使用情况。并附加上host_ip信息。并由定时任务_publish_service_capabilities通过rpc call转发到nova.scheduler.host_manager.HostManager.service_states中。
这两个定时任务为nova-scheduler提供了实时的host信息。所以才干实现准确调度。因为capabilities信息与compute_node表中信息有非常大的相似度,所以调度过程中非常少用到。
nova-scheduler调度到nova-compute的run_instance()主要完毕什么功能呢:
- 检查虚拟机是否已创建。及instance的image大小是否超过root的大小,超过则报错。
- 更新数据库,将instance的vm_state=BUILDING,task_state=NULL状态。horizon上面会有反应,但时间非常短。
- 给虚拟机分配资源,包含cpu、内存、磁盘等。更新数据库,设置instance的host字段。
- 分配网络。首先将instance的tast_state=NETWORKING,然后通过rpc调用nova-network的nova.network.manager.NetworkManager.allocate_for_instance(),返回网络信息。nova-network处理的具体将在以下的nova-network模块讨论。
- 建立块设备。将task_state=BLOCK_DEVICE_MAPPING。由于未给分配块设备,这步将不进行操作,有待后面讨论。
- 将task_state=SPAWNING,假设该类型的instance第一次在该计算节点上创建。该状态要持续几分钟。由于须要下载镜像。
- 依据上面配置的虚拟机信息,生成xml,写入libvirt,xml文件,生成console.log文件。
- 下载镜像,kernel、ramdisk、image,通过glance api下载。它们首先会被放在FLAGS.instances_path的_base文件夹下。然后copy一份到instance的文件夹以下。这样,假设这个计算节点上创建同样虚拟机时。首先查找_base中是否已经下载。假设已经下载过了,则直接copy就能够了。
对于image,一般採用qcow2格式,作为qemu-img的backing_file新建一个image使用,这样能够节约空间。 - 向下载过后的磁盘文件。注入指定的内容。如public_key、/etc/network/interfaces、rootpassword、指定的文件路径和内容、/etc/vmuuid等。
原理比較简单,将下载的image使用mount命令进行挂载,然后将要写入的内容下到特定的位置。 - 最后使用上面生成的xml,调用libvirt创建虚拟机,等待虚拟机正常执行。
- 更新数据库,将instance的vm_state=ACTIVE、task_state=None。
nova-compute做的事情还是挺多的,生成虚拟机xml配置文件、下载镜像并注入文件、调用libvirt创建虚拟机等。这里对于libvirt还需继续研究。
nova-network
这里network採用 FlatDHCP模式,multihost=True。查看代码可知 FlatDHCPManager继承RPCAllocateFixedIP, FloatingIP, NetworkManager三个class,依据python属性訪问流程(參考python对象之属性訪问流程)可知,调用FlatDHCPManger.allocate_for_instance()首先运行FloatingIP.allocate_for_instance(),再由它调用NetworkManger.allocate_for_instance()。主要完毕任务例如以下:
- 获取网络信息。用户可指定network_uuid,否则将直接获取networks表中全部network。
- 给每个network。在数据库创建一个virtual interface,给instance uuid和network id置为对应的值。注意virtual interface表与其他的表不太一样。当删除一个virtual interface时。将直接删除该记录。而不是将deleted=1。
- 从数据库中找出一个network id等于该network或为NULL的fixed ip。设置instance uuid、host、allocated=True、virtual_interface_id。更新dnsmasq的conf文件(默认在nova源代码文件夹下),同一时候给dnsmasq发送HUP信号,重读配置文件,当虚拟机动态获取IP时。dnsmasq依据接收到的mac,查询该配置文件。返回分配给该虚拟机的ip。这里须要注意下,dnsmasq的启动參数--dhcp-script=/usr/bin/nova-dhcpbridge。在虚拟机请求和释放ip时该脚本会被调用,用来设置fixed
ip的leased字段。
- 假设auto_assign_floating_ip为True,则给虚拟机分配floating ip。这里分配的floating ip原本不属于不论什么project。分配过后才设置它的project_id和auto_assigned字段。并将fixed_ip_id字段设为上面分配到fixed ip。
- 在对外出口网卡上绑定floating ip,设置iptables nat的nova-network-PREREOUTING、nova-network-OUTPUT和nova-network-float-snat表,做SNAT和DNAT转换。
这样就能够通过floating ip从外部訪问虚拟机了。 - 通过instance uuid查询数据库。获取它的vif、network、floating ip、fixed ip等信息,以nova.network.model.NetworkInfo结构构造。返回给nova-compute。