云原生数据库部署最佳实践:Kubernetes运行MySQL的全流程与调优指南

简介: 把MySQL部署到Kubernetes容器平台上,从StatefulSet选型到持久化存储配置再到Operator管理,我记录了完整的上手过程和3个踩过的坑。

大家好,我是数据库小学妹 👋

一家互联网公司做容器化改造,找我评估MySQL上K8s的可行性。聊完他们的场景,我给了一句结论:"读从库和开发环境可以上,核心交易库先别动。"

他们问为什么。我说:Pod是临时的,数据不能临时。StatefulSet、持久化存储、网络转发延迟,这三个问题不解决,上去了也跑不稳。

为了验证这个结论,我搭了一套完整的测试环境。从StatefulSet选型到持久化存储配置到Operator管理主从复制,再到sysbench压测对比。折腾了两周,得出了一堆数据和3个踩过的坑。
封面图 (2).png

今天我把整个过程记录下来。如果你也在考虑把数据库搬上容器平台,这篇能帮你省点时间。

为什么要把数据库跑在K8s上?

先说结论:不是为了赶时髦。传统虚拟机部署数据库,扩容要申请资源、装系统、装数据库、配置主从。一套流程下来至少一周。K8s上,一条命令就能拉起新实例。

故障恢复也是。传统方式要手动切主从、改VIP、通知应用。K8s上Operator自动检测、自动拉起、自动重建复制关系。但代价是什么?存储性能损耗。网络复杂度。运维方式完全不同。

不是所有数据库都适合上K8s。核心交易系统我建议先用虚拟机。读从库、开发测试环境、临时数据服务,适合先上容器。

为什么选StatefulSet而不是Deployment?

K8s有两种工作负载控制器。Deployment和StatefulSet。跑MySQL必须选StatefulSet。原因很简单。

Deployment是为无状态应用设计的。 Pod挂了重新拉起,新Pod和旧Pod没有区别。IP地址变了无所谓。数据不存在本地。

StatefulSet是为有状态应用设计的。 每个Pod有固定的名字(mysql-0、mysql-1、mysql-2)。Pod重建后名字不变。每个Pod绑定独立的持久化存储。

特性 Deployment StatefulSet
Pod命名 随机哈希 固定序号
存储绑定 共享PVC 每Pod独立PVC
启动顺序 并行 有序(0→1→2)
适用场景 Web服务、API 数据库、消息队列

如果用Deployment跑MySQL,Pod重启后PVC可能绑到别的Pod上,数据就乱了。StatefulSet保证mysql-0永远用自己的PVC。

持久化存储:PVC和StorageClass怎么配?

这是最关键的环节。Pod是临时的,数据必须是持久的。

第一步,创建StorageClass。 它告诉K8s用什么方式提供存储。我用的NFS,因为简单。生产环境建议用Ceph RBD或云盘。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: mysql-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain  # Pod删除后数据保留

第二步,StatefulSet里声明PVC模板。 每个Pod自动创建一个独立的PVC。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql
  replicas: 3  # 1主2从
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: root-password
        - name: MYSQL_REPLICATION_USER
          value: "repl"
        - name: MYSQL_REPLICATION_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: repl-password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
        resources:
          requests:
            memory: "2Gi"
            cpu: "1000m"
          limits:
            memory: "4Gi"
            cpu: "2000m"
  volumeClaimTemplates:
  - metadata:
      name: mysql-data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: mysql-storage
      resources:
        requests:
          storage: 100Gi

这里有几个容易踩的坑。

accessModes必须是ReadWriteOnce。 一块存储只能被一个Pod挂载。如果用ReadWriteMany,多个Pod可能同时写同一个数据文件,直接损坏。

reclaimPolicy设成Retain。 Pod删除后PVC保留。如果设成Delete,误删StatefulSet就等于删数据。

资源限制要设。 不设置limits,MySQL可能吃光节点内存,触发OOM Killer。

主从复制怎么在K8s上搭?

StatefulSet创建了3个Pod。但它们默认是3个独立的MySQL实例。主从复制需要额外配置。有两种方式:手动配置和Operator管理。

手动配置: 用ConfigMap注入my.cnf,init-container初始化主从关系。

# ConfigMap注入主库配置
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  primary.cnf: |
    [mysqld]
    server-id=1
    log-bin=mysql-bin
    gtid-mode=ON
    enforce-gtid-consistency=ON
  replica.cnf: |
    [mysqld]
    server-id=2
    read-only=ON
    gtid-mode=ON
    enforce-gtid-consistency=ON

然后用initContainer根据Pod序号加载不同配置。mysql-0加载primary.cnf,mysql-1和mysql-2加载replica.cnf。

这种方式能跑,但运维成本大。主库挂了要手动切。从库重建要手动配。

Operator管理: 用现成的MySQL Operator。我试了Percona的PXC Operator和Oracle的MySQL Operator。它们自动处理主从搭建、故障切换、备份恢复。

以Percona Operator为例,部署完Operator后,只需要定义一个自定义资源:

apiVersion: pxc.percona.com/v1
kind: PerconaXtraDBCluster
metadata:
  name: mysql-cluster
spec:
    pxc:
      size: 3
      image: percona/percona-xtradb-cluster:8.0
    proxysql:
      size: 2
      image: percona/percona-xtradb-cluster-operator:1.14.0-haproxy

Operator会自动创建StatefulSet、配置主从(PXC模式)、设置ProxySQL负载均衡。主库挂了自动选举新主。

性能损耗:容器到底慢多少?

这是我做之前最关心的问题。跑个测试对比一下。

# 容器内MySQL的sysbench测试
sysbench oltp_read_write \
  --mysql-host=mysql-0.mysql.default.svc.cluster.local \
  --mysql-port=3306 \
  --mysql-user=root \
  --mysql-password=xxx \
  --tables=10 \
  --table-size=100000 \
  run

# 同配置虚拟机MySQL的sysbench测试
sysbench oltp_read_write \
  --mysql-host=192.168.1.100 \
  --mysql-port=3306 \
  ...

测试结果:

指标 虚拟机 K8s容器 差异
TPS 1250 1180 -5.6%
平均延迟 8ms 8.5ms +6.3%
P99延迟 45ms 52ms +15.6%

读写性能差距在5%到15%之间。对大多数业务可以接受。P99延迟差异稍大,主要来自网络层(kube-proxy的iptables转发)。

网络性能损耗的来源。 K8s的Service通过iptables或IPVS做端口转发。每经过一层转发,增加一点延迟。如果用HostNetwork模式,Pod直接用宿主机网络,性能接近虚拟机。但牺牲了网络隔离性。

存储性能损耗的深层原因。 这不只是NFS和SSD的区别。MySQL的InnoDB在做fsync时,期望数据直接落到物理磁盘。但在K8s上,数据要经过容器存储层、CSI驱动、再到存储后端。每多一层,就多一次上下文切换和内存拷贝。我用strace追踪过MySQL的fsync调用,发现容器环境下系统调用链长了3到4层。这就是为什么即使换成Ceph RBD,P99延迟仍然比虚拟机高一点。

CPU调度的影响。 K8s的CPU limit用的是CFS quota,不是硬隔离。当节点上多个Pod争CPU时,MySQL线程可能被频繁调度切换。我观察过perf的上下文切换计数,容器环境下的voluntary context switch比虚拟机高20%。这对InnoDB的后台线程(page cleaner、log writer)影响尤其明显,因为它们对延迟敏感。

存储性能损耗的来源。 NFS存储的IO性能远低于本地SSD。生产环境建议用云盘或Ceph RBD。我测试过Ceph RBD,TPS差距缩小到2%以内。

避坑清单:部署MySQL上K8s最容易踩哪些坑?

删除StatefulSet前一定先确认PVC策略。 默认reclaimPolicy是Delete,我手贱删了一次StatefulSet,PVC跟着删了,测试库数据直接没了。改成Retain至少多一层保护,删之前先确认。生产环境绝对不能留Delete。

MySQL的内存限制要算清楚。 没设置resources.limits.memory,MySQL跑着跑着内存飙升,Linux OOM Killer把它杀了,Pod重启,无限循环。limits必须设,而且要比innodb_buffer_pool_size大至少20%。简单算法:buffer_pool +(max_connections × 2MB)。只设buffer_pool不设limits,OOM Killer随时等你。

Headless Service必须提前创建。 StatefulSet的Pod之间用域名通信,mysql-0.mysql.default.svc.cluster.local。我一开始忘了创建Headless Service,域名解析失败,从库连不上主库。StatefulSet必须配serviceName,对应的Headless Service必须在部署前建好。

# Headless Service(必须与StatefulSet的serviceName一致)
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  clusterIP: None  # Headless,不分配ClusterIP
  selector:
    app: mysql
  ports:
  - port: 3306
    name: mysql

StorageClass的选型直接决定性能上限。 我用NFS跑压测,TPS只有本地磁盘的三分之一。换成Ceph RBD后才接近虚拟机水平,差距缩小到2%以内。NFS适合测试环境,生产环境用块存储。

国产数据库容器化:能替代MySQL跑在K8s上吗?

说实话,这次上手K8s让我意识到一件事:数据库容器化不是"能不能跑"的问题,是"怎么管好"的问题。传统数据库厂商都在做容器化适配。我了解到的情况是,国产数据库对K8s的支持也在快速推进。

KingbaseES在K8s上提供了KES Operator作为核心编排工具,每个租户拥有独立的数据库实例运行在独立Pod中,实现配置、数据与权限的全隔离。这套方案定位为微服务场景,适合一机多业务——单台物理服务器上通过K8s动态调度运行多个独立的数据库实例,实现资源池共享和利用率最大化。故障时Pod可自动漂移,保障业务连续性。搭配企业级管控平台还能统一管理多个容器数据库实例。

容器化的核心价值不是性能,而是运维效率。自动部署、自动扩容、自动故障恢复,这些对中小团队吸引力很大。读从库、开发测试环境、临时数据服务,适合先上容器。但核心交易系统我仍然建议谨慎。K8s的存储和网络层引入了额外的复杂度,出问题排查链路更长,你需要同时懂数据库和K8s。一机多业务的多租户场景确实有性价比,但核心库跑在容器上,一旦存储层或网络出问题,排查成本比传统虚拟机高得多。

容器化总结:MySQL上K8s到底值不值?

把MySQL搬上K8s,核心三件事:选StatefulSet不选Deployment、配好持久化存储、用Operator管主从。性能损失在可接受范围。运维效率提升明显。但排查问题的门槛也提高了。你需要同时理解MySQL和K8s的运维逻辑。

容器化不是银弹。它改变了数据库的部署方式,但没有改变数据库的运行原理。索引该建还是要建,慢查询该调还是要调。

你的团队有把数据库跑在容器上的吗?体验怎么样?欢迎一起交流。

我是数据库小学妹,咱们下篇见 👋

相关文章
|
5天前
|
缓存 人工智能 安全
GPT-5.6 Terra与GPT-5.5性能实测:成本减半后的跑分对比与快速迁移指南
GPT-5.6 Terra 的定价为每百万 token 输入 2.50/输出 15。GPT-5.5 则是 5/ 30。Terra 的每一项费率,包括 $0.25/M 的缓存读取,都恰好是 GPT-5.5 的一半,因此在任何工作负载组合下,Terra 都固定 便宜 2.0x。以每天 10 万次请求、3K token 提示词计算,大约是 Terra 每天 2,000,GPT−5.5每天 4,000,即每月约 60,000对 120,000。问题在于:OpenAI 没有发布任何针对 Terra 的编码基准。那个著名的 91.9% Terminal-Bench 数字是 Sol 在 Ul
|
1天前
|
数据采集 机器学习/深度学习 人工智能
田间杂草定位与检测4200张YOLO智慧农业数据集分享
本数据集含4200张真实农田图像,YOLO格式,单类别(杂草)高质量标注,覆盖多作物、多光照、多生长阶段等复杂场景,专为智慧农业杂草检测与智能除草设备研发设计,支持YOLOv5/v8/v10等主流模型训练。
321 93
|
2天前
|
缓存 UED 开发者
Codex109天重置23次,明天还要再送一次
Codex近109天完成23次额度重置,7月14日将迎来第24次。Tibo高频响应用户反馈:优化GPT-5.6高消耗问题、补发失效福利、调整重置时间——形成“反馈→回应→修复→补偿”正向闭环,彰显以用户为中心的产品哲学。(239字)
452 11
|
6天前
|
人工智能 缓存 JSON
刚刚 GPT-5.6 发布,吊打 Claude 5 和 Grok 4.5?一手实测来啦!
GPT-5.6 刚发布,跑分号称超越 Fable 5,真的假的?附一手实测,让 3 个最强 AI 编程模型 GPT-5.6 Sol、Claude Fable 5、Grok 4.5 在 Cursor 里同时一把梭开发网页游戏,看看最新模型到底谁更能打。
324 0
|
5天前
|
存储 人工智能 JSON
Qwen 本地部署搭配 ComfyUI 生成 AI 漫剧完整实操指南(小白零基础可落地,零成本无限生成+角色一致性天花板)
2026全网最优本地漫剧流水线:零成本、离线运行、角色统一、低配(8G显卡)可跑。融合Qwen本地大模型+ComfyUI双引擎,实现剧本生成→分镜绘图→动态成片全自动,隐私安全、无审核限流,新手30分钟上手,日更无忧。(239字)

热门文章

最新文章