ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

OpenStack on Kubernetes生产部署实战:网络存储与高可用关键细节

2026/10/7 12:14:38 拓冰建站 浏览量
OpenStack on Kubernetes生产部署实战:网络存储与高可用关键细节 这个系列写到第三篇前两篇聊了为什么要把 OpenStack 从裸机搬到 Kubernetes 上以及基础设施层的前期准备。这篇我不想再重复架构图和大道理直接聊生产部署时真正会卡住你的那些细节。说句实在话OpenStack on Kubernetes 在生产环境里能用但不是把所有组件丢进 Pod 就完事。控制平面容器化带来的收益是真实的但执行下去你会发现真正决定成败的往往不是 OpenStack 本身而是网络、存储、运维模型这几个老问题只是换了个新容器重新出现了一次。这篇实战总结主要面向两类人一类是已经有 Kubernetes 基础、正准备把 OpenStack 云平台搭建在 K8s 上的同学另一类是已经做了技术预研、但被各种奇奇怪怪的问题卡在测试环境里的运维工程师。内容围绕生产部署全流程展开包括方案选型、网络存储设计、高可用布局、实操命令和踩坑记录看完可以直接照着调整自己的部署方案。1. 为什么要把 OpenStack 搬上 Kubernetes1.1 控制平面容器化的现实收益OpenStack 的控制平面组件非常多Keystone、Glance、Nova、Neutron、Cinder、Heat 加上各类附属服务满打满算二十几个进程。传统部署方式基于 Kolla-Ansible一套下来能跑通但升级是真痛苦。言下之意是Ansible 把一堆容器塞到各个控制节点上每个节点都有好几个容器升级的时候要么全量替换要么手动指定顺序出一次问题就要花大量时间排错。把控制平面放进 Kubernetes本质上是把“谁装在哪个节点、谁先起谁后起、挂了怎么拉起”这部分工作交给调度器和控制器来完成。这带来的收益非常直观组件升级变成了标准的工作负载滚动更新机器故障时 Pod 会被自动重新调度而 Pod 的重启策略和健康检查让各种状态不正常的问题无处遁形。我举一个例子在传统部署里 Keystone 某个进程内存泄漏可能到用户大量接入时才发现。代码里但容器化之后只要配置好 livenessProbe泄漏导致响应变慢kubelet 就会主动把它杀掉重建业务抖动基本控制在一个探针周期内。打个生活化的比方传统部署像是电工在一间老房子里拉了一堆明线哪根线对应哪个房间全靠标签改一条线要担心影响别的线路。容器化之后每个设备都是一个标准插头统一插在配电箱Kubernetes里哪个设备有问题就拔掉哪个新设备直接插上预制接口就能用。1.2 不是所有组件都适合容器化但我要先泼一盆冷水不是所有 OpenStack 组件都适合无脑容器化。Nova-Compute 和 Neutron-Agent 这类组件本质上需要直接操作宿主机内核能力、设备文件和 socket 文件容器化之后它们仍然是 Process 级别的工作负载而不是标准 Pod。实际部署中nova-compute 必须要以特权模式运行并且挂载宿主机的 /var/lib/libvirt、/var/lib/nova、/run 等路径。Neutron 的 Open vSwitch Agent 更麻烦它要操作 OVSDB 和 datapath单纯塞进 Pod 里网络流量出现问题的时候你根本不知道是容器隔离导致的还是云主机本身的问题。所以正确的姿势是控制平面和分布式服务用 Kubernetes 做编排而 Compute 和 Agent 节点仍然保留比较特殊的调度方式。做生产规划时建议把节点明确分成几类控制平面节点运行 K8s 和 OpenStack 控制服务计算节点跑 nova-compute 和 Neoutron-agent网络节点如果没被 OVN 收敛掉则跑专用服务。这样的分工在后续故障排查时能省很多时间。2. 部署方案选型Operator 还是 Helm2.1 主流方案的权衡“用 K8s 部署 OpenStack”说起来简单真做起来选型就会让你纠结一阵子。目前主流路线大致有三条OpenStack-Helm、MOSK基于 OpenStack Operator、以及部分大厂自研的部署平台。OpenStack-Helm 是社区里历史最久的方案把 OpenStack 组件都打包成 Helm Charts用 values 文件控制开关和参数。它的优点是可定制程度高、社区资料多但坑也比较明显默认 values 文件非常长且很多组件之间的参数需要手动对齐比如 Keystone 地址、RabbitMQ user 密码这些需要到处引用。MOSK 是从 Mirantis 那套产品线走过来的整体性更好在 OpenStack 服务编排、节点管理、升级流程上比较自动化。商业味浓也不太好二次开发。如果你的团队有商业支持预算MOSK 是可以考虑的。如果你的团队想要可控、可审计、可快速追踪问题的方案我还是建议基于 OpenStack-Helm 做定制这也是我个人实际走的路。另外必须提一句很多新手拿 packstack 装完 OpenStack 觉得很顺利但那是在单机上把服务当作普通进程跑一遍还算不上生产部署。packstack 的价值仅在于体验和开发调试千万别把它当作生产部署基准。生产环境从第一天起就要把控制平面视为分布式系统来设计。2.2 我最终选择的组合我最终采用的是 OpenStack-Helm 加自己维护的 values override 组合。选它的核心理由是Charts 的渲染过程可以被审计真要出问题可以一层层解开看到底是什么配置不对。商业方案虽然省事但黑盒式子系统一旦出现和自家环境不兼容的情况排起来非常绝望。在部署前我建议先做一次标准而简单的 K8s 原生应用 nginx 部署用最小成本验证集群网络、存储和监控链路是否打通。很多人直接上来就跑 OpenStack结果简单应用都跑不通排了半天发现是 K8s 底层的问题那就比较浪费时间了。以下是一个最小化验证的方式apiVersion: apps/v1 kind: Deployment metadata: name: nginx-test namespace: openstack spec: replicas: 3 selector: matchLabels: app: nginx-test template: metadata: labels: app: nginx-test spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80等这个 Deployment 的三个副本都能 Running 之后再执行 OpenStack-Helm 的初始化脚本。这个验证步骤花不了十分钟却能把后续很多变量一开始就先排除掉。3. 生产环境网络与存储的硬核细节3.1 网络方案OVN 还是 Neutron 集群OpenStack 的网络历来是最大坑点容器化之后也没有变简单。传统 Neutron 里用了大量分布在节点上的 Neutron-OpenvSwitch-Agent 和 L3-Agent每个节点都要处理流表和各种 namespace。把这一堆 Agent 迁移到 Kubernetes 里管理复杂度会直线上升。后来社区推动 OVN 方案把控制面的逻辑收敛到 OVN NB/SB 数据库剩下一个 ovn-controller 常驻在计算节点上逻辑上清爽不少。选型时如果集群规模超过几十台物理机或者未来会有频繁的租户网络变更我建议优先考虑 OVN。它在东西向流量、分布式网关和 ACL 管理这些传统难点上表现确实更省心。但也要清楚OVN 的 6301 和 6642 端口会承载较多数据库同步流量不要把 OVN 的 NB/SB 数据库和 OpenStack 业务混在同一个 Pod 组里计入故障域。比 OVN 本身更值得注意的问题是 Neutron 的网络命名空间和 Kubernetes 的 CNI 叠加时可能产生的冲突。如果没有做物理隔离两个系统会在同一批主机上争抢路由表、iptables 规则和接口名称。这种冲突有一个很典型的表现: 某个节点重启之后OpenStack 路由的云主机通了但 K8s 的 Pod 网络不通或者反过来。生产环境应该从物理上把 OpenStack 管理网、租户网络和 Kubernetes 集群网分离让它们各自走独立的网卡和交换网络尽量避免在同一个接口上做多子网复用。3.2 存储Ceph 与本地盘的精打细算存储上生产级 OpenStack 通常绕不开 Ceph。Ceph 给 Glance 提供镜像存储给 Cinder 提供块设备后端给 Nova 提供临时磁盘后端一套系统吃三份需求确实合理。但 Ceph 本身也有一套很重的资源要求部署 Ceph 集群的节点不建议和 OpenStack 控制平面强绑定最好是将 Ceph 视为外部服务通过集群外部网络提供给 OpenStack 的 Pod 使用。如果你在 K8s 里直接部署 Ceph 的 PVC建议提前想好容量隔离方案。OpenStack 用 Ceph 的 RBD 和 CephFS 时一定要给不同的池配置独立的 RADOS 集群和容量限制避免某个项目把存储池写满然后影响所有云主机。比较推荐的做法是为 Cinder、Glance、Nova 分别建池每个池独立设置 pg_num 和 QoS 限速这样即使某个池出现性能瓶颈故障域也不会扩大。另外计算节点本地盘的价值常常被低估。云主机的系统盘虽然以网络存储为主但大文件的高 IO 场景如果全部打给 Ceph成本非常高。实际生产中可以在计算节点使用本地的 NVMe 盘做小型临时镜像缓存把高频读取的镜像缓存放到本地能显著降低 Ceph 集群压力并提升启动速度。说穿了就是把 Ceph 用在持久层把本地盘用在缓存和临时层各管一段。4. 高可用与故障域设计4.1 控制平面的故障域划分OpenStack 控制平面放进 K8s 之后高可用设计从“多节点手工排除”变成了“调度策略设计”。K8s 本身会用 ReplicaSet 拉起多副本但如果你不干预 Pod 的分布可能所有副本落在同一个节点一旦节点宕机整个服务变得不可用。所以给控制平面组件配置 PodAntiAffinity 是必须做的事情尤其对 Keystone、Horizon、Nova-API、Neutron-Server 这类服务。这里实际上是在用 K8s 的调度器解决服务的高可用分布问题。把两个同规格副本的 Keystone 强制分配到不同节点同时也可以利用 topologySpreadConstraints 让副本均匀分布在不同的故障域比如机架、热区。有条件的物理环境最好至少 3 个控制节点分别分布在 3 个不同机柜Kubernetes 节点打上不同的 failure-domain 标签然后让 OpenStack 核心组件的 topology key 指向这个标签。4.2 etcd 和数据库的高可用OpenStack 大量组件依赖数据库MariaDB 或 Galera 的表现直接决定平台稳定性。K8s 里的 MariaDB 容器化有多种姿势但我个人经验是生产环境不要把 MariaDB 和 OpenStack 业务塞进同一个集群除非你有一个很成熟的数据库运维团队。比较安全的方案是用独立的 3 节点 Galera 集群跑在 K8s 集群之外或者使用外部托管的数据库实例。这样即使整个 K8s 集群出事故数据库仍然不被影响。OpenStack 里各类服务的 connection 配置都要指向这个外部数据库并且在数据库连接池参数上做调整防止 Nova 或 Neutron 在并发高峰时把连接数彻底打满。etcd 的部署也值得单独提一下。OpenStack-Helm 默认可以用 Helm 在集群内部部署带 TLS 的 etcd 集群但如果生产环境规模较大建议单独维护 etcd 集群并且关注几个核心参数heartbeat-interval、election-timeout、snapshot-count针对不同磁盘性能做调整。默认参数在低速磁盘上很容易出现频繁 leader 切换然后导致整个集群的元数据操作突然变慢。有过一次经验就是 etcd 和 MariaDB 跑在同一组相对陈旧的机械硬盘上写延迟一高就频繁选举OpenStack 的并发创建请求几乎全部超时。5. 生产部署的完整实操路径5.1 初始化 Kubernetes 集群这里基于一个最常见的基础前提多台干净 Ubuntu 22.04 主机Kubernetes 版本建议 1.27 或以上容器运行时使用 containerd。首先需要确认每个节点都能解析彼此主机名并保证 6443、10250、2379 等端口相互可达。这里不建议用太新的 K8s 版本因为 OpenStack-Helm 的上游适配往往存在滞后太新的版本可能导致官方 Chart 里部分 API 字段失效。集群初始化之后需要立刻部署 CNI 插件。多集群场景下我建议使用 Calico它在处理 BGP 对等和跨子网通信时比较稳定。需要注意的是OpenStack 组件本身有大量对外提供 API 服务的场景Kubernetes 的 Service 可以选用 NodePort 或 LoadBalancer但生产环境我一般建议严格区分集群内访问和外部访问组件之间内部调用使用 ClusterIP外部用户访问 OpenStack API 使用 Ingress 或独立 LB。Kubernetes 集群的监控和日志必须提前就位。部署 Prometheus、Loki 和 Grafana 不是可选项遇到问题的时候一条容器被 Error 或 CrashLoopBackOff 的日志能帮你省下不少 Debug 时间。这些基础服务跑起来了再执行 nginx 验证确认 Deployment、Service、Ingress 都真实可用。5.2 部署 OpenStack 控制平面OpenStack-Helm 的部署流程相对固定但细节容易出错。首先克隆 openstack-helm 和 openstack-helm-infra 代码库到部署机然后使用脚本初始化 Helm 并创建 namespace比如 openstack。执行标准初始化时要注意先把基础设施服务的 Chart 准备完。基础设施包括 MariaDB、RabbitMQ、Memcached、etcd 等。一个常用的方法是先渲染基础设施 Chart然后只安装其中一部分服务比如 依次执行helm repo add openstack-helm https://..., # 实际地址需替换为你仓库的地址 ./tools/deployment/common/00-install-kubernetes.sh ./tools/deployment/common/01-install-infra.sh这条路径早期会拉一堆镜像并建立 PV 存储如果 Ceph 没有就直接用 OpenEBS 等本地存储方案做验证但生产环境不要用太简单的本地目录存储否则后期扩展会非常痛苦。初始化完成后再逐个安装 Keystone、Glance、Nova、Neutron、Cinder。命令大致是helm install keystone ./keystone --namespaceopenstack \ --set images.tags.keystone... \ --set manifests.ingress_apitrue \ --set endpoints.identity.host...重点是 endpoints 配置。OpenStack 组件的 endpoint 是指向集群内部还是外部必须事先明确。这里我们踩过不少坑之前把 endpoint 全部配成了 NodeIP 加 NodePort然后在某些 Pod 里通过 127.0.0.1 访问导致内部调用全部走到了错误的网络。正确的设计应该是内部组件通过 clusterDomain 的方式访问 API比如 keystone.openstack.svc.cluster.local外部用户则通过 Ingress 提供的统一入口访问。这样逻辑上干净排错也容易。5.3 部署 nova-compute 节点控制平面跑起来之后关键步骤是加入计算节点。计算节点上要安装 libvirt 和 qemu-kvm并把相关目录共享给 Pod 使用。nova-compute 的 Deployment 通常通过 nodeSelector 指定标签比如 node-role.kubernetes.io/comptrue。同时需要注意 KVM 设备 /dev/kvm 的挂载没有这个设备虚机启动会直接失败状态一直停在 ERROR 或者调度失败。计算节点的镜像拉取也有讲究。nova-compute 容器比较大而且每次组件升级都可能要重新拉镜像建议在节点上提前把镜像预拉或者配置好私有镜像仓库镜像缓存。生产环境中避免所有节点同时拉镜像导致可能只是镜像仓库本身就是一种风险源。加入计算节点后不是所有虚机都能顺利调度到这台节点上。你需要在 K8s 层面给 nova-compute 所在节点设置足够的资源预留比例。毕竟 KVM Guest 本身还要耗费物理机的 CPU 和内存K8s 默认的资源管理不会理解 OpenStack 实例的资源需求。我在实际环境里就是这样做的nova-compute 节点上的 pods 资源 request 尽量低但把 eviction hard 阈值调低一些给 QEMU 进程留出充足的内存空间。5.4 创建第一台云主机验证当控制平面和计算节点就绪创建第一台云主机的验证点有很多。先用 openstack CLI 创建好项目、用户、外部网络、内部网络和路由然后上传一个测试镜像比如 CirrOS执行openstack server create --flavor m1.tiny \ --image cirros --network demo-net \ test-vm openstack server list最怕的是实例一直处于 BUILD 状态然后直接变成 ERROR。如果发现这种情况先不要急着查 Nova 日志优先查看 nova-compute Pod 里的日志和中转节点上的 nova-conductor 日志。通常刷到 socket 连接失败、数据库连接失败、virt driver 初始化失败这几种问题反而好定位。网络侧确认虚机拿到了私有 IP并能通过路由 ping 通外部网络。如果只是创建成功但 ping 不通九成是安全组规则或者 L3 Agent 没正常启动和安全组的关系比路由更大尤其默认安全组经常只放行 SSH 而不放行 ICMP。6. 常见问题与排查技巧实录6.1 Pod 启动失败镜像和配置是首因控制平面组件里Keystone 和 Glance 的 Pod 启动失败率最高。常见的线索是 CrashLoopBackOff。在这里面被重复尝试的原因第一类是镜像 tag 和组件版本不匹配第二类是数据库初始化和密码不统一。排查这类问题我有一套比较固定的顺序先kubectl describe pod看事件再kubectl logs看容器输出最后进到容器里用openstack-helm-tools检查配置文件。单纯把错误日志拉出来看很多时候只说 connection refused根本原因可能在于 RabbitMQ 的 user 密码和 Keystone 的配置不同步。所以部署过程中建议把所有密码集中放到一个文件里先生成并记录好后续所有 values 引用变量保持一致。避免部署到一半发现某个服务的密码被 Helm 渲染成随机值只能掉头重来。6.2 网络不同OpenStack 网络和 K8s 网络冲突网络问题通常是最耗时间的。有一个常见现象OpenStack 虚机内部的流量到网关正常但从虚机访问宿主机的 Pod 或者另一台计算节点上的虚机时不时会丢包。这种有时候是 MTU 不一致导致的OpenStack 网络和 VXLAN 底网 MTU 通常要设置为 1400 或更小否则大包直接丢了。另一种情况是两个网络栈同时在同一节点上使用 iptables 规则K8s 的 kube-proxy 和 Neutron 的 L3 Agent 可能会互相覆盖。我最后的解决方式很简单物理隔离网络平面分别用不同的网卡、不同的交换机并且 K8s 集群的 Pod 网段和 OpenStack 的内部网络使用完全独立的大段网段避免路由条目互相干扰。只要物理层定义清晰很多软件层互相打架的问题其实根本不会出现。6.3 性能诊断慢的根源经常不在 OpenStack创建虚机慢、API 响应慢、存储 IO 不稳定这些性能问题生产环境迟早都必须面对。在 OpenStack on Kubernetes 的架构里不能一上来就盯着 Nova 或 Cinder 日志优先从底层确认物理层 IO 和网络真实情况。建议用 fio 测试各个节点的块存储能力用 iperf 验证不同节点之间的网络带宽和延迟。在这些结果确认没问题后再对 MySQL 慢查询、RabbitMQ 队列积压、Keystone Token 校验缓存做排查。往往到最后会发现瓶颈是在某个存储节点的单盘性能或者其他基础设施上。还有一种问题很有意思就是容器被重新调度之后Pod 在新的节点上启动但是 Pod 内部某些进程残留了旧节点的状态文件。这类问题要靠 OpenStack-Helm 的 lifecycle hooks 或者手动清理旧节点的挂载数据解决。难受的是这些问题不一定有稳定复现路径所以生产环境中每次节点维护之前提前规划一个完整的节点下线流程非常有必要。最后再分享一个小技巧很多看似是 OpenStack 组件的问题实际上来自 K8s 控制循环层面的设计。调测过程中如果你把 kubectl 和 helm 的日志轮转配置做好很多状态不一致的问题从控制器之间的事件冲突角度去理解比在 OpenStack 代码里反复打日志更有效率。这也是为什么我会建议团队核心成员抽空去细读 Kubernetes 的 controller-manager 和 scheduler 源码。只有把底层调度器的行为吃透遇到虚机调度失败、集群节点资源碎片化这类问题时心里才能有一个清晰的排查路径不至于被各种日志带着跑偏。