ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

OpenStack on Kubernetes生产部署实战:控制面容器化与排障复盘

OpenStack on Kubernetes生产部署实战:控制面容器化与排障复盘 《OpenStack on Kubernetes 生产部署实战十三》这已经是这个系列的第十三篇了。前十二篇从OpenStack各组件的容器化改造讲起一路聊到Kubernetes之上的编排策略、网络选型、存储接入好多朋友在评论区催更问得最多的就是“你的生产环境到底踩了哪些坑”“参数到底怎么调”。说实话OpenStack和Kubernetes两套体系叠在一起复杂度不是简单的11尤其到了生产阶段遇到的每一类问题都值得单独拎出来拆一遍。这篇我打算把控制平面容器化的架构设计、生产部署时的关键参数、以及我在现网里遇到的几个典型故障整理出来给你一条可以直接照着复盘的路径。作为系列的中后段这篇的定位不是给零基础朋友科普概念而是面向已经准备把OpenStack控制面迁到Kubernetes、或者正在做方案评估的运维和架构师。你会看到为什么我敢把MariaDB、RabbitMQ这些有状态组件也放进K8s也会看到网络节点、存储池在容器化形态下怎么划分故障域最后还有几段排障实录都是花过代价换来的经验。Kubernetes负责调度和自愈OpenStack负责对外提供云主机、网络和存储能力这两套系统真正耦合好的时候控制平面的运维成本和故障恢复速度会有质的改变。1. 架构选型为什么敢把OpenStack控制平面交给Kubernetes1.1 从物理机裸部署到K8s编排的演进逻辑传统OpenStack部署方案里控制节点通常是三台物理机或虚拟机跑着Keystone、Nova API、Neutron Server、Glance、Cinder等十几个systemd服务。每台机器的Python环境、配置文件、依赖版本都像积木一样堆在一起升级一个组件经常要连带重启好几个服务而且一旦某台控制节点磁盘或内存出问题故障恢复基本靠人工重装、重新同步配置。这套模式跑了几年最大的痛点不是功能不够用而是“变更太难”。改一个配置项要逐台登录、逐服务重启稍不留神就漏掉一台扩容控制节点要手工装包、手工配HA服务进程挂掉之后的恢复也做不到秒级。Kubernetes天然解决了其中一个核心问题调度和自愈。把OpenStack各服务做成容器镜像用Deployment、StatefulSet去描述期望状态Pod挂了就重建节点宕了就漂移滚动升级也变成了标准动作。我从实际维护经验出发迁移到K8s之后最明显的改善是控制平面的故障恢复时间从“小时级”缩短到“分钟级”。比如之前遇到Keystone进程OOM要登录服务器、重启服务、确认日志现在Pod内存超限被OOMKilled后kubelet配合livenessProbe会自动重建业务侧只有一两次请求超时整体可用性提升非常明显。1.2 控制平面容器化的边界哪些服务适合上K8s哪些留在宿主机这里必须先说清楚一个容易误导人的点并非所有OpenStack组件都适合丢进Kubernetes跑。我见过不少方案把Nova Compute、Neutron Open vSwitch Agent也容器化导致性能和排障复杂度急剧上升。控制平面和数据平面要分开看待。适合放进Kubernetes的是无状态API服务和可横向扩展的worker。Keystone、Nova API、Nova Conductor、Neutron Server、Glance API、Cinder API、Heat Engine这些它们不直接承载数据面的流量状态可以放在数据库和消息队列里Pod重建不丢数据。还有Horizon这样的Web前端容器化部署更是毫无压力。不适合放进Kubernetes的是依赖宿主机内核特性、网卡特性、设备直通的服务。Nova Compute必须管理宿主机上的libvirt、KVM、物理网卡容器化之后挂载/var/run/libvirt、/dev/vhost-net等路径非常别扭而且性能损耗不可控。Neutron的OpenVSwitch Agent同样需要直接在宿主机上操作网桥、流表如果塞进Pod网络路径多一层namespace转换生产环境排查VXLAN隧道问题时能把人折磨疯。还有Ceph的OSD进程存储节点保持物理部署才是正道。我在生产环境里划定的边界是Kubernetes只管控制面计算节点和存储节点继续沿用传统部署方式。控制面容器化带来的收益已经足够大数据面保持稳定可控两边的分工清晰明确排障时也容易划分问题域。2. 网络与控制面的关键设计2.1 OVN与Kubernetes网络的共存关系把OpenStack跑在Kubernetes上网络规划是整个架构里最需要提前想清楚的部分。很多第一次接触这个组合的人会问Kubernetes用Calico或Flannel给Pod分配网段OpenStack Neutron也给虚拟机分配VXLAN网段两套网络会不会冲突答案是不会但前提是你要在物理网络拓扑上做清楚划分。我的方案是改成叠加组网Kubernetes集群的Pod网段用于承载控制面容器之间的通信这个网段和OpenStack给租户虚拟机分配的VXLAN大二层网络没有任何交集。控制面Pod挂的是K8s的Service网络虚拟机数据面走的是OVN的Logical Switch和VXLAN隧道。具体落地时Kubernetes节点上的CNI我选用了Calico也有环境用Canal实测Calico在性能和排障资源上更成熟负责整个K8s集群内Pod的互通。而在OpenStack侧Neutron使用OVN作为ML2的mechanism driver。OVN的ovn-controller跑在计算节点上负责VXLAN隧道的封装和解封装以及OpenFlow流表的下发。两个网络平面在物理层共用网卡但通过不同的VLAN或子网隔离管理网流量和数据网流量互不干扰。需要特别注意的是MTU问题。Kubernetes Pod网络和OVN数据面如果都走VXLAN叠加封装之后报文会膨胀物理网络MTU必须提前预留。默认物理网卡MTU是1500VXLAN隧道会吃掉50字节如果虚拟机内部再叠加VXLAN可用MTU只有1400。生产环境通常建议把管理网和存储网MTU调到9000走Jumbo Frame租户数据网按underlay的实际能力下发MTU值否则会出现“虚拟机内网卡显示UP但大包ping不通、小包正常”的经典问题。2.2 Neutron元数据代理和DHCP在容器化形态下的调度策略Neutron组件里有一类特殊角色比如neutron-metadata-agent和neutron-dhcp-agent它们在传统部署里通常和网络节点绑在一起容器化之后很容易被当成普通Deployment随便调度。我在上线初期就吃过亏metadata-agent被调度到了没有网络节点的K8s worker上租户虚拟机请求metadata地址时完全不通。生产环境的处理方式是给这类Agent使用DaemonSet nodeSelector固定到指定节点。具体来说标记一组节点为network-nodetrue然后在chart的values里把nodeSelector指向这一组节点。这样每个网络节点上都会跑一个metadata-agent的Pod和宿主机上的OVN网桥配合保证metadata服务的192.168.0.2路由可达。DHCP Agent的情况类似但更麻烦的是它需要绑定到固定的namespace和网桥。容器化后DHCP Agent要正常工作必须让Pod共享宿主机网络hostNetwork: true或者把host的网络namespace挂载进容器。我最终采用hostNetwork方式这样DHCP Agent创建的dnsmasq进程能直接监听在宿主机网桥上不引入额外的NAT链路也方便在节点上直接抓包。这里补充一个经验不要把网络Agent设计成随处漂移的Pod。DHCP和metadata服务天然和特定节点上的网桥绑定如果Pod被K8s调度到其他节点不仅起不来还可能因为重复绑定网桥导致网络节点之间出现路由竞争。用nodeSelectorDaemonSet固定拓扑是最稳妥的方式。2.3 数据面的VXLAN与Underlay互联互通OpenStack租户网络的VXLAN流量实际是从计算节点上的br-int、br-tun网桥出去的这些网桥由OVS或OVN管理。在K8s化架构里计算节点的ovn-controller是独立的systemd服务不跑在Pod里。这样设计的好处是OpenStack数据面的故障边界清晰不会因为K8s集群重启导致所有租户虚拟机同时断网。计算节点和网络节点之间的VXLAN隧道需要保证underlay的IP路由可达。我建议用独立的VLAN和网段承载VXLAN流量和Kubernetes Pod网段、管理网段分开。如果条件允许最好走独立物理网卡避免VXLAN流量和存储流量争抢带宽。生产流量模型下VXLAN封装后的流量放大效应明显尤其在东西向流量大的场景网卡队列和中断绑定也要提前优化。实际配置时计算节点上的/etc/sysconfig/network-scripts/ifcfg-eth2这类underlay网卡不要配IP或者只配一个管理地址真正的VXLAN隧道通过OVN数据库里的隧道信息自动建立。不要手动去添加VXLAN接口否则一旦OVN数据库和本地配置不一致隧道状态会非常难排查。3. 生产部署实操核心步骤与参数解析3.1 部署前检查清单内核、DNS、NTP一个都不能少正式执行Helm部署之前我给团队定了一张检查清单每一条都是真金白银换来的内核版本统一为4.18以上生产环境建议5.4 LTS开启br_netfilter和nf_conntrack相关模块否则K8s的Service转发和OVN流表匹配会不完整。DNS解析必须走集群内部CoreDNS各控制节点/etc/resolv.conf不要指向外部DNS。OpenStack组件之间通过服务名通信如果Pod内DNS解析到外部很多服务启动时找不到数据库和消息队列报错方式千奇百怪。NTP时间同步必须全局统一。OpenStack组件对时间偏移极其敏感尤其Keystone的tokenNova的虚拟机创建流程时间差超过30秒就会出现认证失败或任务卡死的诡异现象。K8s节点和Pod内都要验证时间同步很多基础镜像不带chrony需要entrypoint里注入。检查K8s集群的PodCIDR和OpenStack的租户VXLAN网段不能重叠。我在测试环境遇到过Pod网段用了10.0.0.0/16租户VXLAN也配了同网段结果虚拟机访问metadata时直接路由到了Pod里排查了两天。确认所有节点可以拉取镜像仓库。生产环境一般有内网Harbor提前把OpenStack各组件镜像push进去避免部署时外网抖动导致镜像拉取超时。3.2 关键Helm Chart参数选型逻辑我用的是OpenStack-Helm这套chart体系社区里有直接可用的chart也可以基于它改。核心values参数选型直接决定生产稳定性按优先级排序如下数据库和消息队列是高可用核心。MariaDB用Galera三副本配置wsrep_cluster_address时不要把Pod IP写死要用K8s的StatefulSet网络标识比如mariadb-cluster-galera-0.mariadb-cluster-galera.mariadb.svc.cluster.local。RabbitMQ同样用三节点镜像模式开启pause_minority避免脑裂时整个集群不可写。这里必须注意MariaDB和RabbitMQ的PVC一定用独立存储池不要和控制面其他无状态应用的存储混在一起防止I/O争抢。Keystone的bootstrap流程要单独跑一次Job。首次部署时Keystone数据库初始化需要创建admin用户、service项目、endpoint。用Helm hook的Job来做这件事并且在Job里带上--wait确保数据库完全就绪后再初始化。我遇到过初始化Job跑太快连不上数据库而失败后续重复执行时又因为部分表已存在而报错最后只能清库重来。Nova的placement、conductor、scheduler三个服务要分开部署。虽然可以合成一个Deployment但生产环境建议拆开方便单独扩缩容和排查。Scheduler的filter_scheduler要开启available_filters加上AggregateInstanceExtraSpecsFilter和RetriesFilter否则调度时会忽略计算节点的资源规格限制。Neutron Server需要两个副本并开启--workers 8。Neutron Server是API和RPC的汇聚点生产负载下很容易成为瓶颈多worker能明显提升并发处理能力。同时Neutron Server的service_plugins里加上router和segments否则租户网络无法创建子网和路由器。3.3 滚动升级与优雅终止的配置要点Kubernetes管理OpenStack的最大优势之一是滚动升级。但OpenStack组件升级不是简单的镜像tag替换有几个配置必须同步调整。Deployment的strategy要设置为RollingUpdatemaxUnavailable设为0maxSurge设为1。这意味着升级时先起一个新Pod等它通过readinessProbe后再终止旧Pod保证API服务零中断。我见过默认配置下maxUnavailable为25%结果升级过程中一半Keystone Pod被杀掉剩余Pod扛不住流量直接雪崩。优雅终止时间terminationGracePeriodSeconds建议设到60秒以上。OpenStack组件的worker进程收到SIGTERM后需要完成正在处理的请求和数据库事务。我在生产环境把Nova API的预停等待时间设为90秒实测能覆盖99%的在途请求。ReadinessProbe的配置我不能不强调它直接决定Pod是否被Service负载均衡。对Keystone这类API服务探针应该请求/v3/auth/tokens用HEAD或直接POST空body不要只检查TCP端口通不通。只检查端口的话进程还活着但内部状态已经异常时流量照样会打进来。对Neutron Server探针要请求/根路径并验证返回200对Nova API请求/或者/v2.1检查版本信息。3.4 存储Ceph接入与PVC设计OpenStack侧计算和存储的配合我选择Ceph作为Glance、Cinder、Nova的后端存储。控制面容器自身的PVC不用Ceph而是用本地块存储或者专门的高性能存储池避免控制面数据库的I/O和虚拟机镜像读写互相干扰。Cinder接入Ceph时cinder.conf里的enabled_backends设为cephglance_api_version设为2。rbd_user和rbd_pool要单独创建不要用Ceph的admin账号直接跑Cinder服务权限隔离是生产安全的基础。Glance镜像存储建议用rbd后端而非文件系统。镜像动辄几十G存本地磁盘扩容麻烦Ceph可以无缝扩展。show_image_direct_urlTrue这个参数要开这样Nova创建虚拟机时可以直接通过RBD克隆镜像不需要先下载镜像再上传节省大量时间。Nova的libvirt部分配置里images_type用rbdimages_rbd_pool指向Nova专用池并开启hw_scsi_modelvirtio-scsi、hw_disk_busscsi。生产环境下虚拟机的磁盘I/O性能和稳定性远好于ide模拟。4. 高可用与故障域设计4.1 控制节点故障域的物理与逻辑划分高可用不是靠Kubernetes的Pod副本数堆出来的。K8s可以保证Pod重新调度但如果所有Pod都跑在同一个物理交换机下面交换机宕机时一切白搭。生产环境必须做故障域设计。我采用的是三控制节点两计算节点集群的最小生产配置控制节点和计算节点分布在不同的机柜、不同的TOR交换机下。在Kubernetes侧给节点打标签比如failure-domain.beta.kubernetes.io/zoneaz1/az2/az3然后给OpenStack各API服务配置podAntiAffinity让同一服务的多个副本尽量分散到不同可用区。OpenStack本身也有故障域概念。Nova的aggregate可以把计算节点按机柜分组metadata里填availability_zone用户创建虚拟机时可以指定AZ。底层K8s的拓扑分布和上层OpenStack的AZ要对应起来这样某一组节点整体宕机时影响范围能控制在一个AZ内不会拖垮整个Region。4.2 数据库与消息队列的高可用陷阱MariaDB Galera和RabbitMQ镜像模式都有各自的坑我在生产中踩过的几个问题值得记录。Galera集群最大的隐患是全量同步。某节点长时间失联后重新加入集群时会触发SST全量同步数据量大的时候会把网络打满甚至导致其他节点也出现延迟。解决办法是为MariaDB Service单独划分存储和网络QoSSST走备份网络同时定期做逻辑备份万一全库损毁时能快速重建。RabbitMQ镜像队列有一个隐藏问题镜像队列的master节点切换后消费者需要重连。OpenStack各服务用的pika或kombu连接池如果实现不严谨连接断掉后不会自动重连表现为消息堆积但不消费。生产上我的建议是不要把消息队列也塞进K8s虽然可用StatefulSet管理但RabbitMQ的节点发现、cookie同步、erlang版本升级在容器里都更复杂。如果一定要容器化务必开启automatic_recovery并设置合理的heartbeat。4.3 三副本控制面故障恢复演练实录生产环境我做过一次控制节点宕机的演练记录下来的恢复过程可以作为你的参考。故障模拟把三台控制节点中的一台直接强制断电。预期是K8s自动将Pod迁移到其他节点OpenStack API保持可用但迁移时间受Pod重新调度镜像拉取服务初始化影响。实际恢复过程断电瞬间Keystone和Neutron Server的Pod漂移到了剩余两台节点由于这两个服务都有3副本流量由另外两个副本正常承载没有故障感知。MariaDB主节点在故障节点上时Galera自动选出新主应用侧有少量连接中断但OpenStack各服务的连接池自动重连后恢复正常。Nova API的Pod调度到新节点后镜像从本地Harbor拉取约1分钟readinessProbe通过后开始接收流量。整体影响窗口在3-5分钟内对应opensatck用户侧表现为虚拟机列表查询变慢创建虚拟机失败几次后会重试成功。这次演练暴露的问题是我们当初给Keystone只配了2副本故障后只剩1个可用Pod虽然没挂但QPS高的时候会很吃力。之后我把所有API服务的副本数调整为3并给关键服务配置了PodDisruptionBudget保证任何时刻至少2个副本可用。5. 监控与排障生产环境真正依赖的几条命令5.1 Prometheus与自定义指标采集Kubernetes自身的监控用Prometheus全家桶已经是标配但OpenStack侧还有自己的监控诉求。除了Pod CPU、内存这些基础设施指标还要采集Nova的nova_api请求延迟、Neutron的rpc调用耗时、Cinder的卷操作队列长度。我在每个控制面Pod里额外挂了一个exporter边车容器通过socket.io和/var/log/路径把OpenStack组件的日志转化为指标再被Prometheus抓取。重点关注的告警规则我有几条建议Keystone认证失败率5分钟内失败次数占总请求数超过5%就告警这通常意味着token签发异常或数据库连接耗尽。Nova conductor的RPC消费延迟如果延迟时间持续大于5秒说明数据库或消息队列出现瓶颈。Cinder卷创建队列长度超过阈值说明存储后端IO遇到问题通常和Ceph的osd相关。5.2 日志采集与快速定位问题的思路容器化部署最大的排障痛点就是日志分散在多个Pod中。生产环境需要统一的日志采集方案我用的是LokiPromtailPromtail以DaemonSet方式部署直接采集每个节点上/var/log/containers/下的日志并通过Pod标签自动加上namespace、pod、container这些元数据。快速定位问题有个固定的思路先查Keystone的token是否正常再查消息队列的队列堆积最后查数据库连接数。OpenStack组件之间依赖链很长日志里经常出现“connection refused”或“timeout”如果直接去查目标服务可能会被表象误导。我的习惯是先在Grafana Loki里跑一条PromQL比如{namespaceopenstack} | ERROR |~ keystone|neutron把整个控制面的错误日志按时间聚合看高频波浪出现在哪个组件再层层往下排查。5.3 常见故障速查表故障现象可能原因排查命令/手法解决方案虚拟机创建后网络不通MTU设置过大或VXLAN underlay异常虚拟机内ping -M do -s 1400测试重新下发租户网络MTU检查物理交换机端口MTUKeystone认证偶尔超时数据库连接池满了show processlist;看连接数调大MariaDB max_connections检查连接泄漏Neutron agent状态为downOVN与数据库同步异常ovn-nbctl show对比数据库记录重启ovn-controller强制重新同步Cinder卷删除卡在deletingCeph RBD镜像仍被占用rbd lock ls查看锁手动释放锁或重启对应实例Pod频繁重启livenessProbe配置过严kubectl describe pod查看重启原因调整探测阈值先排除服务自身问题速查表只是入门真正的生产环境问题往往是多个环节叠加出来的。遇到故障时先不要急着猜原因先把日志和指标时间戳对齐找到第一个异常点再顺着依赖关系往下跟比盲目重启Pod有效得多。聊一点我在实战中的体会这个架构跑了近两年我对“OpenStack on Kubernetes”这件事的认知在持续修正。最开始我期望的是用K8s解决所有运维问题后来发现容器化只解决了调度和自愈OpenStack的复杂性并没有消失只是转移到了配置、网络、存储这些更底层的地方。真正让系统稳定下来的不是某一个精巧的编排技巧而是一整套围绕可观测性、变更管理和故障演练的工程习惯。如果你正在规划这套架构我建议不要一上来就追求所有组件容器化先跑通KeystoneGlanceNovaNeutron这四个核心服务把网络和存储边界理清楚再逐步扩展。最后分享一个日常运维小技巧每次线上变更前手动在测试环境把对应组件的PVC快照做一份变更失败时可以快速回滚这个习惯已经救了我好几次希望你也能用上。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进