1. 实训六虚拟机不是装个VMware就叫云计算实训“实训六虚拟机”这个标题乍看平平无奇甚至有点像某本实验手册里被随手翻到的一页——没有品牌、没有系统、没有具体任务只有五个字。但结合后台爬取的上百条热搜词和那篇博客园里关于OpenStack创建虚拟机的29步流程你立刻能嗅到味道这根本不是教你怎么在Windows上装个VMware Workstation跑Ubuntu的入门课。它是一门以OpenStack为底座、以Nova/Keystone/消息队列为筋骨、以真实云平台调度逻辑为灵魂的综合工程实训。我带过三届云计算方向的学生实训每次开课前最常听到的疑问是“老师我们不是学OpenStack吗为什么第一周还在折腾VMware密钥和Win11蓝屏”——这恰恰暴露了当前教学最大的断层把“虚拟机”当成一个静态的、孤立的软件工具来教而忽略了它在云原生架构中是一个被调度、被认证、被编排、被网络和存储深度耦合的动态服务实例。真正的“实训六”核心不是“怎么启动一台虚拟机”而是“当用户在Web界面上点下‘创建实例’按钮后背后30秒内发生了什么谁在说话谁在等待谁在拒绝”关键词里反复出现的“Nova”“Keystone”“消息队列”不是装饰性标签而是三个必须亲手拧紧的螺丝。Nova是云平台的“产房”负责虚拟机的生老病死Keystone是守门人不验明正身连产房的门都推不开消息队列则是整个系统的“神经中枢”所有组件之间不打电话、不发邮件只往同一个消息池里扔纸条再各自去捞属于自己的那一张。这三者缺一不可就像做一道红烧肉少了冰糖、老抽或八角味道就全变了。所以这篇内容不会教你如何下载VMware密钥也不会告诉你Win11虚拟机转圈圈时该按哪个快捷键。它会带你从一条HTTP请求出发逆向拆解OpenStack创建虚拟机的完整生命线把博客园里那29个步骤变成你能亲手验证、调试、甚至篡改的活体流程。你会看到auth-token如何在Keystone里生成又失效看到nova-scheduler如何在数据库里翻找空闲主机看到Pulsar或RabbitMQ消息队列里那些被序列化的JSON payload长什么样。这不是理论复述这是把云平台的“黑盒子”拆开让你看清每一颗齿轮的咬合方式。2. 为什么必须从Keystone认证开始没有身份一切皆空几乎所有OpenStack实训的失败都卡在第一步——认证。学生常抱怨“我填了用户名密码点登录就报错401”或者更隐蔽的“我能登录但创建虚拟机时提示‘Forbidden’”。这些错误背后不是密码输错了而是对Keystone的工作机制存在根本性误解。Keystone不是简单的账号密码校验器它是一个基于Token的、状态分离的、角色驱动的访问控制中心。跳过它直接操作Nova就像没拿钥匙就想发动汽车——引擎可能轰鸣但车轮纹丝不动。2.1 Keystone的认证链从密码到Token的三次跃迁当你在Horizon界面输入admin/admin并点击登录实际发生的是一个精密的三段式跃迁第一段密码到初始TokenUnscoped TokenKeystone接收到你的POST请求/v3/auth/tokens首先验证user和password字段是否匹配数据库中的哈希值。验证通过后它并不返回你的密码也不保存你的会话而是生成一个随机字符串——这就是Unscoped Token。它的关键特征是只证明“你是谁”不说明“你能干什么”。你可以用这个Token去查询Keystone自身的服务目录/v3/auth/catalog但无法调用Nova的任何API。此时Token的有效期极短默认1小时且不含任何项目Project上下文。提示用curl手动触发这一步是排查认证问题的黄金起点。命令如下curl -i -X POST http://controller:5000/v3/auth/tokens \ -H Content-Type: application/json \ -d { auth: { identity: { methods: [password], password: { user: { name: admin, domain: {id: default}, password: ADMIN_PASS } } } } }如果返回201 Created且Header里有X-Subject-Token说明第一关已过若返回401 Unauthorized请立刻检查ADMIN_PASS是否与keystone.conf中[DEFAULT] admin_password一致——这是90%初学者栽跟头的地方。第二段Unscoped Token到Scoped TokenProject Scoped拿到Unscoped Token后Horizon会立即发起第二次请求/v3/auth/tokens但这次Body里多了一个关键字段scope。它告诉Keystone“我现在不仅要知道我是谁还要知道我要在哪个项目里干活”。Keystone会验证该用户是否属于指定项目Project以及该项目是否存在。验证通过Keystone生成一个新的Scoped Token。这个Token才是真正的“工作证”它包含了project_id、user_id、roles如admin、member等信息并被写入后端数据库通常是MySQL的token表中。注意Scoped Token的权限完全由roles决定。如果你用admin用户登录但Scoped Token里roles数组为空那创建虚拟机必然失败。此时需检查keystone-manage bootstrap初始化时是否正确绑定了角色或手动执行openstack role add --project admin --user admin admin第三段Token的传递与验证Nova的二次校验当Horizon带着Scoped Token调用Nova API如POST /v2.1/servers时Nova服务并不会信任这个Token。它会主动向Keystone发起RPC调用keystoneclient.get_token_info(token)将Token发回Keystone进行实时验证。Keystone查数据库确认Token未过期、未撤销并返回包含完整角色信息的JSON。Nova据此判断该用户是否有os_compute_api:servers:create权限。如果Keystone返回404Token不存在或401Token无效Nova立刻返回401 Unauthorized整个流程终止。这个设计的意义在于Keystone是唯一可信的认证源所有服务都必须向它实时询证杜绝了Token被伪造或缓存导致的权限越界。这也是为什么你在Nova日志里常看到INFO nova.api.openstack.compute.token_validator [-] Validating token...这样的记录——它不是摆设是每秒都在发生的严肃审查。2.2 实操陷阱Token过期与角色缺失的双重绞杀在实训中最典型的“玄学故障”是上午还能创建虚拟机下午突然全部报403 Forbidden。日志里找不到明显错误重启服务也无效。这几乎100%是Token过期策略与角色配置的组合拳。Token过期陷阱OpenStack默认Token有效期为1小时[token] expiration 3600。但很多实训环境为了“省事”把expiration设为0永不过期或极大值如86400。这看似方便实则埋雷。因为Keystone的Token表会持续膨胀当表中记录超过百万级SELECT * FROM token WHERE id %s查询会严重拖慢验证速度导致Nova超时放弃返回504 Gateway Timeout。更糟的是某些版本Keystone在Token表过大时会直接拒绝新Token生成。角色缺失陷阱另一个高频坑是“用户有项目但项目里没角色”。例如你用openstack project create demo创建了demo项目用openstack user create demo创建了demo用户但忘了执行openstack role add --project demo --user demo member。此时demo用户能登录Horizon因为有Unscoped Token也能看到demo项目但一旦尝试创建资源Nova在验证Token时发现roles为空立刻拒绝。这种错误在日志里表现为WARNING nova.api.openstack.compute.token_validator [-] User demo has no roles in project demo但Horizon前端只显示模糊的“Operation not allowed”。我的实战经验每次实训环境初始化后必做三件事检查/etc/keystone/keystone.conf中[token] expiration是否为3600严格遵循生产环境规范执行mysql -u root -p -e USE keystone; SELECT COUNT(*) FROM token;若结果10000立即执行keystone-manage token flush清空过期Token对每个项目执行openstack role list --project PROJECT_NAME --user USER_NAME确保输出至少有一行member或admin。这三步做完80%的认证类故障自动消失。3. Nova调度器的暗箱消息队列如何决定虚拟机落在哪台物理机当Keystone放行后Nova API收到创建虚拟机的请求它不会自己动手造机器而是立刻把任务“甩锅”给Nova Scheduler。这个过程常被简化为“调度器选一台空闲主机”但真相远比这复杂——它是一场在毫秒级完成的、涉及数据库查询、算法计算、规则过滤与权重打分的精密博弈。而连接Nova API与Scheduler的正是那个被热搜词反复提及却极少被深究的“消息队列”。3.1 消息队列OpenStack的“异步神经系统”OpenStack所有核心服务Nova、Neutron、Cinder、Glance都采用AMQPAdvanced Message Queuing Protocol协议通信主流实现是RabbitMQ或Pulsar。它的核心哲学是服务之间不直接调用只通过消息队列交换事件。这种设计带来三大优势解耦、异步、容错。Nova API只需把“创建虚拟机”的请求塞进nova队列就可以立刻返回HTTP 202 Accepted不用等虚拟机真正启动Scheduler可以随时上线或下线只要它监听nova队列就能捡到任务。但这也带来了调试难点问题不出现在代码里而出现在消息的传递与消费过程中。你可能在Nova API日志里看到INFO nova.api.openstack.compute.servers [-] Triggering async build...但在Scheduler日志里却找不到任何响应。这时问题一定出在消息队列的连通性或权限上。验证消息队列连通性的终极命令以RabbitMQ为例sudo rabbitmqctl list_queues name messages_ready messages_unacknowledged正常情况下nova队列的messages_ready应为0任务被及时消费messages_unacknowledged也应为0无消息卡住。如果messages_ready 0且持续增长说明Scheduler进程挂了或配置错误如果messages_unacknowledged 0说明Scheduler消费了消息但没来得及发送ACK确认可能是处理超时或崩溃。3.2 调度算法的四重过滤从“能运行”到“最优选”Nova Scheduler不是随机选主机它执行一套严格的四阶段过滤链Filter Scheduler每一步都像一道安检门第一道门ComputeFilter计算能力过滤检查主机CPU、内存、磁盘是否满足虚拟机规格Flavor。例如你申请一个m1.small1 vCPU, 2GB RAM, 20GB Disk的虚拟机Scheduler会查询nova.services表中binarynova-compute且statusup的主机再查nova.compute_nodes表中对应主机的free_ram_mb 2048、free_disk_gb 20、vcpus_used vcpus。注意这里的free_ram_mb是Nova计算节点上报的值不是Linuxfree -m的输出它可能因hypervisorKVM/QEMU的内存overcommit策略而不同。第二道门AvailabilityZoneFilter可用区过滤检查主机是否在用户指定的可用区Availability Zone内。例如用户在Horizon里选择了nova可用区Scheduler会过滤nova.services表中availability_zonenova的主机。如果所有主机都配置为internal默认而用户选了nova则无主机匹配直接报错No valid host was found。第三道门RamFilter内存隔离过滤这是最容易被忽略的“隐形杀手”。当启用内存超分配[DEFAULT] ram_allocation_ratio 1.5时Nova允许物理内存被超额分配。但RamFilter会强制检查即使超分配单台主机上所有虚拟机的总内存也不能超过physical_ram * ram_allocation_ratio。如果一台主机物理内存16GBram_allocation_ratio1.5则最大可分配24GB。若已有20GB虚拟机在运行再申请一个8GB的就会被此过滤器拒绝。第四道门LeastUsedFilter最少使用率过滤这才是真正的“智能选择”。它不看绝对空闲值而看使用率百分比。计算公式为host_weight (free_ram_mb / total_ram_mb) (free_disk_gb / total_disk_gb)。值越大说明主机越空闲权重越高。Scheduler最终会选择权重最高的主机。但注意这个权重只影响“同条件下的排序”不改变过滤结果。如果只有两台主机通过前三道门它才在这两者间比权重如果只有一台通过它就直接选这一台不管权重多低。实训中一个经典案例学生创建虚拟机总是失败日志显示NoValidHost: No valid host was found。排查发现所有计算节点的nova-compute服务状态都是up但nova.service-list显示它们的State列为down。原因在于nova-compute进程虽然在运行但未能成功向数据库注册心跳nova.compute_nodes表无更新。这通常是因为计算节点的nova.conf中[DEFAULT] my_ip配置错误指向了内网IP而非管理网络IP导致心跳包发不到Controller节点。解决方案修正my_ip重启nova-compute然后执行nova service-cleanup清理僵尸服务。3.3 调度结果的落地数据库里的“宿主绑定”当Scheduler选定主机如compute01后它做的第一件事不是通知nova-compute而是立刻更新数据库。它会执行SQLUPDATE instances SET hostcompute01, nodecompute01 WHERE uuidINSTANCE_UUID。这一步至关重要——它把虚拟机的“归属权”在数据库层面锁定。如果后续nova-compute在compute01上创建失败Nova会根据这个host字段将错误状态同步回数据库并标记实例为ERROR。为什么这步不能省略因为OpenStack是分布式系统nova-api、nova-scheduler、nova-compute可能部署在不同物理机上。如果只靠消息队列传递一旦nova-compute崩溃nova-api无法知道虚拟机该归谁管。数据库的host字段是唯一的、强一致的“事实来源”。这也是为什么你在nova list里看到虚拟机状态是BUILD正在构建但nova show INSTANCE_ID里OS-EXT-SRV-ATTR:host字段已经显示compute01——数据库更新发生在构建开始前而非构建完成后。4. 从消息到实例nova-compute如何把一行JSON变成一台活的虚拟机当Scheduler把“在compute01上创建虚拟机”的指令通过消息队列发给nova-compute进程后真正的魔法才开始。nova-compute不是一个黑盒它是一套精密的“虚拟机装配流水线”每一步都依赖外部服务的协同并将结果实时写入数据库。理解这条流水线是打通“实训六”任督二脉的关键。4.1 流水线第一站Conductor——虚拟机数据的中央厨房nova-compute收到消息后第一反应不是调用KVM而是向nova-conductor发起RPC调用conductor.instance_get_by_uuid。这看起来很反直觉计算节点自己不存数据为什么要问别人答案是安全与解耦。nova-compute直接访问数据库会带来两大风险1计算节点被攻破攻击者可直接读写所有虚拟机数据2数据库连接数爆炸每台计算节点都维持长连接。因此OpenStack强制所有数据库操作经由nova-conductor代理。nova-conductor接到请求后从数据库instances表中拉取该虚拟机的完整元数据flavor_id规格、image_ref镜像ID、networks网络配置、security_groups安全组等。它把这些数据打包成一个Python对象Instance再通过消息队列发回给nova-compute。此时nova-compute才真正拥有了“施工图纸”。关键细节nova-conductor返回的Instance对象里image_ref只是一个UUID如a1b2c3d4...不是镜像文件路径。nova-compute需要拿着这个UUID再去问Glance要真正的镜像URL。这就是为什么Glance服务必须正常——否则nova-compute会卡在“获取镜像”这一步日志里出现ERROR nova.virt.libvirt.driver [-] Cannot find image a1b2c3d4...。4.2 流水线第二站Glance——镜像的“云上仓库”nova-compute拿到image_ref后会构造一个HTTP GET请求发给Glance APIhttp://glance:9292/v2/images/IMAGE_UUID。这个请求必须携带有效的auth-token从Keystone获得否则Glance会返回401。Glance验证Token后返回镜像的详细信息其中最关键的是locations字段它指明了镜像文件的实际存储位置例如locations: [ { url: file:///var/lib/glance/images/a1b2c3d4..., metadata: {} } ]对于本地文件存储Filesystem Storenova-compute会直接读取这个路径下的qcow2文件对于对象存储如Swift、Ceph它会通过对应的driver下载镜像到本地缓存目录/var/lib/nova/instances/_base/。这里有个隐藏性能点如果镜像很大如10GB首次下载会非常慢且所有计算节点都要重复下载。生产环境通常用Ceph RBD作为后端镜像以块设备形式存在nova-compute直接attach无需下载。实训避坑学生常遇到nova-compute日志报Image a1b2c3d4... not found。除了检查Glance服务状态更要检查/etc/nova/nova.conf中[glance] api_servers是否指向正确的Glance地址如http://controller:9292以及[DEFAULT] use_neutron是否为True影响网络驱动加载。4.3 流水线第三站Neutron——网络的“即插即用”在下载镜像的同时nova-compute并行地向Neutron Server发起请求获取虚拟机所需的网络信息。它会调用GET /v2.0/ports?device_idINSTANCE_UUID查询所有绑定到该虚拟机的端口Port。每个Port代表一个虚拟网卡包含mac_addressMAC地址、fixed_ipsIP地址、network_id网络ID等信息。Neutron Server收到请求后会查询其数据库找到对应的Port记录再根据network_id查出网络类型Flat、VLAN、VXLAN、子网Subnet网关、DNS服务器等。最终它返回一个完整的Port对象给nova-compute。nova-compute拿到后会调用libvirt的API为虚拟机创建一个interface typebridge的XML配置并将mac_address和fixed_ips注入其中。网络故障的典型表现虚拟机创建成功nova list显示ACTIVE但无法ping通网关。此时检查neutron port-list --device-id INSTANCE_UUID如果输出为空说明Neutron没有为该实例创建Port——常见原因是nova.conf中[neutron] url配置错误或Neutron Server的ml2_conf.ini中mechanism_drivers未启用openvswitch。4.4 流水线终点Libvirt——KVM的“最后一公里”当镜像、网络、存储如果用了Cinder全部就绪nova-compute终于调用libvirt API执行最后的创建动作。它会生成一个XML定义文件核心片段如下domain typekvm nameinstance-00000001/name memory unitMB2048/memory vcpu1/vcpu os type archx86_64 machinepc-i440fx-bionichvm/type boot devhd/ /os devices disk typefile devicedisk driver nameqemu typeqcow2/ source file/var/lib/nova/instances/.../disk/ target devvda busvirtio/ /disk interface typebridge mac addressfa:16:3e:xx:xx:xx/ source bridgebr-int/ model typevirtio/ /interface /devices /domain然后执行virsh define和virsh start命令。此时KVM内核模块被激活一个真正的Linux进程qemu-system-x86_64被创建它模拟出CPU、内存、硬盘、网卡等硬件并加载qcow2镜像作为根文件系统。最后的验证登录到compute01执行virsh list --all你应该看到instance-00000001状态为running执行ps aux | grep qemu能看到对应的进程执行ip netns exec qdhcp-NETWORK_ID ip a能看到DHCP namespace里的IP分配情况。这三步全部成功才意味着“实训六”的虚拟机真正从一行代码变成了一台活的、可交互的云上服务器。5. 故障诊断全景图从HTTP 500到qemu进程的逐层穿透在“实训六虚拟机”的实操中90%的问题不是功能不会用而是故障不会诊。学生看到nova list里虚拟机状态卡在BUILD或直接变成ERROR第一反应是重启服务、重装系统。这就像医生不看CT片就开刀。真正的高手懂得沿着OpenStack的请求链路一层层向下穿透直到定位到那个具体的、可修复的故障点。5.1 第一层Horizon与Nova API的HTTP对话所有问题始于浏览器。当你在Horizon点击“Launch Instance”浏览器开发者工具F12的Network标签页会捕获到一个POST /dashboard/project/instances/请求。查看它的Response如果是500 Internal Server Error说明Horizon后端Django调用Nova API失败如果是400 Bad Request说明表单填写有误如镜像不存在如果是401 Unauthorized回到第2节检查Keystone。快速定位法在Controller节点实时监控Nova API日志sudo tail -f /var/log/nova/nova-api.log | grep -E (ERROR|WARNING)当你点击创建按钮如果日志里立刻出现ERROR nova.api.openstack.compute.servers [-] Failed to create instance说明问题在API层如果日志静默说明请求根本没到达Nova API——此时检查Apache/Nginx是否在监听5000端口sudo ss -tlnp | grep :5000。5.2 第二层Nova服务间的RPC与消息流如果API日志显示INFO ... Triggering async build...说明请求已进入异步流程。下一步要看Scheduler和Compute的日志。Scheduler日志诊断sudo tail -f /var/log/nova/nova-scheduler.log | grep -E (ERROR|WARNING|INFO.*Filter)如果看到INFO ... Filtered [compute01, compute02]说明过滤成功如果看到WARNING ... No hosts passed the filters说明四重过滤全失败回到第3节检查主机资源、可用区、内存超配如果日志完全空白说明Scheduler没收到消息——检查RabbitMQ队列sudo rabbitmqctl list_queues和nova.conf中[DEFAULT] transport_url是否指向正确RabbitMQ地址。Compute日志诊断sudo tail -f /var/log/nova/nova-compute.log | grep -E (ERROR|WARNING|INFO.*spawn)如果看到INFO ... Starting instance...说明已收到调度指令如果看到ERROR ... Cannot find image检查Glance如果看到ERROR ... Failed to get network info检查Neutron如果看到ERROR ... libvirtError: internal error: process exited while connecting to monitor说明KVM/QEMU环境有问题如CPU不支持虚拟化/proc/cpuinfo | grep vmx为空。5.3 第三层底层服务与系统状态的交叉验证当日志指向某个具体服务如Glance不要只看它的日志要进行交叉验证。Glance验证三板斧openstack image list—— 能否列出镜像若报错检查openstack endpoint list | grep glance确认endpoint URL正确curl -H X-Auth-Token: $TOKEN http://controller:9292/v2/images—— 直接调用API绕过CLIls -l /var/lib/glance/images/—— 镜像文件是否存在权限是否为glance:glanceNeutron验证三板斧neutron net-list—— 能否列出网络若报错检查neutron-server进程是否在运行sudo systemctl status neutron-serverovs-vsctl show—— Open vSwitch桥接是否建立br-int、br-ex是否存在ip link show—— 物理网卡如eth0是否UPovs-system网桥是否绑定到它5.4 第四层KVM与QEMU的终极现场当nova-compute日志显示Starting instance但virsh list里没有虚拟机问题一定在KVM层。KVM环境检查清单egrep -c (vmx|svm) /proc/cpuinfo—— 返回0说明CPU不支持硬件虚拟化需在BIOS中开启Intel VT-x或AMD-Vlsmod | grep kvm—— 应输出kvm_intel或kvm_amd及kvmdmesg | grep -i kvm—— 查看内核日志是否有KVM: disabled by BIOS等错误qemu-system-x86_64 --version—— QEMU版本是否兼容OpenStack Wallaby要求QEMU 5.2。QEMU进程调试如果virsh list有虚拟机但状态为paused执行virsh resume INSTANCE_NAME如果状态为shut off执行virsh start INSTANCE_NAME。若报错error: failed to get domain instance-00000001说明libvirt未加载该域定义执行virsh define /etc/libvirt/qemu/instance-00000001.xml。我的压箱底技巧当所有日志都沉默用strace抓nova-compute的系统调用。命令sudo strace -p $(pgrep -f nova-compute) -e traceopen,connect,sendto,recvfrom -s 1024 -o /tmp/nova-strace.log然后创建虚拟机查看/tmp/nova-strace.log。你会看到它试图connect到127.0.0.1:9292Glance却失败或open(/dev/kvm)返回-1 EPERM权限不足。这是最原始、最可靠的故障定位法比读一百行日志都管用。6. 实训六的延伸价值从虚拟机到云原生的思维跃迁做完“实训六虚拟机”如果只是记住openstack server create这条命令那不过是把课本翻到了第六页。真正的价值在于它为你打开了一扇门一扇通往云原生世界底层逻辑的门。OpenStack的Nova/Keystone/消息队列不是过时的技术而是云基础设施的“汇编语言”。今天你看到的Kubernetes Pod调度、Service Mesh的流量治理、Serverless的函数冷启动其内核思想都能在Nova的29步流程里找到原型。比如Kubernetes的Scheduler和Nova Scheduler本质都是“资源匹配器”前者匹配Pod的requests/limits与Node的allocatable后者匹配Flavor与Compute Node的free_*Kubernetes的etcd和Keystone的数据库都是“单一事实来源”所有组件必须向它询证Istio的Sidecar注入和nova-compute为虚拟机注入网络配置都是在运行时动态编织基础设施能力。所以别把“实训六”当成一个孤立的实验。把它当作一次云基础设施的考古挖掘——你亲手挖出了认证的基石Keystone、调度的引擎Nova Scheduler、通信的神经消息队列、执行的肌肉nova-compute/libvirt。下次看到“K8s Pod Pending”你会本能地去查kubectl describe pod里的Events就像现在你会查nova-compute.log看到“Service Mesh流量异常”你会想到Neutron的Port Binding和OVS流表就像现在你会查ovs-ofctl dump-flows br-int。这才是“实训六虚拟机”最不该被忽略的结尾。它不教你如何应付考试而是给你一把解剖刀让你未来面对任何云技术都有能力切开表皮看清血管与神经的走向。毕竟所有炫目的云服务都不过是这套古老而坚实的逻辑在更高维度上的优雅复现。