OpenStack API实战:从Keystone认证到云主机自动化创建
1. 为什么把练习重点放在 OpenStack API 接口上先讲个我自己的实际经历。之前帮一家客户维护一套私有云环境控制台上点“创建云主机”这个动作很多运维同学闭着眼都能做进项目、选镜像、选规格、配网络、点启动。但客户提了一个需求希望在每天晚上十点自动批量创建二十台测试机早上八点再全部释放掉。这种需求靠人工点控制台根本没法落地只能靠写脚本调用 OpenStack API。所以我的观点很明确OpenStack 私有云平台最值钱的部分不是 Web 控制台那层壳而是壳底下那套完整的 REST API。控制台是给人用的API 是给程序用的。你想让云平台真正成为基础设施让业务系统在需要的时候自助申请资源、自动扩缩容就必须把对云平台的控制能力从“手工点鼠标”升级为“程序调接口”。这个练习项目说白了就是把 OpenStack 私有云平台上最常碰到的操作全部用 API 接口方式走一遍。它的核心价值不在于“我调通了”而在于通过练习把下面这条链路彻底打通先搞清楚 Keystone 到底是怎么认证的Token 是怎么签发和校验的再理解各类服务计算、网络、镜像、存储在 API 层如何协作最后能够在你自己的业务系统里用 Java、Python 或 C 这类语言去调用这些接口对外暴露成你们自己的业务 API。适合看这篇文章的人我大概列一下正在搭建或运维 OpenStack 私有云的同行准备做云管平台或自动化运维平台的开发工程师以及那些已经会用控制台、想进一步往自动化方向走的运维朋友。1.1 私有云里每个“资源对象”都可以映射成 API 操作OpenStack 在逻辑上由一系列独立服务组成每个服务负责一类资源Nova 管计算实例Neutron 管网络、子网和路由Glance 管镜像Cinder 管云硬盘Keystone 管身份认证和项目配额。对应到 API 层面就是一组 RESTful 接口服务资源对象API 入口示例KeystoneToken、Project、User/v3/auth/tokensGlanceImage/v2/imagesNovaServer、Flavor、Keypair/v2.1/serversNeutronNetwork、Subnet、Router/v2.0/networksCinderVolume、Snapshot/v3/volumes在这个模型里“创建一台云主机”这个动作在 UI 上只需要点一次但在 API 层其实是多个服务协同完成的一系列调用先认证拿 Token再查镜像 ID、查规格 ID、确保网络已存在把所有这些拼成一个 create server 的请求发出去最后还要轮询查询实例状态直到变为 ACTIVE。如果你不通过 API 练习去理解这种“多服务协同”的资源模型后面一旦写自动化脚本很容易卡在“为什么创建了网络但云主机起不来”这种问题上。1.2 控制台看不到的关键细节API 全暴露出来了用控制台创建云主机时系统替你处理了太多细节认证、Token 刷新、请求超时重试、异构资源校验、错误信息包装。你以为自己只是“点了一个按钮”实际上后台执行的是一个非常复杂的异步事务。而 API 练习恰恰是把这些细节全部摆在桌面上。举个例子UI 上几乎不会有人关心 Token 用了多久会过期但当你用自己的脚本调用 Nova 接口连续创建 20 台云主机时你就会发现 Token 过期是个绕不过去的问题。再比如UI 上创建完网络后DHCP 服务还在初始化立刻创建云主机可能导致网卡起不来——这件事只有自己也走一遍 API 创建流程才能真正理解资源创建是异步的、需要轮询状态的。我个人建议练习时先用 curl 裸调不要一开始就上现成的 OpenStack SDK。因为 SDK 帮你封装好了认证、重试、解析你反而看不清楚每一次 HTTP 请求到底长什么样。2. 先搭一个能练手的 OpenStack 私有云环境要练习 API前提当然得有一套能连上的 OpenStack 环境。如果你公司已经有现成的私有云平台可以直接跳过这节找管理员开一个带 API 访问权限的测试项目就行。但如果没有现成环境或者你希望从部署侧也了解一下那么我强烈建议用 Kolla-Ansible 快速搭一套 All-in-One 的练习环境。2.1 三种练习环境方案怎么选方案适合场景优点缺点公司现成的私有云测试项目已经有平台、只想练 API零部署成本、环境真实可能没权限碰管理面接口自己用虚拟机搭 All-in-One想完全自主控制、反复折腾自由度高、可随意破坏重建对机器配置有要求、部署耗时多节点 Kolla 部署想模拟生产环境更贴近真实架构至少需要三台机器练习成本高对于绝大多数“我就想练 API”的朋友我首推All-in-One Kolla-Ansible。原因是API 练习需要反复造数据、造完再删如果放在公司的正式环境里容易影响别人自己搭一套则可以随便搞。2.2 Kolla-Ansible 部署路径中的关键点Kolla-Ansible 的核心思路很简单用 Ansible 把 OpenStack 各服务的容器编排起来跑在一台或多台机器上。整个部署过程网上教程很多我不打算从头抄一遍只说几个容易出问题的关键点。建议最低配置CPU 4 核以上、内存 16GB 以上、系统盘 100GB。如果内存不够很多容器在启动后会因为 OOM 被杀掉而且症状非常隐蔽看起来像是“某个 API 端口连不上”实际上只是容器根本没起来。部署前要改的两个核心文件是/etc/kolla/globals.yml和/etc/kolla/passwords.yml。globals.yml里先改两处kolla_base_distro: centos kolla_install_type: source network_interface: eth0 neutron_external_interface: eth1network_interface是 OpenStack 内部管理网络要绑定的网卡neutron_external_interface是提供给云主机外部访问用的网卡。在 All-in-One 环境下通常需要给虚拟机至少配两块网卡否则后面创建外部网络时会发现没有可用的物理接口Flexible IP 也一直处于 DOWN 状态。密码默认会随机生成保存在/etc/kolla/passwords.yml中但里面也有一段注释说明可以自行预置。练习环境里我建议直接预置掉 admin 用户密码和数据库密码免得后面查来查去keystone_admin_password: YourAdminPass123部署命令本身很简单pip install kolla-ansible kolla-ansible -i /etc/kolla/all-in-one.ini bootstrap-servers kolla-ansible -i /etc/kolla/all-in-one.ini precheck kolla-ansible -i /etc/kolla/all-in-one.ini deploy kolla-ansible -i /etc/kolla/all-in-one.ini post-deploy跑完后安装目录下会生成一个/etc/kolla/admin-openrc.sh这是 OpenStack CLI 用来读取环境变量的脚本。后面我们做 API 练习时里面的几个变量就是最重要的线索。2.3 验证环境先把关键端点理清楚部署完成后用openstack endpoint list看一遍所有服务的公开端点这一步非常重要。你会看到类似这样的信息------------------------------------------------------------------------ | Service Type | Interface | URL | ------------------------------------------------------------------------ | identity | public | http://10.0.0.10:5000/v3 | | compute | public | http://10.0.0.10:8774/v2.1| | image | public | http://10.0.0.10:9292 | | network | public | http://10.0.0.10:9696 | ------------------------------------------------------------------------这些 URL 就是 API 练习时的“靶子”。后面所有请求都得打在对应服务的端口上。我建议先把这些端点抄下来同时跑一下openstack token issue确认认证能通过。如果这步都过不了后面一切无从谈起。3. 第一道门槛Keystone 认证接口与 Token 生命周期很多第一次做 OpenStack API 练习的人卡住的地方往往不是 Nova 或 Neutron而是第一步认证。我印象特别深之前在脚本里直接把OS_AUTH_URL配成了 HTTP 协议结果 Keystone 返回 400报错信息是“Authentication required. Please provide a valid token.”——但实际上 Token 根本没拿到问题出在我把认证地址配错了。3.1 Keystone v3 的认证模型必须先理解老版本的 Keystone v2.0 已经被废弃好几年了。现在的 API 练习应该直接基于 v3。v3 和 v2 最大的区别是引入了domain域的概念project 不再是一个扁平的租户列表而是挂在 domain 下面。一个完整的 v3 Token 请求最小的 JSON 结构是这样的curl -s -X POST \ -H Content-Type: application/json \ -d { auth: { identity: { methods: [password], password: { user: { name: admin, domain: { name: Default }, password: YourAdminPass123 } } }, scope: { project: { name: admin, domain: { name: Default } } } } } \ http://10.0.0.10:5000/v3/auth/tokens注意两个细节identity里面指定的是登录用户的信息scope里面指定的是认证后的作用范围。也就是说即使同一个 admin 用户如果 scope 到 A 项目拿到的 Token 只能操作 A 项目里的资源scope 到 B 项目就得再换一个 Token。响应头里的X-Subject-Token就是我们要用的正式 Token。响应体里的token.catalog字段则列出了所有服务的端点列表这个字段在做服务发现时非常有用。3.2 Token 的生效方式与请求头格式拿到 Token 后调用任何其他 API只需要在请求头中携带X-Auth-Token: gAAAAABxxxxxxxxx...不需要再带用户名和密码。所有服务收到请求后都会向 Keystone 校验这个 Token 是否有效、是否过期、对应的项目 scope 是什么。这就像你去园区里办事进门时先到前台换一张访客卡之后去各个办公楼只要刷这张卡就行。Token 本身带有有效期默认通常是 1 小时openstack token issue的返回结果里能看到 expire 时间------------------------------------------ | Field | Value | ------------------------------------------ | expires | 2025-01-05T08:23:22.000000 | | id | gAAAAABxxxxxxxxx | | project_id | 26e3e8bb8ceb4b5c9d1f0ba6... | | user_id | abc123... | ------------------------------------------练习时如果脚本跑着跑着突然全部 401第一反应就去看 Token 是否过期。这算是我踩过最频繁的问题之一。3.3 使用 Catalog 自动发现服务端点我最早练 API 时习惯把每个服务的 URL 硬编码在脚本里。后来才发现 Keystone 返回的 Token 响应体里已经有完整的catalog可以通过服务类型自动找到端点curl -s -X POST ... -H Content-Type: application/json -d ... \ | python3 -c import sys,json; djson.load(sys.stdin); print([e[url] for e in d[token][catalog] if e[type]compute][0])这样的好处是当环境迁移后只需要改一个OS_AUTH_URL其他端点自动获取。这也是所有官方客户端和 SDK 的处理方式。理解了这一点你自己写脚本时就可以模仿而不是每换一套环境就去改几十处 IP。4. Nova 与 Neutron 联调实战用 API 创建网络和云主机拿到 Token 之后就可以来真格的了。我建议的第一个练习目标是通过 API 创建一台带网络的云主机。为什么要选这个目标因为它在 API 层面覆盖了 Neutron、Glance、Nova 三个核心服务相当于把私有云最常用的资源创建流程完整走了一遍。4.1 网络先行Neutron API 三连击云主机没有网络就是一台裸算力所以必须先通过 Neutron 把网络建好。Neutron 的资源层级是Network二层网络→ Subnet网段→ Router三层网关。练习时按这个顺序创建。创建网络的请求curl -s -X POST \ -H X-Auth-Token: $OS_TOKEN \ -H Content-Type: application/json \ -d {network: {name: lab-net, admin_state_up: true}} \ http://10.0.0.10:9696/v2.0/networks响应里会返回网络的id字段这个 ID 后面创建子网和云主机时都要用到。建议在 Shell 脚本里用python3或jq解析出来保存成变量别靠眼睛去复制太容易出错。创建子网curl -s -X POST \ -H X-Auth-Token: $OS_TOKEN \ -H Content-Type: application/json \ -d {subnet: {network_id: 网络ID, name: lab-subnet, cidr: 192.168.100.0/24, ip_version: 4, gateway_ip: 192.168.100.1}} \ http://10.0.0.10:9696/v2.0/subnets这里要注意cidr网段别和宿主机所在的管理网段冲突否则路由会乱。如果只是练习建议挑一个不常用的私有网段比如 192.168.100.0/24。随后创建路由并设置网关curl -s -X POST \ -H X-Auth-Token: $OS_TOKEN \ -H Content-Type: application/json \ -d {router: {name: lab-router, external_gateway_info: {network_id: 外部网络ID}}} \ http://10.0.0.10:9696/v2.0/routers创建完路由后别忘了把子网放到路由上。这一步在 CLI 里对应的是openstack router add subnetAPI 上需要向路由的interfaces子资源发 PUT 请求curl -s -X PUT \ -H X-Auth-Token: $OS_TOKEN \ -H Content-Type: application/json \ -d {subnet_id: 子网ID} \ http://10.0.0.10:9696/v2.0/routers/路由器ID/add_router_interface我在练习中一次“漏连”了这个步骤导致云主机创建成功但完全无法访问外网。因为 Neutron 不会自动把内网子网和路由关联起来你在控制台看到的“一键建网”实际背后就是上面这整套操作。4.2 准备镜像Glance API 的最小可用操作云主机要启动必须有一个可启动的镜像。生产环境一般会自己封装镜像练习时用一个 Cirros 小镜像就够了。通过 Glance API 上传镜像有两种方式一种是直接 POST 镜像数据另一种是先用location字段指定一个可下载的 URL让 Glance 后台去拉取。推荐用 location 方式省流量curl -s -X POST \ -H X-Auth-Token: $OS_TOKEN \ -H Content-Type: application/json \ -d {name: cirros-test, container_format: bare, disk_format: qcow2, visibility: public, location: http://download.cirros-cloud.net/0.6.1/cirros-0.6.1-x86_64-disk.img} \ http://10.0.0.10:9292/v2/images上传后 Glance 会返回一个镜像 ID。可以用curl -s -H X-Auth-Token: $OS_TOKEN \ http://10.0.0.10:9292/v2/images/镜像ID通过响应里的status字段判断镜像是否上传完成。queued表示还在排队active才表示可用。4.3 Nova API 创建云主机与状态轮询网络和镜像都准备好了接下来是最核心的 Nova API 调用。curl -s -X POST \ -H X-Auth-Token: $OS_TOKEN \ -H Content-Type: application/json \ -d { server: { name: lab-vm-001, flavorRef: 规格ID, imageRef: 镜像ID, networks: [{uuid: 网络ID}], key_name: 练习用的密钥对名称 } } \ http://10.0.0.10:8774/v2.1/项目ID/servers首次练习时最容易踩的坑是不知道flavorRef和镜像 ID 需要是 UUID 格式。你可以先这样查询curl -s -H X-Auth-Token: $OS_TOKEN \ http://10.0.0.10:8774/v2.1/项目ID/flavors拿到 flavor 列表后找到id: 1或某个 UUID。如果这里写错Nova 会返回 400错误信息里会明确提示找不到 flavor。发送创建请求后马上查询实例状态curl -s -H X-Auth-Token: $OS_TOKEN \ http://10.0.0.10:8774/v2.1/项目ID/servers/实例ID返回的status字段可能依次是BUILD、ACTIVE或ERROR。如果最终是ERROR别慌再看响应里的fault字段里面通常会写明具体原因。最常见的是网络 ID 不存在、镜像没有真正 active、或者配额不足。4.4 为什么一定要理解“异步任务”的概念这次练习给我最大的认知转变是OpenStack API 里大部分资源创建都是异步的。你 POST 一个 serverNova 返回 202 表示已接受并不代表云主机已经创建完成。官方 SDK 和 CLI 之所以“看起来像同步”是因为它们在底层帮你做了循环轮询。自己写脚本时也得模仿这种机制否则很容易出现“刚创建完实例就去 ping结果根本不通”的情况。轮询的间隔建议 2 到 5 秒不要小于 1 秒不然会给控制节点造成不必要的压力。5. 从练习到落地把 OpenStack API 封装成对外可调用的服务当你已经能通过 curl 完成创建云主机这套练习才走完了一半。因为在真实业务里调用方不会直接跟 OpenStack 打交道而是通过你自己的平台服务。这也是热词里“Java 开发 API 接口以供外部调用”“VC 访问 http 的服务端 API 接口”这两类需求的由来。原始 OpenStack API 暴露给外部业务系统存在几个问题安全风险太高正确的做法是始终在网络层面隔离 OpenStack 管理面数据结构过宽创建云主机的请求里可能有一堆外部系统根本不需要的字段业务语义不匹配外部系统需要的可能是“开一台测试机”这个动作而不是 OpenStack 的资源模型。所以我建议的封装路径是自己写一层薄薄的 API 服务把 OpenStack 的操作包装成业务系统能理解的动作。5.1 用 Python Flask 快速封装“创建云主机”接口Python 调用 OpenStack API 有两种方式直接用官方openstacksdk或者用requests手动发请求。对于练习我建议两种都试一下。使用 openstacksdk 的写法相对简洁import openstack conn openstack.connect( auth_urlhttp://10.0.0.10:5000/v3, usernameadmin, passwordYourAdminPass123, project_nameadmin, user_domain_nameDefault, project_domain_nameDefault, ) flavor conn.compute.find_flavor(m1.tiny) image conn.image.find_image(cirros-test) network conn.network.find_network(lab-net) server conn.compute.create_server( nameauto-vm, flavor_idflavor.id, image_idimage.id, networks[{uuid: network.id}], ) conn.compute.wait_for_server(server)这段代码相当于把刚才的 curl 步骤全部封装起来了。然后基于它写一个 Flask 接口from flask import Flask, jsonify, request import openstack import threading app Flask(__name__) def create_server_async(name, image_name, network_name, flavor_name): conn openstack.connect(...) result {name: name, status: unknown} # 实际创建并轮询 ... return result app.route(/api/v1/servers, methods[POST]) def create_server(): data request.get_json() name data.get(name, default-vm) threading.Thread( targetcreate_server_async, args(name, cirros-test, lab-net, m1.tiny) ).start() return jsonify({code: 0, message: 已受理}), 202接口对外返回的是一个“已受理”状态异步创建完成后可以通过轮询GET /api/v1/servers/xxx来获取最终状态。这种异步设计也符合云平台本身的语义。5.2 用 Java 调用 OpenStack API 的思路Java 场景下最直接的方法是使用HttpClient或RestTemplate发送带 Token 的 HTTP 请求。核心流程和 curl 一样先请求 Keystone 获取 Token再把 Token 放在后续请求头中。一个简化版的骨架import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; public class OpenStackClient { private String token; public String authenticate(String authUrl, String username, String password) throws Exception { String body String.format( {\auth\:{\identity\:{\methods\:[\password\],\password\:{\user\:{\name\:\%s\,\domain\:{\name\:\Default\},\password\:\%s\}}},\scope\:{\project\:{\name\:\admin\,\domain\:{\name\:\Default\}}}}}, username, password ); HttpRequest request HttpRequest.newBuilder() .uri(URI.create(authUrl /v3/auth/tokens)) .header(Content-Type, application/json) .POST(HttpRequest.BodyPublishers.ofString(body)).build(); HttpResponseString response HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); this.token response.headers().firstValue(X-Subject-Token).orElseThrow(); return token; } public static void main(String[] args) throws Exception { OpenStackClient client new OpenStackClient(); String token client.authenticate(http://10.0.0.10:5000, admin, YourAdminPass123); System.out.println(Token: token); } }你会发现 Java 调用 OpenStack API 和 Python 没什么本质区别它的难点不在语言而在认证流程和资源 ID 的拼接。只要理解了前面 curl 那几步用任何语言都能复刻。5.3 为什么封装层要独立设计“业务语义”练习到这一步我特别建议想一想你自己要提供给调用方的接口应该长什么样是一次性把参数全部收齐的创建接口还是分步提交、支持后续查询状态的接口实际项目里最常用的模式是对外接口业务含义底层 OpenStack 操作POST /api/v1/vm/create申请一台云主机Neutron 查网络、Nova 创建实例GET /api/v1/vm/{id}查询主机状态Nova 查询 serverDELETE /api/v1/vm/{id}释放云主机Nova 删除 server、可选删除云硬盘我看到很多人练习时只做了“创建”没有做“释放”。但释放资源在云平台里其实同样重要不释放的云主机一直在扣配额尤其在练习环境里资源很快会被耗尽。所以建议至少把一个“完整生命周期”调通过创建 → 查询 → 删除。当然VC 这类客户端要接入这个封装层并不需要关心它背后是 OpenStack只需要按普通 HTTP REST API 的方式调你的接口即可。这也是为什么我强调要独立设计封装层语言生态各不同但 HTTP 协议是通用的。6. 练习阶段绕不开的坑以及怎么快速排查最后这部分我把练习 OpenStack API 过程中踩过最深、也最影响效率的几个坑整理出来。这些“坑”跟前文的步骤不完全一样它们更多属于“你明明按文档做了但就是不对”的疑难杂症。6.1 版本号与接口路径不匹配报错摸不着头脑OpenStack 各个服务的 API 版本演进并不一致。Nova 现在的标准版本是 v2.1但 Keystone 是 v3Glace 是 v2Neutron 用 v2.0Cinder 又用 v3。如果把任意一个端点的版本号当成“所有服务统一版本”来写就会有一批请求返回 404。排查思路很简单先用浏览器或 curl 访问各端点的根路径看看返回的 JSON 中有没有版本列表。比如访问http://10.0.0.10:8774/会看到 Nova 支持的版本及微版本范围。不要凭感觉猜要以端点实际返回的版本信息为准。6.2 项目 Scope 错误导致的“看不见资源”这是权限类错误里最容易被忽略的一种。用 admin 用户 scoped 到 admin 项目后再去 API 请求另一个项目的资源列表很可能返回空。不是资源不存在而是 Token 的 scope 决定了你看不到另一个项目。当时我遇到过一种更隐蔽的情况在脚本里存了一个 Token但这个 Token 是用domain级别的 scope 取得的没有指定具体 project。用它调 Nova 接口时返回 403报错是“Policy doesnt allow this action to be performed”。解决方式很简单重新发起认证请求在scope里补上项目信息。6.3 云主机创建成功但 IP 不通优先查 DHCP 和命名空间练习环境里最经典的“静默故障”是云主机起来了状态 ACTIVE但 ping 不通。这时多数人第一反应去查 Nova实际上问题往往出在 Neutron。按这个顺序排查看云主机的addresses字段确认 OpenStack 认为它应该有什么 IP登录网络节点All-in-One 环境下就是部署机用ip netns list查看是否存在对应的 DHCP 命名空间和路由命名空间检查该命名空间内端口是否 UP用ip netns exec qrouter-xxx ping 云主机IP测试三层连通性最后再看安全组规则默认安全组可能不放行 ICMP。这个排查链路走一遍能帮你把 Nova 和 Neutron 的边界彻底理清楚Nova 只管实例生命周期网络通不通得找 Neutron。6.4 API 请求中的字段大小写和嵌套结构OpenStack API 对 JSON 字段名和嵌套结构的处理比较严格。比如 Neutron 创建网络时请求体必须是最外层{network: {...}}的包裹结构不能直接把网络属性写在顶层。很多练习者第一次直接 POST{name: demo}收到 400 后完全不明白为什么。我的经验是练习时先打开官网 API Reference对照请求示例逐字段检查不要凭记忆写。对于这种后端用 Python 开发、序列化框架到处可见的项目少一个嵌套层的报错信息经常是“Malformed request body”看不出具体是哪个字段的问题。6.5 善用 OpenStack 自带的调试工具别瞎猜最后推荐一个我平时最常用的排查组合。想让 CLI 显示底层 HTTP 请求可以设置环境变量OS_DEBUG1或者给命令加--debug参数。这样你能看到openstack server create实际发出的每一个请求路径、请求体和响应头。在自己用 curl 练习时始终加上-i参数先看响应头和 HTTP 状态码再解析响应体。如果怀疑服务内部报错直接去看容器日志。Kolla 部署环境下用docker logs 容器名或kolla-ansible -i hosts dump查看对应服务的日志。这套方法比反复猜字段名、猜权限问题要高效得多。只要把“实际 HTTP 请求长什么样”完整拿在手里大部分问题都能一眼定位。最后聊一下我个人的体会。API 练习这件事真正难的不是记命令而是建立一种“资源思维”任何一步操作你都能反过来想清楚它在 API 层调用了哪个服务、需要哪些前置资源、返回什么状态。当你练到闭着眼睛能把创建云主机的整个调用链背出来再去看那些云管平台、自动化运维系统你会发现它们也没什么神秘的本质就是在 OpenStack API 之上做业务封装、权限收敛和状态管理。如果你现在正好也要做类似练习建议不要贪多先把“认证→建网→建主机→查询→删除”这条最短链路完整走通再慢慢扩展镜像、云硬盘、快照。链路通了后面所有高级玩法都只是在这个框架上增加资源类型而已。