一、概述
OpenStack 采用 UUID 作为实例的原生标识符,但为了兼容 AWS EC2 API,实现了一套 UUID 到短格式 EC2 ID 的映射机制。
二、两种 ID 格式
2.1 OpenStack 原生格式(UUID)
- 格式:标准 UUID(36字符)
- 示例 :
4daf043f-cc30-4513-b639-84906a4278e1 - 特点:全局唯一、易于分布式系统使用
- 存储位置 :
instances表的主键
2.2 EC2 兼容格式
- 格式 :
i-+ 8位十六进制数 - 示例 :
i-00000004 - 特点:短小、易读,符合 AWS EC2 规范
- 生成方式:通过映射表的自增整数转换而来
三、映射表结构
3.1 数据库表定义
sql
-- 表名:instance_id_mappings
-- 位置:nova/db/main/models.py:1093-1102
CREATE TABLE instance_id_mappings (
id INT AUTO_INCREMENT PRIMARY KEY, -- 自增整数,用于生成 EC2 ID
uuid VARCHAR(36) NOT NULL, -- 指向 instances.uuid
created_at DATETIME,
updated_at DATETIME,
deleted_at DATETIME,
deleted INT DEFAULT 0,
INDEX ix_instance_id_mappings_uuid (uuid)
);
3.2 实际数据示例
css
+----+--------------------------------------+
| id | uuid |
+----+--------------------------------------+
| 1 | a0364b6e-61c7-477a-85e5-721074cfbbf3 | → i-00000001
| 2 | 4daf043f-cc30-4513-b639-84906a4278e1 | → i-00000002
| 4 | 66baa731-8d64-4d0b-85ad-45d404cd67f9 | → i-00000004
+----+--------------------------------------+
注意:ID 是自增的,可能不连续(如这里没有 3)
四、转换流程详解
4.1 UUID → EC2 ID(正向转换)
步骤 1:虚拟机访问元数据服务
ruby
# 虚拟机内部执行
curl http://169.254.169.254/latest/meta-data/instance-id
步骤 2:元数据服务处理请求
ruby
# 文件:nova/api/metadata/base.py:254-262
def get_ec2_metadata(self, version):
meta_data = {
'instance-id': self.instance.ec2_ids.instance_id, # ← 调用 ec2_ids
# ...
}
步骤 3:获取 EC2 ID
python
# 文件:nova/objects/ec2.py:214-217
@staticmethod
def _get_ec2_ids(context, instance):
ec2_ids = {}
# 将 UUID 转换为 EC2 格式的 instance-id
ec2_ids['instance_id'] = id_to_ec2_inst_id(context, instance.uuid)
步骤 4:UUID 转整数 ID
python
# 文件:nova/objects/ec2.py:52-60
def id_to_ec2_inst_id(context, instance_id):
"""从 UUID 获取 EC2 格式的 instance-id"""
if uuidutils.is_uuid_like(instance_id):
# 查询或创建映射记录
int_id = get_int_id_from_instance_uuid(context, instance_id)
return id_to_ec2_id(int_id) # 转换为 i-xxxxxxxx 格式
步骤 5:查询或创建映射(懒加载)
python
# 文件:nova/objects/ec2.py:64-74
@memoize # 缓存7天
def get_int_id_from_instance_uuid(context, instance_uuid):
try:
# 先尝试查询已有映射
imap = EC2InstanceMapping.get_by_uuid(context, instance_uuid)
return imap.id # 返回整数 ID,如 4
except exception.NotFound:
# 首次访问,创建新映射(自增 ID)
imap = EC2InstanceMapping(context)
imap.uuid = instance_uuid
imap.create() # INSERT,数据库自动分配 ID
return imap.id
步骤 6:格式化为 EC2 ID
python
# 文件:nova/objects/ec2.py:47-49
def id_to_ec2_id(instance_id, template='i-%08x'):
"""将整数 ID 转换为 EC2 格式"""
return template % int(instance_id) # 4 → 'i-00000004'
步骤 7:返回结果
css
i-00000004
4.2 EC2 ID → UUID(反向查询)
python
# 文件:nova/objects/ec2.py:150-153
@base.remotable_classmethod
def get_by_id(cls, context, ec2_id):
"""通过 EC2 整数 ID 查询 UUID"""
# ec2_id = 4
db_imap = db.ec2_instance_get_by_id(context, ec2_id)
return cls._from_db_object(context, cls(), db_imap)
流程:
ini
'i-00000004' → 解析为 4 → 查询 instance_id_mappings WHERE id=4
→ 得到 uuid='4daf043f-cc30-4513-b639-84906a4278e1'
五、为什么需要映射?
5.1 历史原因
- 早期 OpenStack(Essex 之前):使用整数 ID
- Essex 版本开始:改用 UUID,但需要保持 EC2 兼容性
- 映射表:作为兼容层,连接新旧系统
5.2 EC2 兼容性
AWS EC2 的元数据服务使用 i-xxxxxxxx 格式,许多 cloud-init、监控工具、脚本依赖这个格式。
5.3 API 兼容性
OpenStack 的 EC2 API(已弃用但仍有用户)需要短格式 ID。
六、两种元数据端点对比
6.1 EC2 兼容端点
ruby
curl http://169.254.169.254/latest/meta-data/instance-id
# 输出:i-00000004
特点:
- 返回短格式 EC2 ID
- 兼容 AWS EC2 工具
- 需要映射表支持
6.2 OpenStack 原生端点
ruby
curl http://169.254.169.254/openstack/latest/meta_data.json
输出:
json
{
"uuid": "4daf043f-cc30-4513-b639-84906a4278e1",
"name": "demo2",
"hostname": "demo2.novalocal",
"project_id": "95a4115a122d4184ba5b8c7836fcc770",
...
}
特点:
- 返回真实 UUID
- 包含更多 OpenStack 原生信息
- 不需要映射表
七、关键代码位置
| 功能 | 文件路径 | 行号 | 说明 |
|---|---|---|---|
| 映射表模型 | nova/db/main/models.py |
1093-1102 | InstanceIdMapping 表定义 |
| EC2 ID 格式化 | nova/objects/ec2.py |
47-49 | id_to_ec2_id() 函数 |
| UUID→整数 | nova/objects/ec2.py |
64-74 | get_int_id_from_instance_uuid() |
| 元数据返回 | nova/api/metadata/base.py |
258 | instance-id 字段设置 |
| 数据库操作 | nova/db/main/api.py |
4106-4142 | EC2 映射 CRUD |
八、性能优化
8.1 缓存机制
python
# nova/objects/ec2.py:26-27
# 映射结果缓存 7 天
_CACHE_TIME = 7 * 24 * 60 * 60
@memoize # 使用装饰器缓存
def get_int_id_from_instance_uuid(context, instance_uuid):
...
8.2 懒加载
- 映射记录在首次访问时才创建
- 不访问 EC2 端点的实例不会创建映射
- 减少数据库写入压力
九、实际应用建议
9.1 如果你是 OpenStack 管理员
- 查询实例:优先使用 UUID(OpenStack CLI/API)
- 监控系统:使用 UUID 更稳定
- 数据库维护 :
instance_id_mappings表只增不减,可定期清理已删除实例的记录
9.2 如果你在虚拟机内部
ruby
# 获取 EC2 格式 ID(兼容性好)
curl http://169.254.169.254/latest/meta-data/instance-id
# 获取真实 UUID(推荐)
curl -s http://169.254.169.254/openstack/latest/meta_data.json | jq -r '.uuid'
9.3 如果你在开发应用
- 推荐:使用 OpenStack 原生 UUID
- 兼容性需求:如需支持 AWS EC2,使用 EC2 格式
- 双重支持:同时提供两种格式的支持
十、总结
| 项目 | OpenStack UUID | EC2 ID |
|---|---|---|
| 格式 | 4daf043f-cc30-4513-b639-84906a4278e1 |
i-00000004 |
| 长度 | 36 字符 | 10 字符 |
| 原生性 | ✅ 原生主键 | ❌ 兼容层 |
| 全局唯一 | ✅ 是 | ⚠️ 仅单集群内 |
| 需要映射表 | ❌ 否 | ✅ 是 |
| 获取端点 | /openstack/latest/meta_data.json |
/latest/meta-data/instance-id |
| 使用场景 | OpenStack API、数据库查询 | EC2 兼容工具 |
核心观点 :OpenStack 原生使用 UUID,映射表仅用于生成 EC2 兼容的短 ID,不是将 EC2 ID 转回 UUID,而是将 UUID 转为 EC2 ID。