Kubernetes 部署 Percona XtraDB Cluster 高可用 MySQL 集群实战

发布时间:2026/10/21 0:30:41
Kubernetes 部署 Percona XtraDB Cluster 高可用 MySQL 集群实战 1. 为什么要在 Kubernetes 里跑 PXC 集群把 MySQL 跑在 Kubernetes 上很多人第一反应是“有状态服务上容器这不是自找麻烦吗”。我一开始也这么想直到手里几个业务系统的数据库单点故障越来越频繁主从切换靠脚本加人工半夜被叫起来处理主库挂掉的事发生了好几次才下决心把数据库这层也搬到 K8s 里统一编排。选 PXCPercona XtraDB Cluster而不是普通 MySQL 主从核心原因就一个它自带多主同步复制和自动选主能力节点挂了集群自己收敛不需要外部中间件来管故障转移。PXC 的本质是 Galera Cluster 的一个发行版三个及以上节点组成一个集群任意节点都能读写写入时通过认证协议在集群内广播事务多数节点确认后才提交。这套机制决定了它对网络延迟和节点数量很敏感也决定了它在 K8s 里的部署方式和普通无状态应用完全不同——你需要 StatefulSet 来保证稳定的网络标识和存储需要 Headless Service 来做节点发现需要 PVC 来保证数据持久化还需要一个初始化引导逻辑来决定“谁是第一个启动的节点”。这篇文章面向的是已经对 Kubernetes 有基本操作能力、手里有真实数据库迁移或高可用需求的运维和开发人员。如果你只是想在本地跑个 MySQL 玩玩那用单实例 Deployment 就够了没必要上 PXC。但如果你面对的是生产环境、要求 RPO 接近零、能接受一定程度的写入延迟换取高可用那 PXC on K8s 这套方案值得认真研究。下面我会从整体设计思路讲起把每个关键环节拆开包括我实际部署时踩过的坑和调优参数尽量让你照着做就能跑起来。2. 整体架构设计与核心组件选型2.1 为什么是 StatefulSet 而不是 Deployment无状态应用用 DeploymentPod 名字随机、IP 随机、存储共享挂了重建就行。但 PXC 每个节点都有自己的数据副本节点之间通过固定的域名互相通信Pod 重启后必须还能被集群里其他节点认出来。StatefulSet 正好解决这个问题它给每个 Pod 分配一个稳定的序号pxc-0、pxc-1、pxc-2配合 Headless Service 生成稳定的 DNS 记录pxc-0.pxc-service.namespace.svc.cluster.localPod 重建后域名不变其他节点还能找到它。这里有个细节很多人忽略StatefulSet 的 Pod 默认是有序启动的pxc-0 起来之后才起 pxc-1。这个特性对 PXC 初始化特别关键因为集群第一个节点需要以 bootstrap 模式启动来初始化集群后面的节点才能加入。如果你用 Deployment所有 Pod 同时起来谁都不认识谁集群根本组不起来。2.2 Headless Service 的作用与配置要点普通 Service 会给一个 ClusterIP流量通过 kube-proxy 负载均衡到后端 Pod。但 PXC 节点之间需要直接通信每个节点要知道其他节点的具体地址不能走负载均衡。Headless Service 就是把clusterIP: None设上这样 Service 不给虚拟 IP而是直接返回所有后端 Pod 的 DNS A 记录。PXC 节点通过查询这个 Service 的 DNS 就能拿到集群里所有节点的 IP实现自动发现。配置上要注意两点一是selector必须匹配 StatefulSet 的 Pod 标签二是端口要暴露 PXC 通信需要的几个端口。PXC 用到 3306MySQL 客户端、4567Galera 复制、4568IST 增量同步、4444SST 全量同步。这几个端口少一个集群都起不来我见过有人只开了 3306结果节点一直卡在 joining 状态。2.3 存储方案选择Local PV 还是网络存储数据库对磁盘 IO 极其敏感存储选型直接决定集群性能。K8s 里常见的有几类存储类型延迟可用性适用场景Local PV本地 SSD极低节点故障数据丢失对性能要求极高、有副本冗余网络块存储如 Ceph RBD中等跨节点可迁移通用生产环境NFS较高共享方便测试环境、低并发我的建议是生产环境优先用网络块存储虽然延迟比本地盘高一些但 Pod 漂移到其他节点时数据还能挂载回来。Local PV 性能最好但节点挂了数据就没了除非你的 PXC 副本数足够多、能接受单节点数据丢失后重新同步。测试环境用 NFS 就够了别在测试环境纠结性能。存储容量上PXC 每个节点都存全量数据所以 PVC 大小要按单节点全量数据量来算不是总量除以节点数。比如你有 500G 数据三个节点每个都要 500G 以上的 PVC再留 30% 余量给 binlog 和临时表。2.4 镜像选择Percona 官方镜像 vs 自建镜像Percona 官方提供了percona/percona-xtradb-cluster镜像开箱即用内置了集群启动脚本能根据环境变量自动判断是 bootstrap 还是 join。我强烈建议直接用官方镜像别自己从 MySQL 镜像改因为 PXC 的启动逻辑涉及 Galera 的引导判断自己写脚本很容易在节点重启时出现脑裂。镜像版本选择上5.7 和 8.0 都有官方支持。8.0 在性能和 SQL 特性上更好但如果你现有业务还在 5.7迁移到 8.0 要注意字符集和认证插件的兼容性。我目前生产用的是 8.0.32 这个版本跑了半年多没出过集群层面的问题。3. 部署前的环境准备与关键参数计算3.1 集群规模怎么定三个节点是底线PXC 基于 Galera 的多数派协议集群要能正常工作存活节点数必须大于总数的一半。这意味着1 个节点没有高可用挂了就没了2 个节点坏一个就只剩一个达不到多数派集群只读3 个节点坏一个还剩两个多数派成立集群继续可写5 个节点能容忍两个节点同时故障所以最小生产规模是 3 节点。有人为了省钱用 2 节点结果一个节点维护重启整个集群就只读了业务直接受影响。如果预算实在紧张可以用 3 个节点但配置低一些也别用 2 个高配节点。节点数也不是越多越好。Galera 的写入需要所有节点认证节点越多认证阶段的网络往返越多写入延迟越高。3 到 5 个节点是比较平衡的选择超过 7 个节点写入性能下降会很明显。3.2 资源请求与限制的合理设置数据库 Pod 的资源设置有个原则requests 要保证limits 要留余量。PXC 节点在同步大事务时内存和 CPU 会短时飙升如果 limits 设得太紧OOMKilled 或者 CPU 被限流会导致节点被踢出集群。我一般这样配以 8 核 16G 的节点为例resources: requests: memory: 8Gi cpu: 2 limits: memory: 14Gi cpu: 6内存 limits 留 2G 给系统和其他进程别把 16G 全给 MySQL。MySQL 的innodb_buffer_pool_size一般设为容器内存的 50% 到 70%比如 limits 是 14Gbuffer pool 设 8G 到 10G 比较合适。设太大反而会因为内存不足触发 swap性能断崖式下跌。CPU 的 limits 不要设成和 requests 一样否则节点突发负载时会被 throttle。我见过有人 requests 和 limits 都设 2 核结果 SST 全量同步时 CPU 打满被限流同步超时失败节点一直加不进集群。3.3 网络与 DNS 配置的坑K8s 集群的 DNS 解析默认走 CoreDNSStatefulSet 的 Pod 域名解析需要 Headless Service 配合。这里有个常见问题Pod 的hostname和subdomain必须设置否则 DNS 记录生成不出来。spec: hostname: pxc subdomain: pxc-service这样 pxc-0 的完整域名就是pxc-0.pxc-service.default.svc.cluster.local。PXC 的集群地址配置里要写这个完整域名不能只写 Pod 名。另外如果你的 K8s 集群跨多个可用区要注意节点间网络延迟。Galera 对延迟很敏感跨区延迟超过 5ms 就会出现明显的写入性能下降超过 50ms 基本不可用。尽量把 PXC 节点调度到同一区域用podAntiAffinity让它们分散在不同物理节点上保证高可用但别跨太远。4. 完整部署实操从零到集群可用4.1 创建 Namespace 和 ConfigMap先把命名空间建好所有资源都放里面方便管理。kubectl create namespace pxc-demo然后是 ConfigMap把 PXC 的配置文件挂进去。核心配置项我列一下apiVersion: v1 kind: ConfigMap metadata: name: pxc-config namespace: pxc-demo data: custom.cnf: | [mysqld] default_storage_engineInnoDB innodb_buffer_pool_size8G innodb_log_file_size1G innodb_flush_log_at_trx_commit2 wsrep_provider_optionsgcache.size2G;gcs.fc_limit256 wsrep_slave_threads8 wsrep_sst_methodxtrabackup-v2 max_connections1000这里几个参数值得解释。innodb_flush_log_at_trx_commit2是把刷盘频率从每次事务改成每秒一次牺牲一点持久性换性能PXC 本身有副本冗余单节点掉电丢一秒数据可以接受。gcache.size是 Galera 的写缓存设大一点能让新加入的节点走 IST 增量同步而不是 SST 全量同步省时间。wsrep_slave_threads是并行应用事务的线程数设成 CPU 核数左右比较合适。4.2 部署 Headless ServiceapiVersion: v1 kind: Service metadata: name: pxc-service namespace: pxc-demo spec: clusterIP: None selector: app: pxc ports: - name: mysql port: 3306 - name: galera port: 4567 - name: ist port: 4568 - name: sst port: 4444注意clusterIP: None这行这是 Headless Service 的标志。端口名要和后面 StatefulSet 里的容器端口名对应上。4.3 StatefulSet 核心配置逐项拆解StatefulSet 是整个部署的核心我把它拆成几块讲。副本数和 Service 关联apiVersion: apps/v1 kind: StatefulSet metadata: name: pxc namespace: pxc-demo spec: serviceName: pxc-service replicas: 3 podManagementPolicy: OrderedReadyserviceName必须指向刚才建的 Headless Service这是 Pod 域名能解析的前提。podManagementPolicy用默认的OrderedReady保证 pxc-0 先起来。容器配置containers: - name: pxc image: percona/percona-xtradb-cluster:8.0.32 env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: pxc-secret key: root-password - name: CLUSTER_NAME value: pxc-cluster - name: XTRABACKUP_PASSWORD valueFrom: secretKeyRef: name: pxc-secret key: xtrabackup-password - name: DISCOVERY_SERVICE value: pxc-service ports: - containerPort: 3306 - containerPort: 4567 - containerPort: 4568 - containerPort: 4444DISCOVERY_SERVICE指向 Headless Service 名字官方镜像的启动脚本会去查这个 Service 的 DNS 来发现集群成员。密码用 Secret 管理别明文写在 YAML 里。存储挂载volumeMounts: - name: data mountPath: /var/lib/mysql - name: config mountPath: /etc/percona-xtradb-cluster.conf.d/custom.cnf subPath: custom.cnf数据目录挂 PVC配置文件从 ConfigMap 挂进来。subPath的用法是把 ConfigMap 里的单个 key 挂成文件而不是覆盖整个目录。volumeClaimTemplatesvolumeClaimTemplates: - metadata: name: data spec: accessModes: [ReadWriteOnce] storageClassName: fast-ssd resources: requests: storage: 500Gi每个 Pod 会根据自己的序号生成独立的 PVC比如># 先部署 replicas: 1 kubectl apply -f pxc-statefulset.yaml # 等 pxc-0 变成 Running 且 Ready kubectl get pods -n pxc-demo -w # 确认集群状态 kubectl exec -n pxc-demo pxc-0 -- mysql -uroot -p -e show status like wsrep_cluster_size看到wsrep_cluster_size是 1说明单节点集群起来了。然后再扩容kubectl scale statefulset pxc -n pxc-demo --replicas3pxc-1 和 pxc-2 启动时会自动去查pxc-service的 DNS发现 pxc-0 已经在跑就以 join 模式加入集群。加入过程中会做 SST 全量同步把 pxc-0 的数据全量拷贝过来数据量大的话这一步会比较久。注意扩容前一定要确认 pxc-0 的wsrep_cluster_size是 1 且wsrep_ready是 ON否则新节点可能加入失败或者形成两个独立集群脑裂。4.5 验证集群状态的关键命令集群起来之后别急着上业务先验证状态。进任意一个节点执行-- 集群大小应该是 3 show status like wsrep_cluster_size; -- 节点是否就绪应该是 ON show status like wsrep_ready; -- 集群状态应该是 Synced show status like wsrep_local_state_comment; -- 集群中所有节点的状态 show status like wsrep_incoming_addresses; -- 复制队列正常应该是 0 show status like wsrep_local_recv_queue;wsrep_local_state_comment是Synced才说明节点完全同步好了。如果是Joining或者Donor说明还在同步数据这时候别切流量过来。wsrep_local_recv_queue持续大于 0 说明节点应用事务跟不上可能是wsrep_slave_threads设小了。5. 常见故障排查与运维经验5.1 节点一直卡在 Joining 状态这是最常见的故障。现象是 Pod 是 Running但wsrep_local_state_comment一直是Joining集群大小也不对。排查思路按这个顺序来看日志kubectl logs pxc-1 -n pxc-demo重点看 SST 相关的报错。常见的是 xtrabackup 版本不匹配、SST 端口不通、磁盘空间不足。检查网络从 pxc-1 里telnet pxc-0.pxc-service 4444看 SST 端口通不通。NetworkPolicy 如果配了要放行 4444、4568、4567 这几个端口。检查磁盘SST 全量同步需要和目标节点数据量相当的临时空间PVC 满了会直接失败。检查 gcache如果 gcache 太小新节点没法做 IST 增量同步只能走 SST 全量数据量大时容易超时。我遇到过一次是 PVC 空间不够SST 传到一半失败节点反复重试。扩容 PVC 后重新加入就好了。5.2 集群脑裂最危险的状态脑裂是指集群分裂成两个独立的部分各自认为自己是主集群都接受写入。这种情况数据会不一致恢复起来非常麻烦。脑裂的根因通常是网络分区或者节点数配置不当。比如 3 节点集群网络抖动导致一个节点和另外两个失联失联的那个节点如果还接受写入就脑裂了。Galera 的多数派机制本来能防止这个但如果配置了pc.weight或者evs相关参数不当可能绕过保护。预防措施节点数用奇数3 或 5不要随意调整pc.weight除非你很清楚在做什么网络层面保证节点间通信稳定别跨高延迟区域监控wsrep_cluster_status正常是Primary变成non-Primary说明节点已经脱离集群如果真脑裂了先停掉所有节点的写入然后选数据最全的那个节点做 bootstrap 重建集群其他节点清空数据重新加入。这个过程会丢数据所以预防远比恢复重要。5.3 写入性能突然下降PXC 的写入性能受几个因素影响按影响程度排序因素影响排查方法网络延迟高节点间 ping 延迟应小于 5ms大事务高查wsrep_local_recv_queue大事务会阻塞节点数过多中超过 5 节点认证开销明显磁盘 IO中查 iostatIO util 持续 100% 说明瓶颈参数配置低检查wsrep_slave_threads、innodb_flush_log_at_trx_commit大事务是 PXC 的天敌。一个 10 万行的批量插入在单机 MySQL 上可能几秒就完了但在 PXC 里这个事务要在所有节点上认证和应用任何一个节点慢都会拖累整个集群。建议把大事务拆成小批次比如每 1000 行提交一次。5.4 节点重启后无法重新加入节点维护重启后如果 gcache 里还有它需要的增量数据会走 IST 快速同步如果 gcache 已经被覆盖就只能走 SST 全量同步。SST 期间该节点不可用而且会从 donor 节点拷贝全量数据对 donor 节点也有压力。减少 SST 频率的办法把gcache.size设大能缓存更多增量节点重启前先手动wsrep_desync让它优雅退出维护窗口尽量短别让节点离线太久如果节点重启后一直加不进去最干脆的办法是清空该节点的数据目录让它重新做 SST。虽然慢但比反复重试靠谱。kubectl exec -n pxc-demo pxc-1 -- rm -rf /var/lib/mysql/* kubectl delete pod pxc-1 -n pxc-demoPod 重建后会自动重新加入集群走 SST 全量同步。5.5 备份策略别只依赖集群冗余PXC 的副本冗余能防节点故障但防不了误删库、误更新这种逻辑错误。必须做定期逻辑备份。我用的方案是每天凌晨用mysqldump或xtrabackup做一次全量备份存到对象存储。备份时要注意从单个节点备份别从多个节点同时备份浪费资源用--single-transaction保证一致性快照备份文件要验证可恢复性别等到真需要恢复时才发现备份是坏的kubectl exec -n pxc-demo pxc-0 -- mysqldump -uroot -p \ --single-transaction --routines --triggers \ --all-databases backup-$(date %Y%m%d).sql恢复的时候先停掉集群写入选一个节点导入备份然后让其他节点重新 SST 同步。这个过程要谨慎最好在测试环境演练过再上生产。6. 生产环境调优与监控要点6.1 关键监控指标PXC 集群要监控的指标比单机 MySQL 多一层除了常规的 QPS、连接数、慢查询还要盯 Galera 的状态wsrep_cluster_size集群节点数掉了说明有节点离线wsrep_cluster_status必须是Primarynon-Primary说明节点脱离集群wsrep_ready必须是ONOFF说明节点不能接受写入wsrep_local_recv_queue接收队列持续大于 0 说明应用事务跟不上wsrep_flow_control_paused流控暂停比例大于 0.05 说明集群压力大wsrep_local_state_comment节点状态Synced才正常这些指标用 Prometheus 的 mysqld_exporter 采集配 Grafana 面板展示。告警规则我设的是wsrep_cluster_size小于预期值持续 1 分钟就告警wsrep_local_recv_queue大于 100 持续 5 分钟告警。6.2 参数调优的取舍PXC 的参数调优本质是在一致性、性能、可用性之间找平衡。几个关键取舍同步复制 vs 异步复制PXC 默认是同步复制写入要等多数节点确认。如果业务能接受异步可以调wsrep_sync_wait0但这样就不保证强一致了。金融类业务别动这个参数。流控阈值gcs.fc_limit控制流控触发阈值默认 16。调大能减少流控频率但节点落后太多时恢复更慢。我一般设 256配合wsrep_slave_threads8在写入压力和同步延迟之间取平衡。SST 方法wsrep_sst_method有xtrabackup-v2、rsync、mariabackup几个选项。xtrabackup-v2支持增量是首选。rsync会锁表生产环境别用。6.3 滚动升级的正确姿势PXC 版本升级或者参数变更需要重启节点必须逐个滚动操作不能同时重启多个。流程是从集群里摘掉一个节点wsrep_desyncON重启该节点等它重新同步好wsrep_local_state_commentSynced恢复该节点wsrep_desyncOFF等集群稳定几分钟再操作下一个节点整个过程集群保持可用但性能会有波动因为少了一个节点分担。建议在业务低峰期做。注意升级前一定要备份并且确认新版本和旧版本的 Galera 协议兼容。跨大版本升级比如 5.7 到 8.0不能滚动升级需要逻辑导出导入。7. 我踩过的几个坑和最后的小建议第一个坑是忘了配 PodAntiAffinity结果三个 PXC Pod 被调度到同一个物理节点上那个节点一挂整个集群全没了。后来加了反亲和规则强制分散到不同节点affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: app: pxc topologyKey: kubernetes.io/hostname第二个坑是PVC 用了默认的 StorageClass那个存储类是基于机械盘的IOPS 低得可怜集群跑起来后写入延迟一直在 100ms 以上。换成 SSD 存储类后降到 5ms 以内。数据库对存储的要求真的不能省。第三个坑是没设wsrep_sst_donorSST 时随机选 donor 节点有时候选到一个负载已经很高的节点导致 SST 超时。后来指定了固定的 donor 优先级让负载低的节点优先做 donor。最后分享一个小技巧在 PXC 前面挂一个 ProxySQL 或者 HAProxy 做读写分离和故障转移。PXC 虽然多主但让应用直接连多个节点会有连接管理问题而且节点故障时应用要自己处理重连。加一层代理应用只连代理代理负责健康检查和路由应用层就简单多了。ProxySQL 还能做读写分离把读流量分散到多个节点减轻写入节点的压力。这套方案我在几个生产环境跑了快一年经历过节点故障、网络抖动、大事务冲击集群都能自己恢复没出现过数据丢失。K8s 管有状态服务确实比无状态麻烦但一旦跑顺了运维效率比传统部署高很多。希望这些经验能帮你少走点弯路。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询