1. 文档概述
1.1 目标
将 OpenStack Nova 的 Metadata 服务迁移到 Kubernetes 环境,实现虚拟机实例的元数据管理功能。
1.2 迁移范围
- ✅ 核心元数据(meta_data.json)
- ✅ 用户初始化脚本(user_data)
- ✅ SSH 密钥注入(public_keys)
- ⚠️ 网络配置(network_data.json)- 简化实现
- ❌ 供应商数据(vendor_data)- 可选
- ❌ EC2 兼容格式 - 不实现
2. Nova Metadata 接口清单
2.1 OpenStack 格式接口
| 接口路径 | 功能 | 优先级 | 迁移决策 |
|---|---|---|---|
/openstack/latest/meta_data.json | 实例基础信息 | P0 ⭐️⭐️⭐️⭐️⭐️ | 必须实现 |
/openstack/latest/user_data | 初始化脚本 | P0 ⭐️⭐️⭐️⭐️⭐️ | 必须实现 |
/openstack/latest/network_data.json | 网络配置 | P1 ⭐️⭐️⭐️⭐️ | 简化实现 |
/openstack/latest/vendor_data.json | 供应商数据 | P2 ⭐️⭐️⭐️ | 可选 |
/openstack/latest/password | 实例密码 | P2 ⭐️⭐️⭐️ | 不实现 |
2.2 EC2 兼容格式接口(不实现)
| 接口路径 | 功能 | 迁移决策 |
|---|---|---|
/latest/meta-data/instance-id | 实例 ID | 不实现 |
/latest/meta-data/hostname | 主机名 | 不实现 |
/latest/meta-data/local-ipv4 | 内网 IP | 不实现 |
/latest/meta-data/public-keys/0/openssh-key | SSH 公钥 | 不实现 |
决策原因: EC2 格式主要用于兼容性,K8s 环境下可以简化为直接使用环境变量。
3. 核心接口详细分析
3.1 meta_data.json(实例元数据)
功能说明
提供虚拟机的基本身份信息,包括 UUID、名称、IP、SSH 密钥等。
Nova 实现流程
-
虚拟机内请求 http://169.254.169.254/openstack/latest/meta_data.json ↓
-
请求到达 nova-api-metadata 服务 ↓
-
根据 IP 地址查询数据库:
- instances 表:uuid, name, hostname, created_at
- instance_metadata 表:用户自定义标签
- keypairs 表:SSH 公钥 ↓
-
组装 JSON 数据返回
#### 输入数据(Nova 数据源)
- **数据库表**:instances, instance_metadata, keypairs
- **关键字段**:uuid, display_name, hostname, key_name, project_id, availability_zone
#### 返回数据示例
```json
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "my-instance",
"hostname": "my-instance.novalocal",
"availability_zone": "nova",
"project_id": "f9d3a1e2b4c3d5e6f7a8b9c0d1e2f3a4",
"public_keys": {
"mykey": "ssh-rsa AAAAB3NzaC..."
},
"meta": {
"role": "webserver",
"environment": "production"
}
}
K8s 实现方案
核心机制:Downward API
Kubernetes 的 Downward API 允许 Pod 自动获取自己的元数据,通过环境变量注入。
映射关系:
| Nova 字段 | K8s 来源 | 注入方式 |
|---|---|---|
| uuid | Pod.metadata.uid | 环境变量 |
| name | Pod.metadata.name | 环境变量 |
| hostname | Pod.metadata.name | 环境变量 |
| project_id | Pod.metadata.namespace | 环境变量 |
| availability_zone | Pod.spec.nodeName | 环境变量 |
| local-ipv4 | Pod.status.podIP | 环境变量 |
| meta | Pod.metadata.labels | 环境变量 |
实现方式: 创建 Pod 时,通过 Downward API 自动注入环境变量:
env:
- name: INSTANCE_UUID
valueFrom:
fieldRef:
fieldPath: metadata.uid
- name: INSTANCE_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: INSTANCE_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
Pod 内访问:
echo $INSTANCE_UUID
echo $INSTANCE_NAME
echo $INSTANCE_IP
3.2 user_data(初始化脚本)
功能说明
用户在创建虚拟机时提供的初始化脚本,虚拟机启动后自动执行(通常由 cloud-init 处理)。
Nova 实现流程
1. 用户创建虚拟机时传入 user_data(base64 编码)
POST /servers {"server": {"user_data": "IyEvYmluL2Jhc2g..."}}
↓
2. Nova 存储到数据库 instances.user_data 字段
↓
3. 虚拟机启动后,cloud-init 请求 metadata API
↓
4. nova-api-metadata 返回解码后的脚本
↓
5. cloud-init 执行脚本(安装软件、配置服务等)
输入数据
- API 请求:base64 编码的脚本
- 存储位置:数据库 instances.user_data
返回数据示例
#!/bin/bash
echo "Hello World"
apt-get update
apt-get install -y nginx
systemctl start nginx
K8s 实现方案
核心机制:ConfigMap + Init Container
流程:
1. API Gateway 接收 user_data
↓
2. 创建 ConfigMap 存储脚本
↓
3. Pod 使用 Init Container 执行脚本
↓
4. Init Container 完成后,主容器启动
实现步骤:
步骤 1:创建 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: my-instance-userdata
data:
user-data.sh: |
#!/bin/bash
echo "Hello World"
apt-get update
apt-get install -y nginx
步骤 2:Pod 使用 Init Container
apiVersion: v1
kind: Pod
spec:
initContainers:
- name: init-userdata
image: ubuntu:22.04
command: ["/bin/bash", "/userdata/user-data.sh"]
volumeMounts:
- name: userdata
mountPath: /userdata
containers:
- name: main
image: ubuntu:22.04
volumes:
- name: userdata
configMap:
name: my-instance-userdata
defaultMode: 0755 # 可执行权限
为什么用 Init Container?
- ✅ 在主容器启动前执行(符合初始化语义)
- ✅ 失败会阻止 Pod 启动(确保初始化成功)
- ✅ 与主容器隔离(不影响运行环境)
3.3 public_keys(SSH 密钥)
功能说明
提供 SSH 公钥,写入 ~/.ssh/authorized_keys,允许用户 SSH 登录。
Nova 实现流程
1. 用户创建虚拟机时指定 key_name
POST /servers {"server": {"key_name": "mykey"}}
↓
2. Nova 从 keypairs 表查询公钥内容
↓
3. 虚拟机启动,cloud-init 请求 metadata API
↓
4. nova-api-metadata 返回公钥
↓
5. cloud-init 写入 ~/.ssh/authorized_keys
输入数据
- 数据库表:keypairs
- 关键字段:name, public_key, user_id
返回数据示例
{
"public_keys": {
"mykey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7... user@host"
}
}
K8s 实现方案
核心机制:Secret 挂载
流程:
1. API Gateway 接收 SSH 公钥
↓
2. 创建 Secret 存储公钥
↓
3. Pod 挂载 Secret 到 ~/.ssh/authorized_keys
↓
4. SSH 服务读取密钥,允许登录
实现步骤:
步骤 1:创建 Secret
apiVersion: v1
kind: Secret
metadata:
name: my-instance-ssh
type: Opaque
stringData:
authorized_keys: |
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7... user@host
步骤 2:Pod 挂载 Secret
apiVersion: v1
kind: Pod
spec:
containers:
- name: main
volumeMounts:
- name: ssh-keys
mountPath: /root/.ssh
readOnly: true
volumes:
- name: ssh-keys
secret:
secretName: my-instance-ssh
defaultMode: 0600 # 权限 600(SSH 要求)
为什么用 Secret?
- ✅ 专为敏感数据设计(加密存储)
- ✅ 支持文件挂载(直接写入文件系统)
- ✅ 权限控制(可设置文件权限)
3.4 network_data.json(网络配置)
功能说明
提供网络接口配置信息(IP、网关、DNS 等),用于静态网络配置。
Nova 实现流程
1. 请求 http://169.254.169.254/openstack/latest/network_data.json
↓
2. nova-api-metadata 查询 Neutron
- neutron.list_ports(device_id=instance_id)
- 获取 IP、MAC、网关、路由等
↓
3. 组装 network_data.json 返回
返回数据示例
{
"links": [
{
"ethernet_mac_address": "fa:16:3e:9c:bf:3d",
"id": "tap1",
"mtu": 1500
}
],
"networks": [
{
"id": "network0",
"link": "tap1",
"ip_address": "192.168.1.10",
"netmask": "255.255.255.0",
"gateway": "192.168.1.1"
}
],
"services": [
{"type": "dns", "address": "8.8.8.8"}
]
}
K8s 实现方案
核心机制:CNI 自动管理
Kubernetes 通过 CNI(Container Network Interface)插件自动配置网络,不需要手动配置。
自动提供:
- ✅ IP 地址分配(IPAM)
- ✅ 路由配置
- ✅ DNS 解析(kube-dns/CoreDNS)
- ✅ 网络隔离(NetworkPolicy)
简化实现: 如果需要提供 network_data.json,可以通过环境变量暴露基本信息:
env:
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: HOST_IP
valueFrom:
fieldRef:
fieldPath: status.hostIP
为什么简化?
- ✅ K8s 网络自动管理,无需静态配置
- ✅ 容器环境不需要复杂的网络配置文件
- ✅ 减少实现复杂度
4. K8s 实现方案总结
4.1 核心技术栈
| 技术 | 作用 | 对应 Nova |
|---|---|---|
| Downward API | 注入 Pod 元数据 | Metadata API |
| ConfigMap | 存储配置数据 | user_data |
| Secret | 存储敏感数据 | SSH 密钥 |
| Init Container | 初始化容器 | cloud-init |
| client-go | K8s API 客户端 | Nova API 客户端 |
4.2 整体架构
┌─────────────────────────────────────────────┐
│ 用户(发送 Nova API 请求) │
└──────────────────┬──────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ API Gateway(Go 程序) │
│ │
│ 1. 接收 Nova API 请求(POST /servers) │
│ 2. 转换为 K8s 资源 │
│ 3. 调用 K8s API 创建资源 │
│ 4. 返回 Nova 格式响应 │
└──────────────────┬──────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Kubernetes API Server │
└──────────────────┬──────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 创建的资源 │
│ │
│ ├─ ConfigMap(user_data 脚本) │
│ ├─ Secret(SSH 公钥) │
│ └─ Pod │
│ ├─ Init Container(执行 user_data) │
│ └─ Main Container(注入元数据环境变量) │
└─────────────────────────────────────────────┘
4.3 创建虚拟机的完整流程
用户发送请求
POST /servers {
"server": {
"name": "my-vm",
"imageRef": "ubuntu:22.04",
"flavorRef": "2",
"user_data": "IyEvYmluL2Jhc2g...",
"key_name": "mykey"
}
}
↓
API Gateway 处理
├─ 1. 解码 user_data(base64 → 文本)
├─ 2. 查询 SSH 公钥(从 keypairs)
├─ 3. 获取 Flavor 配置(CPU/内存)
↓
创建 K8s 资源
├─ 1. 创建 ConfigMap
│ name: my-vm-userdata
│ data: user-data.sh
├─ 2. 创建 Secret
│ name: my-vm-ssh
│ data: authorized_keys
├─ 3. 创建 Pod
│ ├─ Init Container
│ │ ├─ 挂载 ConfigMap
│ │ └─ 执行 user-data.sh
│ ├─ Main Container
│ │ ├─ 注入环境变量(Downward API)
│ │ ├─ 挂载 Secret(SSH 密钥)
│ │ └─ 资源限制(CPU/内存)
↓
返回响应
{
"server": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "my-vm",
"status": "BUILD"
}
}
5. 关键概念解释
5.1 Downward API
定义: Kubernetes 的一个功能,允许 Pod 获取自己的元数据信息。
类比:
- Nova Metadata:外部查询台(需要 HTTP 请求 169.254.169.254)
- Downward API:内部注入器(自动写入环境变量或文件)
支持的字段:
- Pod 名称:
metadata.name - Pod UID:
metadata.uid - Pod IP:
status.podIP - 节点名:
spec.nodeName - 命名空间:
metadata.namespace - 标签:
metadata.labels['key']
使用方式:
env:
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
5.2 Init Container
定义: 在主容器启动之前运行的特殊容器,用于执行初始化任务。
特点:
- 按顺序执行(一个接一个)
- 必须全部成功,主容器才启动
- 可以包含主容器镜像中没有的工具
对应关系:
- Nova:cloud-init 执行 user_data
- K8s:Init Container 执行 user_data 脚本
执行流程:
Pod 创建 → Init Container 1 → Init Container 2 → 主容器启动
5.3 Secret vs ConfigMap
对比:
| 类型 | 用途 | 数据敏感性 | 存储方式 |
|---|---|---|---|
| Secret | 密码、密钥、证书 | 敏感数据 | Base64 编码 + 加密 |
| ConfigMap | 配置文件、脚本 | 非敏感数据 | 明文存储 |
使用场景:
- Secret:SSH 密钥、数据库密码、API Token
- ConfigMap:user_data 脚本、配置文件、环境变量
5.4 client-go
定义: Kubernetes 官方提供的 Go 语言客户端库,用于与 K8s API Server 交互。
核心功能:
- 创建/查询/删除 K8s 资源(Pod、Service、ConfigMap 等)
- 监听资源变化(Watch)
- 批量操作
基本使用:
// 1. 创建客户端
config, _ := rest.InClusterConfig()
clientset, _ := kubernetes.NewForConfig(config)
// 2. 操作资源
// 创建 Pod
clientset.CoreV1().Pods(namespace).Create(ctx, pod, metav1.CreateOptions{})
// 查询 Pod
clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
// 删除 Pod
clientset.CoreV1().Pods(namespace).Delete(ctx, name, metav1.DeleteOptions{})
6. 迁移决策说明
6.1 为什么不完全兼容 169.254.169.254?
方案对比:
| 方案 | 优点 | 缺点 | 工作量 |
|---|---|---|---|
| 完全模拟 169.254.169.254 | 100% 兼容传统应用 | 需要特权容器、复杂网络配置 | 1000+ 行代码 |
| Downward API | 简单、K8s 原生、无需特权 | 不兼容传统 cloud-init | 200 行代码 |
决策:使用 Downward API
理由:
- ✅ K8s 原生支持,稳定可靠
- ✅ 无需特权容器(安全)
- ✅ 实现简单,维护成本低
- ✅ 对于新应用足够用
- ⚠️ 传统应用需要修改代码(读环境变量而不是 HTTP 请求)
6.2 为什么不实现 vendor_data?
原因:
- 使用频率低:大部分场景不需要
- 供应商特定:内容因部署环境而异
- 可延后实现:不影响核心功能
如果需要: 创建全局 ConfigMap 即可,工作量很小。
6.3 为什么不实现 EC2 格式?
原因:
- 主要用于兼容:AWS EC2 格式主要为了迁移旧应用
- OpenStack 格式够用:功能重叠
- 工作量较大:需要实现大量端点
决策:只实现 OpenStack 格式
7. 数据流对比
7.1 Nova Metadata 数据流
虚拟机内应用
↓ HTTP GET
http://169.254.169.254/openstack/latest/meta_data.json
↓
Neutron metadata proxy(网络层转发)
↓ 添加 X-Instance-ID header
nova-api-metadata 服务
↓ 查询数据库
MySQL(instances, keypairs, instance_metadata)
↓ 返回数据
nova-api-metadata 组装 JSON
↓ HTTP 200 + JSON
虚拟机内应用(解析使用)
7.2 K8s Downward API 数据流
Pod 创建时
↓
API Gateway 定义 Downward API
↓
Kubernetes API Server
↓ 调度 Pod
Kubelet(节点代理)
↓ 自动注入环境变量
Pod 内容器
↓ 读取环境变量
应用直接使用(无需 HTTP 请求)
关键区别:
- Nova:运行时动态查询(需要网络和数据库)
- K8s:启动时静态注入(无需额外请求)
8. 实施路线图
阶段 1:最小可行产品(2 周)
目标:实现核心虚拟机管理
✅ 功能清单:
1. POST /servers - 创建虚拟机
├─ 创建 Pod
├─ Downward API 注入元数据
└─ 返回 Nova 格式响应
2. GET /servers - 列出虚拟机
3. GET /servers/{id} - 查看详情
4. DELETE /servers/{id} - 删除虚拟机
验收标准: 可以通过 API 创建、查询、删除"虚拟机"(实际是 Pod)。
阶段 2:Metadata 完整支持(1 周)
目标:实现元数据功能
✅ 功能清单:
1. user_data 支持
├─ 创建 ConfigMap
└─ Init Container 执行脚本
2. SSH 密钥支持
├─ 创建 Secret
└─ 挂载到 ~/.ssh/authorized_keys
3. 完善元数据环境变量
└─ Downward API 所有字段
验收标准:
- Pod 内能通过环境变量获取元数据
- user_data 脚本正确执行
- SSH 密钥正确挂载
阶段 3:增强功能(1 周,可选)
⚠️ 可选功能:
1. network_data.json 简化版
2. Flavor 管理 API
3. 错误处理和日志
4. 性能优化
9. 工作量评估
9.1 核心功能工作量
| 功能模块 | 工作量 | 代码量 |
|---|---|---|
| API Gateway 框架 | 1 天 | 100 行 |
| 创建虚拟机(POST /servers) | 2 天 | 200 行 |
| 查询和删除 | 1 天 | 100 行 |
| user_data 支持 | 1 天 | 100 行 |
| SSH 密钥支持 | 1 天 | 50 行 |
| Downward API 配置 | 0.5 天 | 50 行 |
| 测试和调试 | 2 天 | - |
| 总计 | 8.5 天 | ~600 行 |
9.2 技能要求
- ✅ Go 语言基础
- ✅ Kubernetes 基本概念(Pod、ConfigMap、Secret)
- ✅ HTTP API 开发经验
- ⚠️ 不需要深入的 K8s 运维经验
- ⚠️ 不需要深入的 OpenStack 开发经验
10. 总结
10.1 核心要点
| 方面 | Nova | K8s | 迁移策略 |
|---|---|---|---|
| 元数据 | HTTP API (169.254.169.254) | Downward API(环境变量) | 简化实现 |
| 初始化 | cloud-init + user_data | Init Container + ConfigMap | 功能等价 |
| SSH 密钥 | Metadata API | Secret 挂载 | 功能等价 |
| 网络 | Neutron 动态配置 | CNI 自动管理 | K8s 原生 |
| 存储 | Cinder 卷服务 | PVC/PV | K8s 原生 |
10.2 关键决策
-
使用 Downward API 代替 169.254.169.254
- 理由:简单、原生、安全
- 代价:不兼容传统应用
-
不实现 EC2 格式
- 理由:工作量大、收益小
- 代价:无法直接兼容 AWS 迁移应用
-
简化 network_data.json
- 理由:K8s 自动管理网络
- 代价:丢失部分网络配置能力
10.3 适用场景
适合:
- ✅ 新开发的云原生应用
- ✅ 容器化应用
- ✅ 可以修改代码的应用
不适合:
- ❌ 完全依赖 cloud-init 的传统应用
- ❌ 无法修改的闭源应用
- ❌ 需要完整 OpenStack 兼容性的场景
11. 参考资料
- Nova Metadata 官方文档 docs.openstack.org/nova/2025.2…
- Kubernetes Downward API kubernetes.io/docs/tasks/…
- Init Containers kubernetes.io/docs/concep…
- ConfigMap kubernetes.io/docs/concep…
- Secret kubernetes.io/docs/concep…
- client-go 示例 github.com/kubernetes/…
附录:快速参考
A. 接口迁移速查表
| Nova 接口 | 必须? | K8s 方案 |
|---|---|---|
| meta_data.json | ✅ | Downward API |
| user_data | ✅ | ConfigMap + Init Container |
| public_keys | ✅ | Secret 挂载 |
| network_data.json | ⚠️ | 简化或不实现 |
| vendor_data.json | ❌ | 不实现 |
| password | ❌ | 不实现 |
| EC2 格式 | ❌ | 不实现 |
B. K8s 资源对照表
| Nova 概念 | K8s 资源 |
|---|---|
| 虚拟机实例 | Pod / Deployment |
| 规格(Flavor) | ResourceRequirements |
| 镜像 | Container Image |
| SSH 密钥 | Secret |
| user_data | ConfigMap |
| 元数据 | Downward API |
| 网络 | Service / Ingress |
| 卷 | PersistentVolume |