K8S部署MySQL主从复制实现高可用数据库
K8S部署MySQL主从复制实现高可用数据库
一、创建MySQL
1.编写YAML文件
#mysql.yamlapiVersion:v1kind:Servicemetadata:name:mysqllabels:app:mysqlspec:ports:-port:3306targetPort:3306selector:app:mysqlclusterIP:None# Headless---apiVersion:apps/v1kind:StatefulSetmetadata:name:mysqlnamespace:defaultspec:serviceName:mysqlreplicas:2#2个pod(mysql-0主库、mysql-1从库)selector:matchLabels:app:mysqltemplate:metadata:labels:app:mysqlspec:containers:-name:mysqlimage:mysql:8.0args:-mysqld---server-id=1# 主库固定ID---log-bin=mysql-bin# 开启binlog日志,主从复制核心依赖---binlog-format=ROW# 禁用域名解析,加快连接、减少报错---skip-name-resolveenv:-name:MYSQL_ROOT_PASSWORDvalue:"hyz123!@#"# 环境变量:设置MySQL root密码ports:-containerPort:3306volumeMounts:-name:mysql-datamountPath:/var/lib/mysql# 自动给每个 Pod 创建独立 PVCvolumeClaimTemplates:-metadata:name:mysql-dataspec:accessModes:["ReadWriteOnce"]# 读写单次挂载模式resources:requests:storage:5Gi# 每个节点分配5Gi存储空间2.创建StatefulSet
kubectl apply-fmysql.yaml3.验证是否创建成功
kubectl get pods kubectl get pvc kubectl get svc- 可以看到Pod名字是固定名字,不是随机字符串!
- PVC状态处于Bound状态
- Service的
ClusterIP为Node
4.验证数据库是否能登录
kubectlexec-itmysql-0 -- mysql-uroot-p'hyz123!@#'二、搭建主从复制
1.进入主库 mysql-0
kubectlexec-itmysql-0 -- mysql-uroot-p'hyz123!@#'2.创建复制账号
CREATE USER 'repl'@'%' IDENTIFIED BY 'Repl@123456'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%'; FLUSH PRIVILEGES; SHOW MASTER STATUS;执行完看到类似结果,记下来 2 个值
File :mysql-bin.000001
Position: 1171
SHOW MASTER STATUS; +------------------+----------+--------------+------------------+-------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +------------------+----------+--------------+------------------+-------------------+ | mysql-bin.000001 | 1171 | | | | +------------------+----------+--------------+------------------+-------------------+ 1 row in set (0.00 sec)3.进入从库mysql-1
kubectlexec-itmysql-1 -- mysql-uroot-p'hyz123!@#'4.建立主从复制关系
STOP SLAVE; CHANGE MASTER TO MASTER_HOST='mysql-0.mysql.default.svc.cluster.local', MASTER_PORT=3306, MASTER_USER='repl', MASTER_PASSWORD='Repl@123456', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=1171; GET_MASTER_PUBLIC_KEY=1; START SLAVE;参数说明:
MASTER_HOST='mysql-0.mysql.default.svc.cluster.local'
指定主库的主机名或IP地址,在Kubernetes环境中通常使用服务名作为主机名MASTER_PORT=3306
指定主库MySQL服务的端口号(默认3306)MASTER_USER='repl'
指定用于复制的专用用户名(应为具有REPLICATION SLAVE权限的账号)MASTER_PASSWORD='Repl@123456'
指定复制账号的密码(注意:密码中特殊字符需正确转义)MASTER_LOG_FILE='mysql-bin.000001'
指定主库当前二进制日志文件名,必须与主库SHOW MASTER STATUS输出一致MASTER_LOG_POS=1171
指定主库二进制日志的起始位置,必须与主库SHOW MASTER STATUS输出一致GET_MASTER_PUBLIC_KEY=1
启用SSL加密连接,使从库获取主库的公钥用于安全通信(若主库配置了SSL)
5.验证状态
SHOW SLAVE STATUS\G成功标准✅ :
#你必须看到这两个都是YES Slave_IO_Running: Yes Slave_SQL_Running: Yes6.配置从库只读,不允许写入数据
SET GLOBAL read_only = ON; SET GLOBAL super_read_only = ON;三、测试
1.主库创建数据库,看从库有没有同步
①进入主库执行
create database test_final;②进入从库执行
show databases;2.测试从库只读功能
①主库mysql-0 执行
--选库 use test_final; -- 建表 CREATE TABLE IF NOT EXISTS user ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) NOT NULL ); -- 插入4条数据 INSERT INTO user(name) VALUES('zhangsan'),('lisi'),('wangwu'),('test'); -- 查询看结果 SELECT * FROM user;②然后去 从库 mysql-1 执行
use test_final; SELECT * FROM user;可以正常查询到数据
尝试写入数据
insert into user(name) values('test123');返回结果:从库无法写入,只有只读权限
ERROR 1290 (HY000): The MySQL server is running with the --super-read-only option so it cannot execute this statement四、进阶
1.配置抽离:ConfigMap/Secret
现在 MySQL 密码是直接写死在 YAML 里的,非常不安全!
env:-name:MYSQL_ROOT_PASSWORDvalue:"hyz123!@#"# 环境变量:设置MySQL root密码我们今天改成K8s 生产标准用法:
密码 → Secret
配置 → ConfigMap
①创建MySQL密码Secret
kubectl create secret generic mysql-secret\--from-literal=root-password=hyz123!@#查看是否创建成功:
kubectl get secret看到mysql-secret就成功 ✅
②修改YAML文件
# ==============================# MySQL 无头 Service# ==============================apiVersion:v1kind:Servicemetadata:name:mysqllabels:app:mysqlspec:ports:-port:3306targetPort:3306selector:app:mysqlclusterIP:None# 无头服务:不分配ClusterIP,直接暴露Pod IP,适用于有状态应用# ==============================# MySQL 配置文件(ConfigMap)# 作用:存放 my.cnf 配置,不写在容器里# ==============================apiVersion:v1kind:ConfigMapmetadata:name:mysql-configdata:my.cnf:|[mysqld] log-bin=mysql-bin # 启用二进制日志,用于主从复制和数据恢复 binlog-format=ROW # 设置二进制日志格式为行模式,更安全精确 skip-name-resolve # 跳过DNS解析,提高连接速度和稳定性# ==============================# MySQL StatefulSet# 密码从 Secret 取# 配置从 ConfigMap 取# ==============================apiVersion:apps/v1kind:StatefulSetmetadata:name:mysqlnamespace:defaultspec:serviceName:mysql# 关联上面定义的Service,用于Pod网络标识replicas:2# 部署2个MySQL实例,实现主从架构selector:matchLabels:app:mysqltemplate:metadata:labels:app:mysqlspec:containers:-name:mysqlimage:mysql:8.0# 使用MySQL 8.0官方镜像# 从 ConfigMap 加载配置volumeMounts:-name:mysql-configmountPath:/etc/mysql/conf.d/my.cnf# 将ConfigMap挂载为配置文件subPath:my.cnf# 只挂载特定文件,避免覆盖整个目录-name:mysql-datamountPath:/var/lib/mysql# 挂载数据卷,持久化MySQL数据# 环境变量:密码从 Secret 取(不硬编码)env:-name:MYSQL_ROOT_PASSWORDvalueFrom:secretKeyRef:name:mysql-secret# 从名为mysql-secret的Secret获取key:root-password# 获取root-password键的值ports:-containerPort:3306# 暴露MySQL默认端口# 挂载 ConfigMapvolumes:-name:mysql-configconfigMap:name:mysql-config# 引用上面定义的ConfigMap# 自动独立PVCvolumeClaimTemplates:-metadata:name:mysql-data# 与volumeMounts中定义的名称一致spec:accessModes:["ReadWriteOnce"]# 单节点读写访问模式resources:requests:storage:5Gi# 每个实例分配5GB存储空间③滚动更新MySQL
kubectl apply-fmysql.yaml④验证是否生效
kubectl get pods等mysql-0mysql-1都 Running 就成功了 ✅
2.配置健康检查(探针)
①K8s 三种探针对比
| 探针类型 | 核心作用 | 检测失败处理 | 典型场景 | 关键配置要点 |
|---|---|---|---|---|
| LivenessProbe | 检测容器是否卡死/僵死/进程挂掉 | 直接重启Pod | MySQL卡死、Java假死、进程崩溃但未退出 | 需设置合理initialDelaySeconds避免误杀 |
| ReadinessProbe | 检测容器是否完全启动并能提供服务 | 停止转发流量,不重启 | MySQL启动慢、Java加载资源中、服务未就绪 | failureThreshold需高于启动时间 |
| StartupProbe | 专为启动超慢服务设计的临时探针 | 启动阶段失败会重启,启动完成后失效 | 大内存Java应用、数据库初始化、冷启动耗时服务 | 必须配合livenessProbe使用,设置足够timeout |
②修改YAML文件
MySQL 官方容器标配:mysqladmin ping(就是指用 root 用户 + 密码,执行mysqladmin ping能通→健康,不通→不健康)
# 无头ServiceapiVersion:v1kind:Servicemetadata:name:mysqllabels:app:mysqlspec:ports:-port:3306targetPort:3306selector:app:mysqlclusterIP:None---# 配置存放:ConfigMapapiVersion:v1kind:ConfigMapmetadata:name:mysql-configdata:my.cnf:|[mysqld] server-id=1 log-bin=mysql-bin binlog-format=ROW skip-name-resolve---# MySQL 有状态服务 + 健康探针apiVersion:apps/v1kind:StatefulSetmetadata:name:mysqlnamespace:defaultspec:serviceName:mysqlreplicas:2selector:matchLabels:app:mysqltemplate:metadata:labels:app:mysqlspec:containers:-name:mysqlimage:mysql:8.0ports:-containerPort:3306# 密码从Secret读取,不硬编码env:-name:MYSQL_ROOT_PASSWORDvalueFrom:secretKeyRef:name:mysql-secretkey:root-password# 挂载配置文件 + 数据卷volumeMounts:-name:mysql-configmountPath:/etc/mysql/conf.d/-name:mysql-datamountPath:/var/lib/mysql# ========== 存活探针:检测MySQL是否卡死 ==========livenessProbe:exec:command:#mysqladmin ping-mysqladmin--uroot--p$(MYSQL_ROOT_PASSWORD)-pinginitialDelaySeconds:30# 容器启动30秒后开始检测periodSeconds:10# 每10秒检测一次timeoutSeconds:5# 单次检测超时5秒failureThreshold:3# 失败3次就重启Pod# ========== 就绪探针:检测MySQL是否可正常连接 ==========readinessProbe:exec:command:#mysqladmin ping-mysqladmin--uroot--p$(MYSQL_ROOT_PASSWORD)-pinginitialDelaySeconds:15# 启动15秒后开始periodSeconds:5# 每5秒检测timeoutSeconds:3failureThreshold:2volumes:-name:mysql-configconfigMap:name:mysql-configvolumeClaimTemplates:-metadata:name:mysql-dataspec:accessModes:["ReadWriteOnce"]resources:requests:storage:5Gi③部署生效
kubectl apply-fmysql.yaml等待 Pod 重建完成:
kubectl get pods-w④验证探针是否生效
查看 Pod 详细信息(看探针配置)
kubectl describe pod mysql-0看到liveness和readiness代表配置已经注入!
⑤制造人为故障测试探针
我们删除主库的mysqld.sock文件,让数据库连接失败
kubectlexecmysql-0 --rm-rf/var/run/mysqld/mysqld.sock另开一个终端,观察探针 + 自愈的全过程
kubectl get pod-w这说明我们配置探针!这就是生产级健康检查的真正威力:服务异常 → 探针自动发现 → 自动重启恢复 → 全程无需人工干预
3.配置容器资源限制( CPU / 内存)
为什么要加资源限制?
- requests(请求):告诉 K8s “我至少需要多少资源”,用于调度和保证基础性能。
- limits(限制):防止 MySQL/Java 把整个节点的 CPU、内存吃爆,影响其他服务。
①修改YAML文件
# 无头 Service:给 StatefulSet 提供稳定域名apiVersion:v1kind:Servicemetadata:name:mysqllabels:app:mysqlspec:ports:-port:3306targetPort:3306selector:app:mysqlclusterIP:None---# ConfigMap:存放非敏感配置apiVersion:v1kind:ConfigMapmetadata:name:mysql-configdata:my.cnf:|[mysqld] server-id=1 log-bin=mysql-bin binlog-format=ROW skip-name-resolve---# StatefulSet:MySQL 有状态服务 + 资源限制 + 探针apiVersion:apps/v1kind:StatefulSetmetadata:name:mysqlnamespace:defaultspec:serviceName:mysqlreplicas:2selector:matchLabels:app:mysqltemplate:metadata:labels:app:mysqlspec:containers:-name:mysqlimage:mysql:8.0ports:-containerPort:3306# 资源限制:防止吃爆服务器resources:requests:cpu:"100m"# 至少 0.1 核 CPUmemory:"256Mi"# 至少 256MB 内存limits:cpu:"500m"# 最多 0.5 核 CPUmemory:"512Mi"# 最多 512MB 内存# 密码从 Secret 安全读取env:-name:MYSQL_ROOT_PASSWORDvalueFrom:secretKeyRef:name:mysql-secretkey:root-password# 挂载配置文件 + 数据卷volumeMounts:-name:mysql-configmountPath:/etc/mysql/conf.d/-name:mysql-datamountPath:/var/lib/mysql# 存活探针:服务挂了自动重启livenessProbe:exec:command:-mysqladmin--uroot--p$(MYSQL_ROOT_PASSWORD)-pinginitialDelaySeconds:30periodSeconds:10timeoutSeconds:5failureThreshold:3# 就绪探针:没准备好就不给流量readinessProbe:exec:command:-mysqladmin--uroot--p$(MYSQL_ROOT_PASSWORD)-pinginitialDelaySeconds:15periodSeconds:5timeoutSeconds:3failureThreshold:2volumes:-name:mysql-configconfigMap:name:mysql-configvolumeClaimTemplates:-metadata:name:mysql-dataspec:accessModes:["ReadWriteOnce"]resources:requests:storage:5Gi②部署生效
kubectl apply-fmysql.yaml然后等 Pod 重建完成:
kubectl get pods-w③验证资源限制是否生效
kubectl describe pod mysql-0这里可以看到资源控制已经生效了,防止服务把服务器吃爆!
4.MySQL主从手动故障切换
场景模拟:
- 原主库:原主库 mysql-0 彻底挂掉(宕机、断网、损坏、无法修复、连不上)
- 原从库:
mysql-1→ 升级为新主库
①登录从库(即将升为新主库)
kubectlexec-itmysql-1 -- mysql-uroot-p②停止从库复制(关键)
MySQL8.0 必须关两个:
SET GLOBAL read_only = OFF; SET GLOBAL super_read_only = OFF;③清除复制信息,独立成一台全新主库
RESET MASTER;作用:清空旧主库的 binlog 位点、复制账号信息,彻底和旧主切割。
④确认当前新主库状态
SHOW MASTER STATUS;此时mysql-1已经是完全独立可写的新主库。
⑤业务层切换
把所有项目、配置、连接串,比如Java项目:delopment.yaml或application.properties
- 旧地址(废弃):
mysql-0.mysql.default.svc - 新地址(使用):
mysql-1.mysql.default.svc
application.properties示例(主库挂了,必须强制写死新主库:):
delopment.yaml示例(主库挂了,必须强制写死新主库:):
改完重新发布一下 Java 服务,就切换完了!
⑥旧主库恢复完毕后,设置成新从库
后期mysql-0修好、启动正常后,不能直接当主库用,要改成新从库:
⑦登录旧主库
kubectlexec-itmysql-0 -- mysql-uroot-p'hyz123!@#'⑧清空旧复制配置
STOP SLAVE; RESET MASTER;⑨查看新主库mysql-1的 binlog 点位
去 mysql-1 执行:
SHOW MASTER STATUS;拿到:File、Position
⑩旧主库反向指向新主库
CHANGE MASTER TO MASTER_HOST='mysql-1.mysql.default.svc.cluster.local', MASTER_USER='slave', MASTER_PASSWORD='hyz123!@#', MASTER_LOG_FILE='mysql-bin.000012', MASTER_LOG_POS=157, GET_MASTER_PUBLIC_KEY=1;开启同步+只读
START SLAVE; SET GLOBAL read_only = ON; SET GLOBAL super_read_only = ON;至此:
新主:
mysql-1新从:
mysql-0主从架构反向重建完成。
五、总结
通过以上配置,MySQL 现在拥有了企业生产级标准!
| 功能模块 | 具体实现 | 作用说明 |
|---|---|---|
| 有状态服务部署 | StatefulSet + Headless Service | 为 MySQL 提供稳定的网络标识和有序部署,是数据库这类有状态应用的标准部署方式。 |
| 配置解耦 | ConfigMap | 将 my.cnf 等配置文件从镜像中剥离,方便统一管理和修改,无需重新构建镜像。 |
| 敏感数据安全 | Secret | 安全存储 MySQL root 密码,避免明文泄露,通过环境变量注入容器。 |
| 高可用主从复制 | 一主一从架构 | 实现数据备份和读写分离,提升数据库的可用性和读性能。 |
| 从库只读保护 | read_only=ON 配置 | 防止主从数据不一致,保证从库数据安全。 |
| 自动故障自愈 | LivenessProbe(存活探针) | 使用mysqladmin ping检测服务是否卡死 / 崩溃,失败则自动重启容器,实现故障自愈。 |
| 流量保护 | ReadinessProbe(就绪探针) | 使用mysqladmin ping检测服务是否准备就绪,失败则标记为未就绪,不接收业务流量。 |
| 资源隔离与控制 | resources.requests/limits | 限制 MySQL 的 CPU / 内存使用上限,防止单个服务吃爆服务器资源,保障集群稳定。 |
| 主从故障手动切换 | 手动提升从库为新主库 + 业务流量切换 | 当原主库故障时,将从库提升为新主库,修改业务连接地址,快速恢复写入能力,实现业务持续可用。 |
