1. 为什么选择StatefulSet部署MySQL主从架构
我第一次在生产环境用Kubernetes部署MySQL集群时,踩过一个经典坑:用Deployment部署的MySQL节点重启后,数据竟然莫名其妙丢失了。后来才发现,Deployment设计的无状态特性与MySQL这种有状态服务存在根本性冲突。这就是为什么我们需要StatefulSet——它完美解决了有状态服务的三大痛点:
- 稳定的网络标识:每个Pod会固定分配形如mysql-0、mysql-1的DNS名称,就算Pod重建也不会改变。这意味着你的应用永远可以通过mysql-0.mysql访问主节点
- 有序的生命周期:StatefulSet会严格按照编号顺序启停Pod(先主后从),避免主从切换时的脑裂问题
- 持久化存储绑定:每个Pod都会绑定独立的PVC,即使Pod被调度到其他节点,数据也会跟着走
实测对比发现,用StatefulSet部署的MySQL集群,在K8s节点故障迁移场景下,数据一致性保障率从Deployment方案的不足60%提升到99.9%以上。这组数据让我彻底放弃了用Deployment强上数据库的念头。
2. 环境准备与基础配置
2.1 创建隔离的命名空间
首先我们需要给MySQL集群一个独立的运行环境:
# 01-namespace.yaml apiVersion: v1 kind: Namespace metadata: name: mysql labels: app: mysql这个操作相当于给MySQL单独划分了一个"房间",既避免了资源争抢,又方便后续监控和日志收集。我习惯用kubectl create -f 01-namespace.yaml创建后,立即执行以下命令验证:
kubectl get ns mysql --show-labels2.2 配置安全凭证
数据库密码绝对不能明文写在配置文件里!Secret对象是我们的首选方案:
# 02-secret.yaml apiVersion: v1 kind: Secret metadata: name: mysql-secret namespace: mysql type: Opaque data: password: MTIzNDU2 # echo -n "123456" | base64这里有个实用技巧:在团队协作时,建议用Kubernetes的SealedSecret或Vault等工具对Secret进行二次加密。曾经有团队因为直接提交了base64编码的Secret到代码库,导致数据库被入侵。
3. 主从配置分离策略
3.1 使用ConfigMap管理配置
主从节点的my.cnf配置差异很大,我们通过ConfigMap实现配置分离:
# 03-configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mysql namespace: mysql data: master.cnf: | [mysqld] log-bin=mysql-bin server-id=1 binlog-format=ROW slave.cnf: | [mysqld] super-read-only server-id=2这种配置方式有个隐藏优势:当需要调整binlog格式时,只需修改ConfigMap然后滚动重启Pod,完全不需要登录到容器内部操作。记得去年MySQL 5.7升级时,这个特性帮我们省去了大量重复劳动。
4. 网络服务设计要点
4.1 Headless Service的作用
这是主从架构最关键的通信基础:
# 04-service.yaml apiVersion: v1 kind: Service metadata: name: mysql namespace: mysql spec: ports: - port: 3306 clusterIP: None selector: app: mysqlHeadless Service的特殊之处在于它不会分配ClusterIP,而是直接返回Pod的DNS记录。这意味着:
- mysql-0.mysql.mysql.svc.cluster.local 永远指向主节点
- mysql-1.mysql.mysql.svc.cluster.local 永远指向第一个从节点
我在调试时最喜欢用这个命令验证DNS解析:
kubectl run -it --rm debug --image=busybox --restart=Never -- nslookup mysql.mysql4.2 读写分离服务
生产环境一定要把读写流量分开:
# 05-read-service.yaml apiVersion: v1 kind: Service metadata: name: mysql-read namespace: mysql spec: ports: - port: 3306 selector: app: mysql应用层连接时记住这个原则:写操作连mysql-0.mysql,读操作连mysql-read。这个简单的策略让我们的查询性能提升了3倍,因为请求被自动分摊到了所有从节点。
5. StatefulSet核心配置解析
5.1 初始化容器的作用
这个StatefulSet配置中最精妙的就是initContainers的设计:
# 06-statefulset.yaml片段 initContainers: - name: init-mysql image: mysql:5.7 command: - bash - "-c" - | # 根据Pod序号自动分配server-id [[ "$HOSTNAME" =~ -([0-9]+)$ ]] || exit 1 ordinal=${BASH_REMATCH[1]} echo server-id=$((100 + $ordinal)) > /mnt/conf.d/server-id.cnf # 主从配置分离 if [[ $ordinal == 0 ]]; then cp /mnt/config-map/master.cnf /mnt/conf.d/ else cp /mnt/config-map/slave.cnf /mnt/conf.d/ fi这段脚本实现了两个自动化:
- 根据Pod序号(mysql-0、mysql-1)自动生成递增的server-id
- 自动区分主从配置
曾经有团队手动维护server-id导致主从冲突,这个方案彻底解决了这个问题。
5.2 数据同步机制
从节点的数据同步通过xtrabackup容器实现:
- name: clone-mysql image: gcr.io/google-samples/xtrabackup:1.0 command: - bash - "-c" - | # 从上一个节点同步数据 ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql xtrabackup --prepare --target-dir=/var/lib/mysql这个方案比传统的mysqldump效率高得多,实测10GB数据库的从库初始化时间从2小时缩短到15分钟。要注意的是,首次启动时务必确保主节点(mysql-0)完全就绪后再启动从节点。
6. 生产环境优化建议
6.1 存储选型方案
千万不要使用emptyDir或hostPath!我们吃过亏的几种存储方案对比:
| 存储类型 | 读写性能 | 扩容便利性 | 数据安全性 | 适用场景 |
|---|---|---|---|---|
| NFS | 中 | 高 | 中 | 测试环境 |
| Ceph RBD | 高 | 中 | 高 | 生产环境 |
| Local PV | 极高 | 低 | 低 | 边缘计算 |
现在我们的标准配置是:Ceph RBD + StorageClass动态供给,配合定期快照功能。
6.2 监控与告警
这组监控指标必须配置:
- 主从延迟时间(Seconds_Behind_Master)
- 复制线程状态(Slave_IO_Running/Slave_SQL_Running)
- 连接数使用率(Threads_connected/max_connections)
推荐用这个Prometheus查询监控主从状态:
mysql_global_status_slave_sql_running == 0 OR mysql_global_status_slave_io_running == 0 OR mysql_global_status_slave_retried_transactions > 07. 故障排查实战案例
去年我们遇到过一个典型问题:从节点突然无法同步数据。通过以下步骤定位到原因:
- 检查从库状态
kubectl exec mysql-1 -n mysql -- mysql -uroot -p123456 -e "SHOW SLAVE STATUS\G"发现错误消息提示无法连接主库3306端口
检查网络策略发现新部署的NetworkPolicy误拦截了mysql命名空间的流量
这个案例教会我们:在K8s环境排查数据库问题,一定要从Pod网络、存储、资源配置三个维度综合分析。