Placement 深度解析:OpenStack 资源追踪与调度优化

Placement 深度解析:OpenStack 资源追踪与调度优化

为什么需要 Placement

在 Placement 出现之前(Pike 版本独立),Nova Scheduler 直接查询每个 nova-compute 节点的资源状态,存在以下问题:

  • 调度时需要查询所有 compute 节点,性能差
  • 资源类型固定(CPU/RAM/Disk),无法扩展
  • 无法精确追踪 NUMA、GPU、SR-IOV 等复杂资源
  • 资源分配没有原子性保证,可能超分

Placement 通过集中式资源库存管理解决了这些问题。


核心概念

资源提供者(Resource Provider)

1
2
3
4
5
6
7
8
9
10
11
ResourceProvider(资源提供者)
├── 代表一个可以提供资源的实体
├── 可以是:计算节点、NUMA 节点、GPU、SR-IOV PF
└── 支持树形结构(嵌套资源提供者)

示例:
compute-node-1(根 RP)
├── compute-node-1_NUMA0(NUMA 节点 RP)
│ ├── compute-node-1_NUMA0_PF0(SR-IOV 物理函数 RP)
│ └── compute-node-1_NUMA0_GPU0(GPU RP)
└── compute-node-1_NUMA1(NUMA 节点 RP)

资源类(Resource Class)

1
2
3
4
5
6
7
8
9
10
11
12
13
# 标准资源类(nova/objects/fields.py)
VCPU # 虚拟 CPU
MEMORY_MB # 内存(MB)
DISK_GB # 磁盘(GB)
VGPU # 虚拟 GPU
SRIOV_NET_VF # SR-IOV 虚拟函数
PCI_DEVICE # PCI 设备
NUMA_TOPOLOGY # NUMA 拓扑
FPGA # FPGA 加速器

# 自定义资源类(以 CUSTOM_ 开头)
CUSTOM_BAREMETAL_SMALL
CUSTOM_LICENSE_WINDOWS

库存(Inventory)

1
2
3
4
5
6
7
8
9
10
11
12
每个 ResourceProvider 对每种 ResourceClass 维护库存:

Inventory {
resource_provider: compute-node-1
resource_class: VCPU
total: 32 # 物理 CPU 总数
reserved: 2 # 系统保留
min_unit: 1 # 最小分配单位
max_unit: 32 # 最大分配单位
step_size: 1 # 步长
allocation_ratio: 16.0 # 超分比(32 * 16 = 512 可分配)
}

分配(Allocation)

1
2
3
4
5
6
Allocation {
resource_provider: compute-node-1
resource_class: VCPU
consumer_uuid: <instance-uuid>
used: 4 # 该实例使用 4 个 vCPU
}

数据模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 资源提供者
resource_providers (uuid, name, generation, parent_provider_id)

-- 库存
inventories (resource_provider_id, resource_class_id,
total, reserved, min_unit, max_unit,
step_size, allocation_ratio)

-- 分配记录
allocations (resource_provider_id, resource_class_id,
consumer_id, used)

-- 特征(Trait)
traits (name) -- e.g. HW_CPU_X86_AVX2, STORAGE_DISK_SSD
resource_provider_traits (resource_provider_id, trait_id)

调度流程中的 Placement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# nova/scheduler/filter_scheduler.py

def select_destinations(self, context, spec_obj, ...):
# 1. 构建资源请求
request_groups = spec_obj.requested_resources
# e.g. [{VCPU: 4, MEMORY_MB: 8192, DISK_GB: 50}]

# 2. 调用 Placement API 获取候选 RP
alloc_reqs, provider_summaries, allocation_request_version = \
self.placement_client.get_allocation_candidates(
context, request_groups
)
# Placement 返回满足资源需求的 RP 列表

# 3. 过滤(Filter)
hosts = self._get_filtered_hosts(hosts, spec_obj, ...)

# 4. 打分(Weigh)
weighed_hosts = self._get_weighed_hosts(hosts, spec_obj)

# 5. 原子性分配资源(claim)
self.placement_client.claim_resources(
context, instance_uuid, alloc_req
)

Placement API 查询示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 查询满足条件的资源提供者
GET /placement/allocation_candidates?
resources=VCPU:4,MEMORY_MB:8192,DISK_GB:50
&required=HW_CPU_X86_AVX2 # 必须有 AVX2 指令集
&forbidden=CUSTOM_MAINTENANCE # 排除维护中的节点
&group_policy=isolate # 不同资源组来自不同 RP

# 响应
{
"allocation_requests": [
{
"allocations": {
"compute-node-1-uuid": {
"resources": {"VCPU": 4, "MEMORY_MB": 8192, "DISK_GB": 50}
}
}
}
],
"provider_summaries": {
"compute-node-1-uuid": {
"resources": {
"VCPU": {"capacity": 512, "used": 100},
"MEMORY_MB": {"capacity": 524288, "used": 65536}
}
}
}
}

特征(Trait)机制

Trait 是资源提供者的能力标签,用于精细化调度:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 查看所有标准 Trait
openstack trait list | grep HW_CPU

# 给宿主机打 Trait
openstack resource provider trait set \
--trait HW_CPU_X86_AVX2 \
--trait HW_CPU_X86_AVX512F \
--trait STORAGE_DISK_SSD \
<compute-node-uuid>

# Flavor 要求特定 Trait
openstack flavor set gpu-flavor \
--property trait:CUSTOM_GPU_NVIDIA_A100=required

嵌套资源提供者(Nested RP)

用于精确建模复杂硬件资源:

1
2
3
4
5
6
7
8
9
10
11
12
compute-node-1
├── VCPU: 64, MEMORY_MB: 262144

├── NUMA_NODE_0
│ ├── VCPU: 32, MEMORY_MB: 131072
│ └── GPU_0
│ └── VGPU: 8 (每个 GPU 切分 8 个 vGPU)

└── NUMA_NODE_1
├── VCPU: 32, MEMORY_MB: 131072
└── SRIOV_PF_0
└── SRIOV_NET_VF: 16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# nova/virt/libvirt/driver.py
# nova-compute 上报嵌套 RP 给 Placement

def update_provider_tree(self, provider_tree, nodename, ...):
# 上报 NUMA 拓扑
for numa_node in self._get_numa_topology():
provider_tree.new_child(
name=f'{nodename}_NUMA{numa_node.id}',
parent=nodename,
uuid=generate_uuid()
)
provider_tree.update_inventory(
f'{nodename}_NUMA{numa_node.id}',
{
'VCPU': {'total': numa_node.cpus, ...},
'MEMORY_MB': {'total': numa_node.memory_mb, ...}
}
)

源码关键路径

1
2
3
4
5
6
7
8
9
10
11
12
13
placement/
├── api/ # Flask REST API
│ └── handlers/
│ ├── resource_providers.py # RP CRUD
│ ├── inventories.py # 库存管理
│ ├── allocations.py # 分配管理
│ └── allocation_candidates.py # 核心:候选查询
├── db/
│ └── sqlalchemy/
│ └── api.py # 数据库操作
└── objects/
├── resource_provider.py
└── allocation_candidate.py # 候选算法

生产运维

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看资源使用情况
openstack resource provider list
openstack resource provider show <uuid>
openstack resource provider inventory list <uuid>
openstack resource provider usage show <uuid>

# 查看某实例的资源分配
openstack resource provider allocation show <instance-uuid>

# 修复不一致的分配(nova-manage 工具)
nova-manage placement heal_allocations --verbose

# 同步 compute 节点资源到 Placement
nova-manage placement sync_aggregates

常见问题

问题 原因 解决
调度失败 No valid host Placement 中资源不足或 Trait 不匹配 检查 allocation_candidates API 返回
资源泄漏 VM 删除后 Allocation 未清理 运行 heal_allocations
超分比不生效 Inventory 的 allocation_ratio 未更新 重启 nova-compute 触发上报