openstack之虚拟机的创建流程,openstack虚拟机


这篇博文静静的呆在草稿箱大半年了,如果不是因为某些原因被问到,以及因为忽略它而导致的损失,否则我也不知道什么时候会将它完成。感谢这段时间经历的挫折,让我知道不足,希望你能给我更大的决心!

本文试图详细地描述openstack创建虚拟机的完整过程,从用户发起请求到虚拟机成功运行,包括客户端请求的发出、keystone身份验证、nova-api接收请求、nova-scheduler调度、nova-computer创建、nova-network分配网络。对于每一个模块在创建虚拟机的过程中所负责的功能和执行的操作,进行较为详细描述和讨论。

为了方便描述,本文假设所有的服务ip地址为localhost,端口使用服务的默认设置端口。openstack为F版。下图为创建虚拟机的一个大概流程图,粗糙地表示下。接下来将对每一个模块进行详细介绍,如有错误,欢迎拍砖!



客户端

创建虚拟机的第一步是需要客户端调用nova-api,发送创建虚拟机请求。目前openstack提供两种客户端:

1 命令行指令nova,通过指定命令行参数,就可以调用nova-api创建虚拟机,一个最简单的创建虚拟机指令如下:

nova boot vm_name --flavor flavor_id --image image_uuid

2 网页交互页面horizon,这是通过web操作页面来调用nova-api创建虚拟机,比较简单易用。选定相关参数后,点击create就可以了。

这两种客户端除了UI不一样以外,功能方面基本都是一样。就创建虚拟机来讲,它们需要完成:

  • 用户身份验证。客户端构造一个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。身份验证通过后,客户端才可对nova-api发送创建请求。因为客户端只配置了keystone的服务地址和端口,对于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()发送创建虚拟机请求。

可以看出,无论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

从上面可知,在客户端发起创建虚拟机请求时,keystone需要对客户端的用户名和密码进行验证。keystone-all与nova-api一样,api的发布没有采用任何框架,而是使用router、paste类库,从头写的,实现风格上又与nova-api有点差异。keystone-all服务会监听两个端口:localhost:5000,即publicURL,一般用户使用密码可以访问;localhost:35357,即adminURL,只能使用admin账户和密码访问。在创建虚拟机流程中,调用的keystone的api有两个(不过,每次客户端请求基本都会调用这两个api):

1 http://localhost:5000/v2.0/tokens,客户端请求该api来获取token_id和serviceCatalog。keystone会将该api的请求交给keystone.TokenController.authenticate()处理。该函数主要完成:

  •  对于客户端传过来的name、password、tenant_id进行验证。在keystone的数据库user表中保存了user相关信息,包括password加密后的hash值。password的加密使用了sha512算法,由passlib库提供。密码验证如下:passlib.hash.sha512_crypt.verify(password, hashed)。
  • 根据CONF.signing.token_format配置项,为客户端的每一次请求生成一个token_id。目前支持的选择有两个UUID、PKI,默认为UUID。所以在默认情况下,一个token_id就是一个随机产生的uuid。token_id有一个有效时间,从token_id的生成的时刻开始算起。有效时间可通过CONF.token.expiration配置项设置,默认值为86400s。
  • 构建serviceCatalog,这里面就是一堆endpoints,包括nova-api、glance-api、cinder-api等。根据配置项CONF.catalog.driver,可以从数据库的endpoint表中获取,也可以从模板文件中获取。

2 http://localhost:35357/v2.0/tokens/token_id,对请求的token_id进行验证。nova-api接收到请求后,首先使用请求携带的token_id来访问该api,以验证请求是否有效,当然nova-api需要在body里加上admin的账户和密码信息,这些信息需要在api-paste.ini中配置。glance-api、cinder-api等在接收到客户端请求后,都会调用该api对客户端的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是:

http://localhost:8774/v2/project_id/servers,由osapi_compute服务发布。该api是RESTFUL的,以POST方法请求该api,经过几层middleware处理后,最终交给nova.api.openstack.compute.servers.Controller.create()处理。它们主要完成以下功能:

  • 验证客户端的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。
  • 通过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的两个定时任务:

这两个定时任务为nova-scheduler提供了实时的host信息,这样才能实现准确调度。下面介绍下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下载。它们首先会被放在_base目录下,然后copy一份到instance的目录下面。这样,如果这个计算节点上创建相同虚拟机时,首先查找_base中是否已经下载,如果已经下载过了,则直接copy就可以了。对于image,一般采用qcow2格式,作为qemu-img的backing_file新建一个image使用,这样可以节约空间。
  • 最后使用上面生成的xml,调用libvirt创建虚拟机。
nova-network nova-network是nova-compute在创建虚拟机之前,被其调用nova.network.manager.FlatDHCPManger.allocate_for_instance(),为虚拟机分配fixed ip和floating ip(如果auto_assign_floating_ip为True),并返回网络信息。这里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。





以openStack为例,简述IaaS的工作流程

用户验证,资源配额分配,创建实例,nova-network分配IP地址,scheduler根据计算节点查看负载情况选择虚拟机所属服务器,检查本地是否有虚拟机模板,(如果没有)从glance下载模板,利用copy on write 创建虚拟机系统镜像,生成虚拟机配置文件(包含dhcp获取的ip地址),启动虚拟机,用户可以通过web端 的novnc查看虚拟机系统内信息。web端提供块存储、快照、重启等操作。。。一般是按时计费,按系统配置选取套餐类型。。。
 

OpenStack创建虚拟机VM报错 Instance building 0% complete Error building instance

先查看一下scheduler的日志,是否分配到compute,然后查看一下compute的日志。
可能存在的原因就是存储空间不足吧,nova-network的ip无法分配也可能导致创建失败。。。
你先查看一下,然后再追问吧
 

相关内容