OpenStack All-in-One 手动部署实战:从零构建可调试云平台 1. 项目概述为什么今天还要亲手搭一个 OpenStack All-in-One 环境OpenStack allinone 方式部署说白了就是把 Nova、Neutron、Cinder、Glance、Keystone、Horizon 这六大核心服务——连同它们依赖的数据库MariaDB、消息队列RabbitMQ、缓存Memcached甚至 Web 服务器Apache——全部塞进一台物理机或虚拟机里跑起来。它不是生产环境该用的架构但却是理解 OpenStack 真实工作逻辑最不可替代的“解剖台”。我带过十几期云计算实训班每次开课前第一件事就是让学员在自己笔记本上用 VirtualBox 跑起一个 allinone 环境不是为了省事而是因为只有亲眼看到openstack server create命令发出后Nova-scheduler 怎么选节点、Nova-compute 怎么调用 libvirt 启动 QEMU 进程、Neutron-server 怎么下发 OVS 流表、Glance 怎么把镜像从 HTTP 下载并写入本地文件系统——你才真正开始“看见”云平台的毛细血管。很多人刷到“openstack还有必要学吗”这类热搜时心里其实藏着两个真实问题一是“现在都用 Kubernetes 了OpenStack 是不是过时了”二是“就算要学非得手敲命令搭一遍吗不能直接 docker run 或一键脚本”——这两个问题我都试过。三年前我用 Docker Compose 拉起过一套精简版 OpenStack 组件能登录 Horizon、创建实例但当我想调试网络不通的问题时发现容器里没有ovs-vsctl show、没有brctl、日志分散在十几个容器里根本没法复现真实部署中 Neutron-L3-agent 和 OVS agent 的协同逻辑。后来我又试过某厂商提供的“一键部署脚本”20 分钟跑完界面漂亮但/etc/neutron/plugins/ml2/ml2_conf.ini里 driver 配置被硬编码成openvswitch而实际企业环境里可能要用linuxbridge或sriov脚本没留任何钩子让你改等于给你盖好一栋楼却不给钥匙。所以今天重推 allinone 部署不是怀旧而是因为它强制你直面每一个配置项flat_networks physnet1是什么意思provider:physical_network physnet1和provider:network_type flat在创建网络时怎么联动这些细节只有亲手配错三次、查五次日志、重启七次服务之后才会刻进肌肉记忆。这个实操特别适合三类人第一类是刚考完 RHCSA/CE想把 Linux 底层能力迁移到云平台的同学第二类是运维工程师日常管着几十台 K8s Node但对“虚拟机怎么真正跑起来”缺乏系统认知第三类是高校教师或培训讲师需要一套稳定、可讲解、可打断、可故障注入的教学沙箱。它不追求高可用但要求每个组件的启动顺序、端口占用、用户权限、SELinux 上下文都清清楚楚。接下来我会带你从一块干净的 CentOS Stream 9 系统开始不用 Packstack它太黑盒不用 DevStack它太激进就用 OpenStack 官方推荐的openstack-packstack工具链但全程手动干预关键配置确保每一步你都知道“为什么必须这样”。2. 整体设计与思路拆解为什么放弃 Packstack 自动化选择半手工模式Packstack 确实能一条命令完成 allinone 部署packstack --allinone。我第一次用它时27 分钟搞定比官方文档写的还快。但三个月后客户现场出问题我 SSH 进去一看/root/answers.txt文件里密密麻麻全是自动生成的随机密码和 IP 地址/var/log/下的日志按服务名分目录但 Nova 的日志又分散在nova-api.log、nova-scheduler.log、nova-compute.log三个文件里而错误堆栈偏偏跨了两个文件——这时候 Packstack 的“自动化”反而成了障碍。它把所有配置揉进 Puppet 模板你改一个参数得先找到对应模块路径再改.pp文件最后还得 rerun packstack整个过程比手动改配置还慢。所以这次我们采用“Packstack 生成骨架 手工精调配置 systemd 服务级管控”的混合模式核心逻辑就三点第一用 Packstack 生成初始配置但绝不让它自动执行安装。执行packstack --gen-answer-fileanswer.txt后我们拿到的是一个结构清晰、注释完整的 INI 格式应答文件。它把所有服务开关、网络配置、存储路径、认证方式都列成键值对比如CONFIG_NEUTRON_OVS_BRIDGE_MAPPINGSphysnet1:br-ex这一行直接告诉你物理网卡eth0映射到 OVS 网桥br-ex比翻 Neutron 文档快十倍。更重要的是这个文件本身就是一个极佳的学习地图——你顺着CONFIG_开头的变量名就能串起整个 OpenStack 的服务依赖图CONFIG_NOVA_COMPUTE_PRIVILEGEDfalse暗示计算节点是否需要 root 权限CONFIG_CINDER_VOLUMES_CREATEn表明不自动创建 LVM 卷组这正好符合我们用本地磁盘做 Cinder 后端的需求。第二禁用 Packstack 的 Puppet 执行引擎改用yum installsystemctl enable的原生方式安装服务。Packstack 默认调用 Puppet agent会在/var/lib/puppet下生成大量缓存和 catalog导致systemctl status neutron-server显示Loaded: loaded (/usr/lib/systemd/system/neutron-server.service; enabled; vendor preset: disabled)但实际进程却是 Puppet 启动的 Ruby 进程PID 和 systemd 不一致。我们绕过这层直接yum install openstack-neutron-server然后systemctl daemon-reload确保每个服务的生命周期完全由 systemd 管控。这样做的好处是journalctl -u neutron-server -f能实时看到完整日志流systemctl stop nova-compute systemctl start nova-compute可以精准控制单个服务启停不会像 Puppet 那样牵一发而动全身。第三网络模型锁定为flatlocal混合模式彻底避开复杂的 VXLAN 或 GRE 封装。很多新手卡在 Neutron 网络上不是因为概念不懂而是因为ml2_conf.ini里type_drivers vxlan,flat和tenant_network_types vxlan这两行配置让 OpenStack 默认创建 VXLAN 网络而你的物理机根本没开 UDP 8472 端口结果openstack network create net01成功但openstack server create却卡在BUILD状态。我们直接删掉vxlan只留flat并明确指定provider:physical_network physnet1这样所有网络流量都走物理网卡直通排查时tcpdump -i eth0 port 53就能看到 DNS 请求ip a s br-ex就能确认网桥状态没有任何封装层干扰。这种“降维打击”式的简化不是偷懒而是把学习焦点从“网络协议栈”拉回到“服务间调用逻辑”本身。3. 核心细节解析与实操要点从系统初始化到 Horizon 登录的 12 个生死关3.1 系统准备CentOS Stream 9 的 5 个硬性前提OpenStack Yoga 版本当前 LTS官方支持的最小操作系统是 CentOS Stream 8但我们选 Stream 9原因很实在Stream 9 默认内核 5.14自带virtio-fs支持这对后续跑 Windows 镜像很重要而且它的dnf包管理器对 Python 3.9 兼容性更好避免pip3 install python-openstackclient时出现ModuleNotFoundError: No module named distutils.util这种经典坑。但 Stream 9 也有陷阱必须提前处理关闭防火墙但保留 firewalld 服务systemctl stop firewalld systemctl disable firewalld是错的。正确做法是firewall-cmd --permanent --add-port6080/tcp firewall-cmd --permanent --add-port5000/tcp firewall-cmd --reload只放开 Horizon6080、Keystone5000、Nova API8774等必需端口。为什么因为某些 OpenStack 服务如 Neutron DHCP agent会动态打开端口全关防火墙等于裸奔而 firewalld 的 rich rules 能精确控制。禁用 NetworkManager 对 OVS 网桥的接管这是 allinone 最隐蔽的杀手。默认情况下NetworkManager 会监控/etc/sysconfig/network-scripts/ifcfg-br-ex并尝试管理它导致ovs-vsctl add-br br-ex创建的网桥几分钟后自动消失。解决方法是编辑/etc/NetworkManager/NetworkManager.conf在[keyfile]段落下加unmanaged-devicesinterface-name:br-ex;interface-name:br-int然后systemctl restart NetworkManager。这个配置必须在创建任何 OVS 网桥前完成否则你会反复遇到“网桥存在但无法分配 IP”的诡异问题。SELinux 必须设为 permissive而非 disabledsetenforce 0是临时方案sed -i s/SELINUXenforcing/SELINUXpermissive/g /etc/selinux/config才是持久化操作。为什么不用 disabled因为部分 OpenStack 服务如 Glance registry依赖 SELinux 的httpd_sys_rw_content_t上下文写入/var/lib/glance/images/disabled 后反而报Permission denied。permissive 模式下SELinux 仍记录所有拒绝事件到/var/log/audit/audit.log你可以用ausearch -m avc -ts recent | audit2why分析具体策略缺失再用semanage fcontext -a -t httpd_sys_rw_content_t /var/lib/glance/images(/.*)? restorecon -Rv /var/lib/glance/images补全这才是真正的安全可控。时间同步必须用 chrony且指向内网 NTP 服务器timedatectl set-ntp true不够。OpenStack 各服务间 Token 有效期只有 1 小时如果 Keystone 服务器和 Nova-compute 服务器时间差超过 5 分钟openstack token issue返回的 token 就会被 compute 节点拒绝。我们用chrony替代ntpd编辑/etc/chrony.conf注释掉默认pool行添加server 192.168.100.1 iburst假设你有内网 NTP 服务器然后chronyc makestep强制校准。没有内网 NTP那就用server time1.aliyun.com iburst但务必加iburst参数首次同步时用 4 个包快速建立时钟偏移量。磁盘分区必须预留 20GB 以上/var/lib/nova/instances空间Packstack 默认把实例磁盘放在/var/lib/nova/instances而 Stream 9 的根分区默认只有 15GB。我们用lsblk查看磁盘布局如果/dev/sda2是根分区且小于 30GB必须在安装系统时手动划分/boot1GB/20GB/var/lib/nova/instances单独挂载 50GB。为什么强调这个因为openstack server create时Nova-compute 会先下载镜像到/var/lib/nova/_base/再qemu-img convert成 qcow2 格式写入instances/目录一个 Cirros 镜像就要占 120MBUbuntu Server 镜像轻松破 2GB空间不足直接导致实例创建失败错误日志里只显示No space left on device根本看不出是磁盘问题。3.2 Packstack 应答文件定制17 个必须修改的 CONFIG 参数生成answer.txt后不要急着运行。打开它用 Vim 的:set number显示行号定位到以下关键参数。我按重要性排序每一行都解释“改什么”和“为什么改”第 42 行CONFIG_SWIFT_INSTALLnSwift 是对象存储allinone 环境完全用不到。设为n可跳过 Swift 相关的 12 个服务安装节省 8 分钟部署时间也避免因swift-ring-builder配置错误导致整个部署中断。第 87 行CONFIG_CINDER_INSTALLy保持 y但第 95 行CONFIG_CINDER_VOLUMES_CREATEnCinder 是块存储服务必须启用但VOLUMES_CREATEn表示不自动创建 LVM 卷组。我们手动创建pvcreate /dev/sdb vgcreate cinder-volumes /dev/sdb这样可以确保卷组名和物理设备完全可控避免 Packstack 随机选错磁盘。第 133 行CONFIG_NEUTRON_INSTALLy第 141 行CONFIG_NEUTRON_SERVER_HOST192.168.100.10Neutron 必须启用且SERVER_HOST必须填你机器的真实管理 IP不是 127.0.0.1。很多教程写127.0.0.1结果 Horizon 里创建网络时提示Connection refused因为 Horizon 通过 Keystone 获取 Neutron endpoint而 endpoint 地址是http://127.0.0.1:9696但浏览器访问 Horizon 是从外部发起的根本连不上本地回环。第 155 行CONFIG_NEUTRON_L3_EXT_BRIDGEbr-ex第 159 行CONFIG_NEUTRON_OVS_BRIDGE_MAPPINGSphysnet1:br-ex这是网络命脉。br-ex是外部网桥必须和你物理网卡绑定physnet1是物理网络名必须和后续创建网络时的--provider-physical-network physnet1严格一致。大小写、拼写、冒号前后空格错一个就无法通信。第 188 行CONFIG_HORIZON_SSLnallinone 环境不用 HTTPS。设为n可跳过证书生成步骤避免因 OpenSSL 版本差异导致certbot失败。如果你坚持用 SSL那必须把第 192 行CONFIG_SSL_CERT/root/cert.crt和第 193 行CONFIG_SSL_KEY/root/cert.key指向真实证书路径并确保cert.crt的 CN 字段匹配你访问 Horizon 的域名如openstack.local。第 215 行CONFIG_HEAT_INSTALLnHeat 是编排服务初学者完全用不到。禁用它能减少 3 个服务进程和 2 个数据库表降低内存占用allinone 环境内存常卡在 4GB 边界。第 233 行CONFIG_MONGODB_INSTALLnMongoDB 是 Ceilometer 的后端用于监控数据存储。allinone 环境不需要监控禁用可省下 500MB 内存。第 256 行CONFIG_RABBITMQ_PASSWORDpassw0rd所有密码字段必须统一修改。Packstack 默认生成随机密码但answer.txt里有 12 个密码字段Keystone、RabbitMQ、MariaDB 等如果各自不同后期调试时openstack endpoint list显示正常但openstack server list却报Invalid OpenStack Identity credentials因为你根本记不住哪个服务用哪个密码。我们全部设为passw0rd注意含数字 0方便记忆和排查。第 278 行CONFIG_NTP_SERVERS192.168.100.1和前面时间同步呼应这里填你内网 NTP 服务器地址确保 Packstack 安装过程中所有服务的时间戳一致。第 301 行CONFIG_NOVA_COMPUTE_PRIVILEGEDfalse计算节点默认不启用特权模式这是安全基线。设为false符合生产习惯也避免因libvirt权限问题导致qemu-kvm进程无法启动。第 324 行CONFIG_GLANCE_BACKENDfile镜像后端用本地文件系统而不是 Swift 或 Ceph。file模式最简单/var/lib/glance/images/目录就是镜像仓库glance image-create后直接ls /var/lib/glance/images/就能看到文件便于验证。第 347 行CONFIG_KEYSTONE_ADMIN_PWpassw0rdKeystone 管理员密码必须和上面 RabbitMQ 密码一致这是整个认证体系的根密码。第 369 行CONFIG_NOVA_API_HOST192.168.100.10Nova API 服务绑定 IP必须和 Neutron 的SERVER_HOST一致保证所有服务 endpoint 指向同一地址。第 392 行CONFIG_NEUTRON_DHCP_AGENTS_PER_NETWORK1DHCP agent 数量allinone 环境设为 1 即可避免资源浪费。第 415 行CONFIG_NEUTRON_LBAAS_INSTALLnLoad Balancing as a Service初学者用不到禁用。第 438 行CONFIG_CEILOMETER_INSTALLnCeilometer 是计量服务禁用。第 461 行CONFIG_AODH_INSTALLnAodh 是告警服务禁用。改完这 17 行保存answer.txt。此时它已不是一个“自动安装脚本”而是一份精准的 OpenStack 架构说明书——每个CONFIG_变量都在告诉你“这个服务要装”“它监听哪个 IP”“它用什么后端”“它和谁通信”。这就是半手工模式的核心价值你不是在执行命令而是在阅读和签署一份分布式系统的契约。3.3 OVS 网桥与物理网卡绑定三步封死网络黑洞Neutron 网络是 allinone 部署的死亡谷80% 的失败案例集中在这里。Packstack 生成的answer.txt里CONFIG_NEUTRON_OVS_BRIDGE_MAPPINGSphysnet1:br-ex只是声明真正的绑定必须手动完成。我们分三步走每一步都有ip link和ovs-vsctl命令验证第一步创建 OVS 网桥并设置为 up 状态ovs-vsctl add-br br-ex ip link set br-ex up验证ip link show br-ex应显示state UPovs-vsctl show应列出br-ex网桥且无端口Ports 列为空。注意br-ex名字必须和answer.txt里CONFIG_NEUTRON_OVS_BRIDGE_MAPPINGS的值完全一致包括大小写。第二步将物理网卡从系统网络栈剥离挂载到 OVS 网桥假设你的物理网卡是ens192用ip link确认执行ip link set ens192 down ovs-vsctl add-port br-ex ens192 ip link set br-ex address $(cat /sys/class/net/ens192/address)验证ovs-vsctl list-ports br-ex应输出ens192ip a s br-ex应显示br-ex拥有ens192的 MAC 地址且inet段为空因为 IP 地址要配在br-ex上而不是ens192上。这一步最关键——如果漏掉ip link set ens192 downens192仍处于UP状态OVS 无法接管流量所有网络请求都会被系统内核丢弃。第三步为br-ex分配管理 IP并配置路由ip addr add 192.168.100.10/24 dev br-ex ip route add default via 192.168.100.1 dev br-ex验证ip a s br-ex应显示inet 192.168.100.10/24ip route show应有default via 192.168.100.1 dev br-ex。此时ping 192.168.100.1网关和ping 8.8.8.8都应通。如果 ping 不通网关检查物理交换机端口是否开启网线是否插对如果 ping 不通外网检查iptables -L FORWARD是否允许转发allinone 环境通常不需要 NAT所以FORWARD链默认 ACCEPT 即可。做完这三步br-ex就成了 OpenStack 的网络心脏。后续创建 provider 网络时--provider-physical-network physnet1中的physnet1就是answer.txt里定义的物理网络名它通过br-ex和ens192的绑定关系把虚拟机的网络流量直接导向物理网络。没有这三步的手动绑定Packstack 即使安装成功openstack server create启动的虚拟机也会永远卡在NETWORKING状态因为 Neutron DHCP agent 根本收不到 DHCP Discover 报文。3.4 Keystone 认证体系初始化绕过 5 个 Token 陷阱Keystone 是 OpenStack 的门禁系统所有服务通信都靠它签发的 Token。但它的初始化极易出错常见陷阱有五个陷阱一admin-openrc.sh文件里的OS_AUTH_URL必须带/v3后缀Packstack 生成的admin-openrc.sh默认是export OS_AUTH_URLhttp://192.168.100.10:5000这会导致openstack token issue报错The resource could not be found.。正确写法是export OS_AUTH_URLhttp://192.168.100.10:5000/v3。为什么因为 Keystone v3 API 是唯一支持 domain、project、user 多层级认证的版本v2 已废弃。这个/v3不是可选项是强制路径。陷阱二admin-openrc.sh必须 source 后才能生效新手常犯的错误是vi admin-openrc.sh修改完就直接openstack token issue结果报Missing value auth-url required for auth plugin password。正确流程是source admin-openrc.sh注意是source不是sh admin-openrc.sh然后env | grep OS_确认OS_AUTH_URL、OS_IDENTITY_API_VERSION等变量已加载。陷阱三OS_IDENTITY_API_VERSION3必须显式设置admin-openrc.sh里默认没有这行。必须手动添加export OS_IDENTITY_API_VERSION3。否则openstack客户端会尝试用 v2 协议通信而 Yoga 版本 Keystone 默认只启用 v3导致认证失败。陷阱四admin-openrc.sh里的OS_PASSWORD必须和answer.txt里CONFIG_KEYSTONE_ADMIN_PW一致这是最痛的坑。Packstack 安装时会读取answer.txt生成数据库密码但admin-openrc.sh里的密码是安装后生成的。如果你在answer.txt里改了CONFIG_KEYSTONE_ADMIN_PWpassw0rd但admin-openrc.sh里还是默认的随机密码openstack token issue就会返回The request you have made requires authentication.。解决方法安装完成后用mysql -u root -ppassw0rd -e use keystone; select * from user;查数据库确认password字段值然后同步到admin-openrc.sh。陷阱五openstack token issue成功后必须立即openstack project list验证Token 有效期只有 1 小时但openstack客户端会缓存 Token 到~/.cache/openstack/token。如果第一次token issue失败缓存里存了无效 Token后续所有命令都会失败。所以每次修改admin-openrc.sh后必须先rm ~/.cache/openstack/token再openstack token issue最后openstack project list看是否返回admin和service两个项目。只有project list成功才证明 Keystone 认证链打通了。绕过这五个陷阱Keystone 就不再是黑盒。你会发现openstack token issue返回的 JSON 里user.id对应数据库user表的主键project.id对应project表主键methods字段显示[password]说明当前认证方式是密码认证。这种“所见即所得”的透明度正是 allinone 部署不可替代的价值。3.5 Horizon 界面登录终极排查从 502 Bad Gateway 到 Welcome 页面的 7 分钟Horizon 是 OpenStack 的 Web 门户http://192.168.100.10/dashboard是你部署成功的最后一道门。但 90% 的人卡在502 Bad Gateway原因无非三类Apache 配置错误、Django 应用未启动、SELinux 阻断。我们按秒级顺序排查第 0-60 秒确认 Apache 服务状态systemctl status httpd如果显示inactive (dead)执行systemctl start httpd systemctl enable httpd。Yoga 版本 Horizon 依赖mod_wsgihttpd启动时会加载/etc/httpd/conf.d/15-horizon_vhost.conf这个文件由 Packstack 生成内容包含WSGIScriptAlias /dashboard /usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi。如果httpd没启自然 502。第 60-120 秒检查 WSGI 脚本路径和权限ls -l /usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi应显示-rw-r--r--. 1 root root ...。如果权限不对如root:apache执行chown root:root /usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi。更关键的是 SELinux 上下文ls -Z /usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi应显示system_u:object_r:httpd_exec_t:s0。如果不是执行semanage fcontext -a -t httpd_exec_t /usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi restorecon -v /usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi。第 120-180 秒验证 Django 配置文件编辑/etc/openstack-dashboard/local_settings确认以下三行OPENSTACK_HOST 192.168.100.10 ALLOWED_HOSTS [192.168.100.10, localhost] SECRET_KEY your-secret-key-here # 必须是非空字符串OPENSTACK_HOST必须是你机器的 IP不能是127.0.0.1ALLOWED_HOSTS必须包含你的访问 IP否则 Django 直接返回400 Bad RequestSECRET_KEY如果是空字符串Horizon 启动时会报KeyError: SECRET_KEY。第 180-240 秒检查 Memcached 状态Horizon 依赖 Memcached 缓存 sessionsystemctl status memcached必须是active (running)。如果 inactivesystemctl start memcached systemctl enable memcached。验证echo stats | nc 127.0.0.1 11211应返回STAT version 1.6.12等信息。第 240-300 秒查看 Apache 错误日志tail -f /var/log/httpd/error_log刷新 Horizon 页面观察日志。常见错误ImportError: No module named django.core.management说明python3-django包未安装执行dnf install python3-djangoOperationalError: (1045, Access denied for user horizonlocalhost)说明 Horizon 数据库用户密码错误检查/etc/openstack-dashboard/local_settings里的DATABASES配置PermissionError: [Errno 13] Permission denied: /var/cache/openstack-dashboard/SELinux 问题执行semanage fcontext -a -t httpd_cache_t /var/cache/openstack-dashboard(/.*)? restorecon -Rv /var/cache/openstack-dashboard。第 300-420 秒强制重建 Horizon 静态文件cd /usr/share/openstack-dashboard python3 manage.py collectstatic --noinput python3 manage.py compress --forceYoga 版本 Horizon 使用django-compressor压缩 CSS/JS如果静态文件未生成页面会显示空白或样式错乱。collectstatic把所有前端资源复制到/var/www/static/compress生成压缩后的CACHE/目录。第 420-480 秒重启 Apache 并测试systemctl restart httpd curl -I http://192.168.100.10/dashboard响应头应包含HTTP/1.1 200 OK和Content-Type: text/html; charsetutf-8。此时用浏览器访问http://192.168.100.10/dashboard输入admin/passw0rd应该看到 Welcome to OpenStack Dashboard 页面。这 7 分钟的排查流程我写了 32 页内部培训手册因为它是所有 OpenStack 教学中最容易被忽略的“最后一公里”。Horizon 不是简单的 Web 页面它是 Django、Apache、Memcached、SELinux 四层防御体系的交汇点。只有亲手走过这 7 分钟你才真正理解什么叫“云平台的可观测性”。4. 实操过程与核心环节实现从零开始的 23 分钟完整部署流水线4.1 环境初始化12 条命令构建纯净基座耗时 3 分钟我们从一台刚装好的 CentOS Stream 9 系统开始确保所有操作在 root 用户下执行。以下是精确到秒的初始化命令流每条都附带原理说明dnf update -y reboot更新系统内核和基础包重启确保新内核生效。Stream 9 的初始 ISO 常含旧版 kernelreboot后uname -r应显示5.14.0-xxx.el9这是 OpenStack Yoga 的最低要求。dnf install -y centos-release-openstack-yoga启用 OpenStack Yoga 仓库。注意不是centos-release-openstack-train或wallabyYoga 是当前 LTS 版本兼容性最好。此命令会创建/etc/yum.repos.d/CentOS-OpenStack-yoga.repo。dnf config-manager --set-enabled powertools启用 PowerTools 仓库。OpenStack 依赖的python3-pip、gcc、libffi-devel等编译工具都在此仓库不启用则dnf install python3-pip会报No match for argument。dnf install -y python3-pip gcc libffi-devel openssl-devel python3-devel安装 Python 开发环境。libffi-devel是cryptography包的编译依赖缺少它会导致pip3 install python-openstackclient失败报fatal error: ffi.h: No such file or directory。pip3 install -U pip setuptools wheel升级 pip 工具链。Stream 9 自带 pip 21.x但 OpenStack Yoga 需要 pip 23否则pip3 install python-openstackclient会因依赖冲突失败。dnf install -y openstack-packstack安装 Packstack 工具。它本质是 Python 脚本位于/usr/bin/packstack依赖puppet和hiera但dnf install会自动解决所有依赖。packstack --gen-answer-fileanswer.txt生成初始应答文件。此命令不联网纯本地生成耗时约 8 秒。生成的