搞离线部署 OpenStack 的人大概都有同感:最难的不是 OpenStack 本身,而是把整套依赖在一个没有互联网的环境里闭环转起来。我最近刚完成一套基于 CentOS Stream 9 的 OpenStack 2024.1 Caracal 高可用集群,用的是离线分层部署的思路,从操作系统源、容器镜像源、到 Kolla-Ansible 编排,一层一层向下铺。这套方案不一定是最快的,但一定是最稳的,尤其适合机房网络隔离、不能访问外网、又必须上生产集群的场景。这篇文章把整个过程按层拆开讲,包含我实际踩过的坑和最终保留的运维动作,给准备在同类型环境里做 OpenStack 的人一个可以照着走的参考。
1. 为什么离线部署要划分层次,而不是直接一键装
1.1 离线环境的真实难点
我见过不少人拿到 OpenStack 部署任务后,第一反应是找一台能联网的机器装个 kolla-ansible,然后把kolla-ansible deploy一行命令写完就算完事。这个思路在能上网的环境里没问题,但在离线内网里会碰上一连串连锁反应:系统基础包装不全、容器镜像拉不到、pip 包没地方下载,甚至连时间同步都做不了,最终常常卡在一些莫名其妙的基础环境错误上。
真正的离线部署难点,不只是“没有网络”这一个事实,而是整个部署过程被切成了多个独立闭环:
- 操作系统层需要本地 Yum 源,否则节点初始化都做不完;
- 容器运行时层需要本地 Docker 源,否则 bootstrap-servers 根本装不上 docker;
- OpenStack 容器镜像需要本地 Registry,否则所有服务镜像拉取失败;
- Kolla-Ansible 和 Ansible 的 Python 依赖需要离线包,否则控制节点连编排工具都起不来。
这些问题互相纠缠,如果你一次性全部铺开处理,任何一个环节出错,排查范围都会变得非常大。所以我在这次部署里采用了分层推进的方式,每一层独立验证通过之后,再进下一层。这样出了问题,问题基本能定界在某一层里,不会出现“明明镜像没问题,却在找 Yum 源”这种离谱的排查方向。
1.2 我理解的“分层”其实是三层含义
“离线分层部署”这个说法,我在这次项目里把它拆成了三个层次:
第一层是软件栈分层。物理机装完 CentOS Stream 9 后,往上依次是内核和驱动、容器运行时、Kolla-Ansible 编排层、再到 OpenStack 各服务容器,每一层都有明确的依赖关系,不能跳着装。
第二层是控制面与数据面分层。控制节点承担 API、调度、数据库、消息队列;计算节点只承担虚拟化计算和网络数据面。高可用集群如果控制面和数据面混在一起,后续维护会非常痛苦,节点发生抖动时影响面也会被放大。
第三层才是部署动作分层。也就是先把离线基础设施全部准备到位,再启动 OpenStack 服务的编排,最后按服务依赖关系逐个补齐。这一层更多是项目管理上的“阶段划分”,对离线项目来说尤其重要,因为离线项目的返工成本远高于在线项目,一旦中间发现底层源有问题,前面所有部署动作都要推倒重来。
把这三层含义理清楚,后面每一步怎么做就比较自然了。
1.3 为什么选择 OpenStack 2024.1 Caracal
OpenStack 2024.1 的版本代号是 Caracal,属于 2024 年上半年发布的版本。对这个版本我的整体评价是:功能上比之前稳定,配置上比之前严苛。
Caracal 比较大的变化是安全策略收紧,服务间默认启用 secure RBAC,对权限模型要求更规范。这对生产集群是好事,但如果你手上的客户端、SDK 版本太老,或者镜像里自带的配置不是新版本的默认值,很容易在创建用户、分配角色这些基础操作里遇到奇怪的 403 权限报错。离线部署时这个问题会被放大,因为镜像一旦锁定,改权限模型的成本比在线环境高得多。
另外,Caracal 在 CentOS Stream 9 上走 Kolla-Ansible 部署时,基础镜像一般选 Rocky Linux 9 系列,两者同属 RHEL 系,兼容风险最小。这一点在准备容器镜像时就要想清楚,后面第 3 部分会细讲。
2. 动手前的环境盘点:硬件、网络平面和系统底线
2.1 控制节点与计算节点的硬件基线
离线部署的一个好处是,通常在规划阶段就能拿到明确的硬件清单,不用像云上环境那样弹性伸缩。但这也意味着硬件规划一旦失误,后面很难临时扩容。我这次的高可用集群采用 3 控制节点 + N 计算节点的结构,控制节点规格如下:CPU 不低于 32 线程,内存 64 GB 起步,系统盘 RAID1 做冗余,数据盘独立挂载,建议至少 500 GB 空间,因为 Glance 镜像、Cinder 后端缓存、日志都要落盘。
计算节点则根据业务虚拟机密度来定,我这边单台计算节点是 48 线程、256 GB 内存,系统盘 RAID1,另有 2 TB 本地数据盘用于 Nova 临时存储。如果你的环境里计划用 Ceph 或独立存储阵列,计算节点本地盘要求可以低一些,但网卡要求不能低。
控制节点的内存是个很容易被低估的点。很多人觉得 64 GB 已经很多了,但 Kolla-Ansible 会把 MariaDB、RabbitMQ、HAProxy、Keystone、Nova 控制器、Neutron Server、Glance 等将近二十个容器压在三个控制节点上。每个容器看似只占几百 MB,叠加起来加上页缓存,内存消耗非常可观。如果还要部署 Octavia、Manila 等扩展组件,控制节点内存建议直接提到 128 GB。
2.2 网络平面怎么划
OpenStack 高可用集群最少需要四个网络平面,我在这次项目里是严格分开的:
| 网络平面 | 网段示例 | VLAN | 主要用途 |
|---|---|---|---|
| 管理网 | 192.168.10.0/24 | 100 | 控制节点内部通信、API 访问、Kolla 容器间通信 |
| 业务网 | 192.168.20.0/24 | 200 | 虚拟机业务流量、Neutron 内部网络 |
| 外部网 | 192.168.30.0/24 | 300 | 浮动 IP、外部访问 |
| 存储网 | 192.168.40.0/24 | 400 | Glance 镜像上传、Cinder/存储后端流量 |
管理网是集群的命脉。VIP、MariaDB 同步、RabbitMQ 集群通信、Kolla-Ansible 的 SSH 管理都走这个网络,建议至少万兆,并且做好网卡 bond。存储网很多人会忽略,但如果后面接独立存储或者 Ceph,存储流量和数据面流量混在一起,网络拥塞时首先要怀疑的就是这里。业务网和外部网要分开,不然浮动 IP 和内部业务网段冲突时,路由规则会调到怀疑人生。
网卡数量方面,每台控制节点至少 4 个物理网口,两个做管理网 bond,两个做业务/外部网;计算节点建议 4 到 6 个口,业务网最好也做 bond,避免单点链路故障导致虚拟机网络中断。
2.3 CentOS Stream 9 初始化容易忽略的几个点
系统装完后,初始化阶段有几个让我吃过亏的细节。
第一个是 SELinux。Kolla-Ansible 官方文档写得比较保守,但实际部署中 SELinux 开启状态下,容器挂载卷和网络命名空间经常会触发权限拦截,报错还很隐晦。我这边直接设置为 Permissive,生产环境如果有合规要求,至少也要保持 Permissive,不要强行 Enforcing,否则排查容器权限问题会耗掉大量时间。
第二个是防火墙。Kolla 自己的 HAProxy 和 Keepalived 会管理大量端口,如果节点上的 firewalld 规则没放行,VIP 看起来是通的,实际访问 API 就是超时。离线内网环境如果物理隔离足够安全,我建议先禁用 firewalld,靠交换机 ACL 做访问控制;如果必须开启,至少放行管理网段的全部端口和业务网的关键端口段。
第三个是网卡固件和驱动。CentOS Stream 9 内核比较新,但新不一定等于兼容性好。有些万兆网卡需要额外的 linux-firmware 包,不然链路协商不稳定,会出现“通一通断一断”的现象。这个在系统装好后必须用ethtool -i和ethtool eth1检查驱动版本和链路速率,不要等到部署 OpenStack 之后再去排查物理层问题。
第四个是虚拟化支持。计算节点上要确认/dev/kvm存在,lsmod | grep kvm能看到 kvm_intel 或 kvm_amd 模块。如果连嵌套虚拟化都要支持,还得确认 BIOS 里 VT-x/AMD-V 已经打开。
3. 离线源层:Yum 仓库、容器镜像仓库、时钟与 DNS
3.1 用 dnf reposync 把 CentOS Stream 9 Yum 源搬进内网
离线环境的第一步是搭好 Yum 源。CentOS Stream 9 的核心仓库主要是 BaseOS、AppStream 和 CRB,另外部署 Docker 还需要 docker-ce-stable 源,部分组件可能用到 EPEL。我的做法是找一台与内网同 CPU 架构、且能临时联网的机器,把仓库全部拉下来,再通过移动介质拷进内网,放到一台源服务器上。
拉取命令大致如下:
dnf reposync --repoid=baseos --repoid=appstream --repoid=crb -p /data/repo --download-metadata dnf reposync --repoid=epel -p /data/repo dnf reposync --repoid=docker-ce-stable -p /data/reporeposync 会同步 RPM 包和仓库元数据,但为了保险,我建议每个仓库目录都再跑一次 createrepo_c,避免某些仓库的元数据文件不完整导致客户端 makecache 失败:
for d in baseos appstream crb epel docker-ce-stable; do createrepo_c /data/repo/$d done然后在内网源服务器上把这些目录通过 Nginx 暴露出来,配置里打开 autoindex。客户端节点上写好本地 repo 文件:
[local-baseos] name=Local BaseOS baseurl=http://192.168.10.10/repo/baseos/ enabled=1 gpgcheck=0 [local-appstream] name=Local AppStream baseurl=http://192.168.10.10/repo/appstream/ enabled=1 gpgcheck=0每台节点执行dnf clean all && dnf makecache验证仓库可用。
关于 gpgcheck 有一个取舍。完全离线内网里,RPM 包在联网同步阶段已经被原始仓库的签名校验过了,进入内网后再开 gpgcheck 意义不大,反而要额外导入一堆 GPG Key,增加出错的概率。我这边是 gpgcheck=0,但前提是源服务器物理可控,且拷入内网的过程有严格校验。
3.2 容器镜像:从打包到内网 Registry
Yum 源只是第一步,OpenStack 本身是以容器方式运行的,所以容器镜像仓库才是核心。Kolla-Ansible 部署时,每个控制节点上会拉起二十多个容器,计算节点上也有十几个,这些镜像缺一不可。
镜像来源有两个选择:一是直接用官方构建好的镜像,从 Quay 等镜像仓库拉取;二是在联网机器上用 kolla-build 自己构建。对离线场景,我推荐前者,镜像版本和 OpenStack 版本的对应关系已经被官方验证过,省去很多兼容性排查。
拉取完成后,把镜像导成 tar 包:
docker pull quay.io/openstack.kolla/centos-source-keystone:2024.1 docker save quay.io/openstack.kolla/centos-source-keystone:2024.1 -o keystone.tar更频繁的做法是用 skopeo 直接复制,skopeo 不需要本地 Docker 守护进程,导出导入速度更快。不管用哪种方式,最终目的都是把镜像包送进内网,然后 push 到内网 Registry。我这边内网 Registry 用的是 Harbor,部署在内网一台单独的机器上,承担所有 OpenStack 镜像的存储和分发。
Kolla-Ansible 里要指定:
docker_registry: 192.168.10.10指向内网 Harbor;docker_registry_insecure: "yes"允许 HTTP 协议拉取;openstack_release: "2024.1",确保拉取的镜像 tag 是 2024.1 而不是默认的其他版本。
这里要特别强调:镜像 tag 和 Kolla-Ansible 版本必须严格对应。Caracal 对应的是 Kolla-Ansible 的 17.x 系列,如果用旧版 Kolla-Ansible 去拉新版镜像,部署时可能连容器启动参数都对不上。
3.3 Kolla-Ansible 的 Python 离线依赖
Yum 源和容器镜像都解决了,还有一个容易被忽视的依赖来源:Kolla-Ansible 本身的 Python 包。控制节点上要安装 ansible-core、kolla-ansible 以及相关依赖,这些包通常来自 PyPI。离线环境下需要在联网机器上预先把 wheel 包下载好:
pip download kolla-ansible==17.0.0 ansible-core -d /data/wheels进入内网后,用本地目录安装:
pip install --no-index --find-links=/data/wheels kolla-ansible==17.0.0 ansible-core这里有个血泪教训:有人在离线节点上直接用yum install ansible,结果装的是系统自带的旧版 ansible-core,和 Kolla-Ansible 要求的版本差了一大截,部署时各种模块找不到。记住,Kolla-Ansible 的 Python 依赖一定走 pip wheelhouse,不要混用系统 RPM 包。
3.4 时间同步和 DNS 在离线环境的特殊处理
时间同步在离线环境里比在线环境更容易被忽略,因为大家默认“集群里各节点开机时间差不多,应该不会差太多”。但 OpenStack 对时间非常敏感,Keystone 的 Fernet token、Nova 的迁移任务、Cinder 的卷状态同步都依赖节点间时钟一致性,差几秒可能问题不大,差几十秒就会出现各种随机故障。
离线环境没有外网 NTP 可用,我的做法是在管理网里指定一台控制节点做内部 Chrony 服务器,配置文件大致如下:
# 服务端 server 192.168.10.10 iburst allow 192.168.10.0/24 local stratum 10其他节点指向这台服务器,并使用chronyc sources -v验证同步状态。
DNS 同样重要。所有节点的主机名解析必须保持一致,建议内网 DNS 直接解析控制节点主机名和管理网 VIP。Kolla-Ansible 在 HAProxy 后端配置里会使用主机名,如果各节点 hosts 文件不一致,会出现后端服务器时通时不通的现象。
4. Kolla-Ansible 分层推进的实操序列
4.1 先初始化所有节点的 Docker 运行时
离线源层就绪后,才正式进入 Kolla-Ansible 的部署阶段。第一步不是 deploy,而是先把所有节点的容器运行时装好。Kolla-Ansible 的bootstrap-servers命令会帮助完成 Docker 安装、配置 daemon、拉起基础容器等动作。
在控制节点上先把 multinode 模板复制出来:
cp /usr/share/kolla-ansible/ansible/inventory/multinode ./multinode修改 inventory 文件,把三个控制节点、计算节点分别填入。控制机到所有节点要配好 SSH 免密登录,否则 Ansible 连不上。
然后执行:
kolla-ansible -i multinode bootstrap-servers这一步依赖前面准备的 docker-ce Yum 源,如果 docker 源没配好,这里会直接报错。所以说分层部署的“层”不是理论概念,每层都支撑着下一层的实际动作。
4.2 globals.yml 和 passwords.yml 的准备
bootstrap 完成后,修改 Kolla 的全局配置。Kolla-Ansible 的配置模板一般在/etc/kolla/globals.yml和/etc/kolla/passwords.yml,如果没有就自己创建。
globals.yml 里我这次用到的关键项:
kolla_base_distro: "rocky" kolla_install_type: "source" openstack_release: "2024.1" kolla_internal_vip_address: "192.168.10.250" network_interface: "eth1" neutron_external_interface: "eth3" docker_registry: "192.168.10.10" docker_registry_insecure: "yes" enable_haproxy: "yes" enable_keepalived: "yes" enable_mariadb: "yes" enable_rabbitmq: "yes"其中kolla_base_distro用 rocky 是因为 Kolla 对 Rocky Linux 9 的支持最成熟,而 CentOS Stream 9 与 Rocky 9 底层兼容很好,这样基础镜像的选择面更广。kolla_install_type用 source 表示从源码构建的镜像,功能更全,排错时报错信息也更明确。kolla_internal_vip_address是高可用集群的虚拟 IP,必须和管理网在同一网段。
密码文件直接用工具生成:
kolla-genpwd生成之后不要再去手工改密码,除非你确实知道自己在改什么。RabbitMQ、MariaDB 的密码如果改得不一致,集群根本起不来。
4.3 分段部署与整体收敛的组合策略
首次部署我最推荐的方式是:先跑一次完整预检,再按需分段定位,最后用一次完整 deploy 收敛。
先预检:
kolla-ansible -i multinode prechecks预检会检查磁盘空间、内存、网络连通性、Docker 状态等基础条件。但预检通过不代表部署一定成功,它只是把最明显的资源问题挡在门外。
预检之后,我通常会先只部署基础设施服务:
kolla-ansible -i multinode deploy --tags mariadb,rabbitmq,memcached这里用--tags做局部部署,不是官方推荐的首次部署方式,但非常适合离线环境下的问题定位。如果 MariaDB 容器起不来,你只需要看数据库这一层的日志,不需要被 Nova、Neutron 同时报错把脑子搞乱。
基础服务验证正常后,再跑完整部署:
kolla-ansible -i multinode deploy完整部署会把所有服务全部拉起,并自动补上之前按 tag 部署时没有覆盖的部分。Kolla-Ansible 的 playbook 本身是幂等的,重复执行不会有副作用。所以我把它当作“分层定位 + 整体收敛”的组合拳,既有分层的排查效率,又有整体部署的完整性。
最后生成 admin openrc:
kolla-ansible -i multinode post-deploy执行后,控制节点上会生成/etc/kolla/admin-openrc.sh,后面所有 OpenStack CLI 操作都要先 source 这个文件。
4.4 计算节点扩容的独立操作
集群跑起来之后,扩容计算节点是常见需求。这一步相对来说更独立,但也需要按层来。
新计算节点先要完成系统初始化、加入 Yum 源、容器镜像预先 warm up,然后把它加入 multinode inventory 的计算节点组。回到控制机执行:
kolla-ansible -i multinode bootstrap-servers --limit new-compute kolla-ansible -i multinode deploy --limit new-compute--limit可以限定 Ansible 只在目标节点上执行,避免整个集群被重新触发一遍。部署完成后,在控制节点上确认 Nova 计算服务已经注册:
openstack compute service list如果新节点状态是 down,先检查该节点上的 nova-compute 容器是否正常,再看管理网到控制节点的连通性。
5. 高可用集群运行中的踩坑记录
5.1 MariaDB Galera 集群分裂的一次恢复
高可用集群里,MariaDB 的 Galera 多主复制是事故高发区。我遇到过机柜断电后,三个控制节点的 mariadb 容器没能同时恢复,两个节点的wsrep_cluster_size显示只有自己一个节点,彼此之间无法同步。
排查时先看每个节点上的集群状态:
SHOW STATUS LIKE 'wsrep_cluster_size'; SHOW STATUS LIKE 'wsrep_local_state_comment';关键思路是找到数据最完整、seqno最大的节点作为引导节点,让它单独启动成一个新集群,再逐个拉起其他节点加入。在 Kolla 环境里,推荐优先使用官方自带的恢复工具:
kolla-ansible -i multinode mariadb_recovery如果恢复工具失败,再手工介入。我个人的建议是:控制节点重启要滚动进行,不要三个节点同时重启。Galera 对同时重启非常敏感,一旦所有节点丢失了彼此的状态,恢复起来相当麻烦。
5.2 RabbitMQ 在内存紧张时的节点分区
还有一次 RabbitMQ 集群出现了网络分区提示,控制节点上rabbitmqctl cluster_status显示节点状态异常。查下来发现根因不是网络,而是某个控制节点内存不足,导致 RabbitMQ 频繁触发内存高水位保护,暂停接收消息,其他节点认为它失联了。
RabbitMQ 的内存高水位默认是总内存的 0.4,在控制节点内存本就不宽裕的情况下,这个阈值很容易触发。我调整了 RabbitMQ 配置里的vm_memory_high_watermark,适当提高了可用内存比例,同时清理了控制节点上不必要的容器和服务,把内存压力降下来。
这个案例反映了一个问题:高可用不等于可以低配。三个控制节点看起来是冗余,但如果每个节点资源都卡在临界值,任何一个节点抖动都会波及整个集群。
5.3 VIP “看起来在,实际访问不了”
HAProxy + Keepalived 是高可用入口,问题表象通常是:VIP 明明显示在其中一台控制节点上,但访问 Horizon 或 Keystone API 就是超时。
我的排查顺序是:
ip addr show dev eth1先确认 VIP 漂移到了哪个节点;docker exec -it haproxy haproxy -c -f /etc/haproxy/haproxy.cfg验证 HAProxy 配置语法;docker logs keepalived看健康检查日志;- 确认当前节点防火墙是否放行了 VIP 端口。
碰到过一次是 network_interface 配置错了,Keepalived 把 VIP 绑定到了错误的网卡上,导致 VIP 虽然在节点上,但网络路径根本不通。这种问题从日志上看不出来,只能逐个检查网络层配置。
5.4 时间误差引发的花式故障
时间不同步是我遇到过的最隐蔽的问题。现象包括:Keystone token 刚创建就过期、Nova 冷迁移超时、镜像上传后状态一直 pending。这些故障看起来毫无关联,最后定位到管理网节点的时钟偏了快 40 秒。
解决方式不复杂,所有节点统一指向内网 Chrony 服务器,然后强制校准:
chronyc makestep但关键不是这一次校准,而是要把时间同步作为集群巡检的固定项。离线环境没有外部时钟源,内部时钟服务器也可能缓慢漂移,必须定期和硬件 RTC 或可信时钟源对比。
6. 部署验收、预演和后续维护
6.1 部署后的健康检查清单
集群部署完成后,不要急着往里面灌业务,先做一轮基础验收。我常用的命令有:
source /etc/kolla/admin-openrc.sh openstack service list openstack endpoint list openstack compute service list openstack network agent list openstack volume service list这几个命令能快速暴露控制面服务是否全部正常注册。接着我会上传一个测试镜像,创建测试网络和一台测试虚拟机,完整走一遍“镜像上传—网络创建—虚拟机创建—绑定浮动 IP—SSH 登录”的流程。
测试镜像建议提前就准备好,比如 Cirros 这种小镜像,离线环境下也要放在内网 HTTP 服务器上,方便随时下载。
高可用验收更重要。我会在业务低峰期手动重启一个控制节点,观察 VIP 是否漂移、服务是否中断,再恢复该节点。这种演练第一次做的时候心里会慌,但做过一次之后,对整个集群的信任度会明显提升。
6.2 用 QEMU 嵌套虚拟化做离线预演
如果硬件还没到位,或者不想在真实物理机上一遍遍试错,可以用 QEMU/KVM 做小规模预演。宿主机开启嵌套虚拟化后,虚拟机里再启动 OpenStack 计算节点是完全可行的。
我这里用的是三层结构:物理宿主机原生 KVM,中间虚拟机充当 OpenStack 控制节点和计算节点,计算节点里的虚拟机再启动业务虚机。这个环境非常适合验证离线源的一致性、镜像版本匹配这类问题。
但要注意,QEMU 嵌套虚拟化的性能折扣很大,而且无法模拟真实网卡的 SR-IOV、DPDK 这类硬件特性。预演只能验证软件栈逻辑,不能替代物理机上的性能测试和驱动兼容性测试。如果你想验证多架构支持,比如后续要跑 ARM64 虚拟机,也可以在这个环境里提前做功能验证。
6.3 后续维护里最值得养成的几个习惯
集群上线后,维护阶段的“分层思维”依然有用,只是层级变成了日常运维动作分层。
第一层是定期巡检,重点看时钟同步、磁盘空间、容器健康状态。Kolla 容器如果异常退出,第一时间docker logs看日志,不要盲目重启。
第二层是备份策略,/etc/kolla下的 globals.yml、passwords.yml 是核心资产,必须异地备份。控制节点的容器数据目录也要定期备份,尤其是 MariaDB 的数据目录。离线环境没有在线恢复的捷径,备份就是最后的防线。
第三层是升级策略。离线环境升级 OpenStack 前,先把新版本的容器镜像全部拉到内网 Registry,再执行kolla-ansible upgrade。不要直接拿在线环境那套“边下边升”的思路来套离线环境,镜像没就位就升级,大概率升到一半卡死。
我自己的体会是,离线高可用集群最怕的不是技术难度,而是“想当然”。Yum 源版本不匹配、容器镜像 tag 不一致、时间不同步,这些坑都是因为想当然地认为“应该没问题”才踩进去的。分层部署的核心价值,就是逼着你在每一层都验证一次“确实没问题”,层与层之间留好检查点,后面整个集群才敢放心交付。