Nova 深度解析:OpenStack 计算服务核心原理与二次开发

Nova 深度解析:OpenStack 计算服务核心原理与二次开发

架构总览

Nova 是 OpenStack 最核心的组件,负责虚拟机的全生命周期管理。它本身是一个分布式系统,由多个进程协作完成工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
外部请求


nova-api ← REST API 入口,参数校验,配额检查
│ RPC call

nova-conductor ← 数据库操作代理,防止 compute 直连 DB
│ RPC call

nova-scheduler ← 选择目标宿主机(FilterScheduler)
│ RPC cast

nova-compute ← 实际执行,调用 libvirt/hypervisor

├── nova-novncproxy ← VNC 控制台代理
└── nova-metadata ← 虚拟机元数据服务(cloud-init 用)

进程职责

进程 职责 是否有状态
nova-api HTTP API 服务,写 DB 创建 instance 记录 无状态,可多实例
nova-conductor DB 操作代理,复杂编排逻辑 无状态,可多实例
nova-scheduler 资源过滤与打分,选宿主机 无状态,可多实例
nova-compute 宿主机上的 agent,管理本机 VM 有状态,每宿主机一个

Cell v2 架构

从 Queens 版本起,Nova 正式采用 Cell v2 架构解决大规模集群的扩展性问题:

1
2
3
4
5
6
7
8
9
nova_api DB(全局)
├── cell_mappings 表(记录所有 Cell
└── instance_mappings 表(记录 VM 在哪个 Cell

nova_cell0 DB(失败的调度记录)

nova_cell1 DBCell 1 的 VM 数据)
nova_cell2 DBCell 2 的 VM 数据)
...
1
2
3
4
5
6
7
8
9
10
11
12
13
nova-api(全局)

├── nova-conductor(cell0,处理调度失败)

├── Cell 1
│ ├── nova-conductor
│ ├── nova-scheduler
│ └── nova-compute × N

└── Cell 2
├── nova-conductor
├── nova-scheduler
└── nova-compute × N

核心优势:每个 Cell 独立的 DB 和 MQ,单 Cell 故障不影响其他 Cell,支持万级宿主机规模。


虚拟机创建完整流程

源码追踪:_build_and_run_instance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# nova/compute/manager.py(最核心的文件,~8000行)

@wrap_exception()
def build_and_run_instance(self, context, instance, ...):
# 1. 准备网络(调用 Neutron)
network_info = self._build_networks_for_instance(context, instance, ...)

# 2. 准备块设备(调用 Cinder/Glance)
block_device_info = self._prep_block_device(context, instance, ...)

# 3. 调用 virt driver 创建虚拟机
self.driver.spawn(
context, instance, image_meta,
injected_files, admin_password,
network_info=network_info,
block_device_info=block_device_info
)

libvirt Driver 层

1
2
3
4
5
6
7
8
9
10
11
12
# nova/virt/libvirt/driver.py

def spawn(self, context, instance, image_meta, ...):
# 1. 下载镜像到本地(imagebackend)
self._create_image(context, instance, disk_info, ...)

# 2. 生成 libvirt XML 配置
xml = self._get_guest_xml(context, instance, network_info, ...)

# 3. 调用 libvirt API 创建域(虚拟机)
guest = self._create_guest(xml, ...)
guest.launch()

libvirt XML 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<domain type='kvm'>
<name>instance-00000001</name>
<memory unit='KiB'>2097152</memory>
<vcpu placement='static'>2</vcpu>
<os>
<type arch='x86_64' machine='pc-i440fx-2.11'>hvm</type>
<boot dev='hd'/>
</os>
<devices>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='none'/>
<source file='/var/lib/nova/instances/uuid/disk'/>
<target dev='vda' bus='virtio'/>
</disk>
<interface type='bridge'>
<source bridge='brq...'/>
<model type='virtio'/>
</interface>
</devices>
</domain>

调度器深度解析

FilterScheduler 工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# nova/scheduler/filter_scheduler.py

def select_destinations(self, context, spec_obj, ...):
# 1. 从 Placement 获取候选宿主机列表
alloc_reqs, provider_summaries = self._get_all_host_states(...)

# 2. 依次通过所有 Filter
hosts = self.host_manager.get_filtered_hosts(hosts, spec_obj)

# 3. 对通过 Filter 的宿主机打分(Weigh)
weighed_hosts = self.host_manager.get_weighed_hosts(hosts, spec_obj)

# 4. 返回得分最高的宿主机
return weighed_hosts[:num_instances]

内置 Filters

Filter 作用
ComputeFilter 过滤掉 nova-compute 服务不可用的节点
RamFilter 内存不足的节点过滤掉
DiskFilter 磁盘不足的节点过滤掉
CoreFilter CPU 不足的节点过滤掉
AvailabilityZoneFilter 按 AZ 过滤
ComputeCapabilitiesFilter 按宿主机 extra_specs 过滤
ImagePropertiesFilter 按镜像属性过滤(如 hw_architecture)
NUMATopologyFilter NUMA 拓扑感知调度
AggregateInstanceExtraSpecsFilter 按主机聚合的 metadata 过滤

自定义 Filter 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 自定义:只调度到有 GPU 标签的宿主机
from nova.scheduler import filters

class GPUFilter(filters.BaseHostFilter):
"""只选择有 GPU 的宿主机"""

def host_passes(self, host_state, spec_obj):
# 检查 flavor extra_specs 是否要求 GPU
extra_specs = spec_obj.flavor.extra_specs
if extra_specs.get('resources:VGPU', 0) == 0:
return True # 不需要 GPU,所有节点都通过

# 检查宿主机是否有 GPU 资源
return host_state.stats.get('has_gpu', False)

热迁移(Live Migration)原理

热迁移是生产中最复杂、最容易出问题的操作。

迁移流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
源宿主机 nova-compute          目标宿主机 nova-compute
│ │
1. pre_live_migration() │
│ ──────────────────────────► │
│ │ 准备网络、磁盘
2. 开始内存传输(libvirt) │
│ ══════════════════════════► │ 持续同步内存脏页
│ │
3. 内存脏页收敛后,暂停源VM │
│ ──────────────────────────► │
│ │ 最后一次内存同步
4. post_live_migration() │
│ ◄────────────────────────── │
│ │
5. 清理源 VM │

关键源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# nova/compute/manager.py

def live_migration(self, context, dest, instance, ...):
# 迁移前检查
self._live_migration_operation(context, instance, dest, ...)

def _do_live_migration(self, context, dest, instance, ...):
# 调用 libvirt 执行实际迁移
self.driver.live_migration(
context, instance, dest,
post_method=self.post_live_migration,
recover_method=self.rollback_live_migration,
...
)

迁移失败常见原因

原因 排查方式
目标节点内存不足 检查 Placement 资源分配
网络不通 检查两节点间 libvirt migration port(16509/49152-49261)
共享存储未挂载 检查 NFS/Ceph 挂载状态
CPU 型号不兼容 检查 cpu_mode 配置,建议用 host-model
内存脏页无法收敛 VM 内存写入过于频繁,考虑冷迁移

虚拟机状态机

1
2
3
4
5
6
7
BUILD ──► ACTIVE ──► SHUTOFF
│ │ │
│ REBOOT REBUILD
│ │ │
│ MIGRATING RESIZING
│ │ │
└──► ERROR ◄──────────┘

状态存储在 instances.vm_stateinstances.task_state 两个字段:

  • vm_state:稳定状态(active/stopped/error)
  • task_state:过渡状态(spawning/migrating/deleting)

生产级二次开发实践

1. 扩展虚拟机元数据

1
2
3
# 在 nova/objects/instance.py 中扩展字段
# 在 nova/db/main/models.py 中添加数据库列
# 通过 Alembic migration 变更 schema

2. 自定义虚拟机创建 Hook

1
2
3
4
5
6
7
8
# nova/compute/manager.py 中的关键 Hook 点
# 使用 oslo.middleware 或直接修改 manager.py

def _build_and_run_instance(self, context, instance, ...):
# 在 spawn 前后插入自定义逻辑
self._pre_create_hook(instance)
self.driver.spawn(...)
self._post_create_hook(instance)

3. 性能优化关键配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# nova.conf
[DEFAULT]
# compute 节点并发创建 VM 数量
max_concurrent_builds = 10

# 调度器缓存时间(减少 Placement 查询频率)
[scheduler]
discover_hosts_in_cells_interval = 300

# libvirt 连接池
[libvirt]
num_pcie_ports = 28
virt_type = kvm
cpu_mode = host-passthrough # 性能最好,但迁移兼容性差

关键监控指标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Prometheus 告警规则示例
groups:
- name: nova
rules:
- alert: NovaComputeDown
expr: openstack_nova_agent_state{service="nova-compute"} == 0
for: 2m
labels:
severity: critical

- alert: NovaHighBuildFailureRate
expr: rate(openstack_nova_server_status{status="ERROR"}[5m]) > 0.1
for: 5m
labels:
severity: warning

- alert: NovaSchedulerNoValidHost
expr: increase(nova_scheduler_attempts_total{result="failure"}[5m]) > 10
labels:
severity: warning