创建虚拟机的过程是一个经典的分布式、异步、状态驱动的工作流。其核心设计哲学是:API 接收请求 -> 调度决策 -> 资源分配 -> 虚拟化层执行。整个过程涉及多个 Nova 服务以及外部组件(Glance, Neutron, Cinder, Keystone)。
1、基本流程
sequenceDiagram participant User participant nova-api participant DB as Nova DB participant MQ as Message Queue participant Scheduler participant Conductor participant Compute participant Libvirt participant Glance participant Neutron participant Cinder User->>nova-api: 1. POST /servers (创建实例) nova-api->>DB: 2. 创建初始实例记录(state: BUILDING) nova-api->>MQ: 3. cast(run_instance to scheduler) Scheduler->>MQ: 4. 获取消息 Scheduler->>DB: 5. 查询资源 Scheduler->>MQ: 6. cast(build_and_run_instance to compute.X) Compute->>MQ: 7. 获取消息 Compute->>Conductor: 8. 请求资源(镜像、网络等) Conductor->>DB: 9. 查询数据 Conductor-->>Compute: 10. 返回信息 Compute->>Glance: 11. 下载镜像 Compute->>Neutron: 12. 分配网络 Compute->>Cinder: 13. 挂接卷(如适用) Compute->>Libvirt: 14. 定义并启动域(XML) Libvirt-->>Compute: 15. 成功 Compute->>DB: 16. 更新状态(state: ACTIVE) Compute->>MQ: 17. 发送状态事件
流程可分为三大阶段:
- API 阶段 (nova-api):接收并验证请求,创建数据库记录,立即返回响应,将后续任务异步下发。
- 调度阶段 (nova-scheduler):决策虚拟机在哪个计算节点上启动。
- 构建阶段 (nova-compute):与各类服务交互准备资源,调用 Hypervisor 真正创建虚拟机。
2、详细流程源码分析
2.1 API请求入口
nova/api/openstack/compute/servers.py
# nova/api/openstack/compute/servers.py (Caracal版本) class ServersController(wsgi.Controller): @wsgi.response(202) @validation.schema(schema_server_create_v242, '2.42') def create(self, req, body): """处理创建虚拟机API请求""" context = req.environ['nova.context'] server = body['server'] # 1. 参数校验与提取 name = server['name'] image_ref = server.get('imageRef') flavor_ref = server.get('flavorRef') # 2. 创建实例对象 instance = objects.Instance(context=context) instance.display_name = name instance.image_ref = image_ref # 3. 调用Compute API (instances, resv_id) = self.compute_api.create( context, instance_type=flavor_ref, image_href=image_ref, # ...其他参数 ) # 4. 返回202 Accepted响应 return wsgi.ResponseObject({}, status=202)
2.1 核心创建逻辑
nova/compute/api.py
# nova/compute/api.py (Caracal版本) class API(base.Base): @check_instance_create @wrap_exception() def create(self, context, instance_type, image_href, **kwargs): # 1. 配额检查(使用Placement API) self._check_quotas(context, requested_cores, requested_ram, ...) # 2. 创建RequestSpec对象 request_spec = objects.RequestSpec( context=context, flavor=flavor, image=image_meta, num_instances=num_instances, # ...其他参数 ) # 3. 安全组处理(使用Neutron API) security_groups = self._get_requested_security_groups(context, ...) # 4. 异步调用Conductor self.compute_task_api.schedule_and_build_instances( context, request_spec=request_spec, security_groups=security_groups, # ...其他参数 )
2.3 调度决策
nova/scheduler/manager.py
# nova/scheduler/manager.py (Caracal版本) class SchedulerManager(manager.Manager): @messaging.expected_exceptions(exception.NoValidHost) def select_destinations(self, context, request_spec, ...): # 1. 获取所有主机状态(从Placement) hosts = self.host_manager.get_all_host_states(context) # 2. 应用过滤器链 filter_properties = self._get_filter_properties(request_spec) hosts = self.host_manager.get_filtered_hosts(hosts, filter_properties) # 3. 权重计算 weighed_hosts = self.host_manager.get_weighed_hosts( hosts, request_spec, filter_properties) # 4. 选择目标主机 selections = [] for i in range(request_spec.num_instances): if not weighed_hosts: raise exception.NoValidHost(reason="") chosen_host = weighed_hosts.pop(0).obj selections.append(chosen_host) return selections
2.4 计算节点执行
nova/compute/manager.py
# nova/compute/manager.py (Caracal版本) class ComputeManager(manager.Manager): @wrap_exception() @wrap_instance_fault def build_and_run_instance(self, context, instance, request_spec, ...): # 1. 准备网络(使用Neutron API) network_info = self.network_api.allocate_for_instance( context, instance, requested_networks, ...) # 2. 处理块设备(使用Cinder API) block_device_info = self._prep_block_device( context, instance, bdms, ...) # 3. 获取镜像元数据 image_meta = self._get_image_metadata(context, instance.image_ref) # 4. 调用Driver创建虚拟机 self.driver.spawn( context, instance, image_meta, injected_files=injected_files, admin_password=admin_password, network_info=network_info, block_device_info=block_device_info) # 5. 更新实例状态 instance.vm_state = vm_states.ACTIVE instance.task_state = None instance.save()
2.5 Hypervisor交互
nova/virt/libvirt/driver.py
# nova/virt/libvirt/driver.py (Caracal版本) class LibvirtDriver(driver.ComputeDriver): def spawn(self, context, instance, image_meta, **kwargs): # 1. 准备磁盘(支持多种后端) disk_info = self._create_image(context, instance, image_meta) # 2. 生成安全启动配置 secure_boot = self._get_secure_boot_config(instance) # 3. 生成Libvirt XML # /var/lib/nova/instances/<instance-uuid>/libvirt.xml xml = self._get_guest_xml( context, instance, disk_info, network_info=kwargs['network_info'], block_device_info=kwargs['block_device_info'], secure_boot=secure_boot) # 4. 定义并启动虚拟机 guest = self._host.get_guest() guest.create(xml, flags=libvirt.VIR_DOMAIN_START_PAUSED) # 5. 注入元数据 self._inject_data(instance, network_info=kwargs['network_info']) # 6. 恢复虚拟机运行 guest.resume()
3、常见问题
3.1 查看实例错误状态
第一步永远是查看实例的详细状态
openstack server show b2c3d4e5-f6g7-8901-bcde-f12345678901 -c status -c fault
+--------+----------------------------------------------------------------------------------------------------+ | Field | Value | +--------+----------------------------------------------------------------------------------------------------+ | fault | { | | | "message": "No valid host was found. There are not enough hosts available.", | | | "code": 500, | | | "details": "Exceeded maximum number of retries. Exception: No valid host was found. ...", | | | "created": "2025-09-15T10:23:12Z" | | | } | | status | ERROR | +--------+----------------------------------------------------------------------------------------------------+
fault 字段通常会给出比较直接的错误原因。
3.2 根据状态定位问题阶段
-
长时间处于
SCHEDULING状态:- 问题:
nova-scheduler无法找到合适的主机。 - 排查:
- 资源不足:检查目标主机是否有足够的 CPU、内存、磁盘。使用
openstack hypervisor stats show和openstack hypervisor show <hypervisor-id>。 - 过滤器导致:检查
nova-scheduler日志,看主机是如何被过滤掉的。常见于强制策略(如DifferentHostFilter找不到另一个实例)。 - Placement 数据不同步:运行
nova-manage placement sync同步数据。
- 资源不足:检查目标主机是否有足够的 CPU、内存、磁盘。使用
- 问题:
-
长时间处于
SPAWNING状态或最终变为ERROR:- 问题:
nova-compute在目标节点上构建失败。 - 排查:
- 查看计算节点日志:这是最重要的步骤!日志路径通常为
/var/log/nova/nova-compute.log。 - 镜像下载失败:Glance 镜像 URL 不可达、镜像格式不支持(如 raw, qcow2)、磁盘空间不足(检查
/var/lib/nova/instances/_base)。 - 网络问题:Neutron 无法分配端口(如 IP 耗尽、安全组规则错误)。查看 Neutron 相关日志 (
/var/log/neutron/*.log)。 - Hypervisor 问题:Libvirt 权限问题(检查
libvirtd进程和/var/lib/libvirt/权限)、QEMU 进程启动失败(检查ps aux | grep qemu)、SElinux 或 AppArmor 策略限制。 - 卷挂载失败:Cinder Volume 状态不是
available或连接器(connector)信息有误。
- 查看计算节点日志:这是最重要的步骤!日志路径通常为
- 问题:
-
立即变为
ERROR:- 问题:通常发生在 API 或调度阶段,是前置检查失败。
- 排查:
- 查看 API 节点日志:
/var/log/nova/nova-api.log。 - 配额不足:检查项目配额
openstack quota show <project>。 - 参数无效:指定的 Flavor、Image、Network 不存在或不可见。
- Keystone 认证失败:Token 过期或权限不足。
- 查看 API 节点日志:
3.3 核心排查工具与命令
- 日志!:
tail -f /var/log/nova/nova-*.log。使用grep <instance-uuid>过滤特定实例的日志,这是最强大的工具。 - 虚拟化层检查:
- 登录到计算节点,检查 Libvirt 域:
virsh list --all(看实例是否存在)。 - 如果存在但有问题,查看其定义:
virsh dumpxml <instance-name>。 - 查看虚拟机控制台日志:
virsh console <instance-name>(需在镜像内启用 console)。
- 登录到计算节点,检查 Libvirt 域:
- 网络命名空间:Neutron 使用 Linux Network Namespace。在计算节点上,使用
ip netns list找到实例相关的命名空间(如qrouter-或qdhcp-),然后ip netns exec <ns-name> bash进入命名空间内部调试网络(ping,ip addr)。