K8S部署MySQL主从复制实现高可用数据库
一、创建MySQL
1.编写YAML文件
yaml
#mysql.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
ports:
- port: 3306
targetPort: 3306
selector:
app: mysql
clusterIP: None # Headless
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: default
spec:
serviceName: mysql
replicas: 2 #2个pod(mysql-0主库、mysql-1从库)
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
args:
- mysqld
- --server-id=1 # 主库固定ID
- --log-bin=mysql-bin # 开启binlog日志,主从复制核心依赖
- --binlog-format=ROW # 禁用域名解析,加快连接、减少报错
- --skip-name-resolve
env:
- name: MYSQL_ROOT_PASSWORD
value: "hyz123!@#" # 环境变量:设置MySQL root密码
ports:
- containerPort: 3306
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
# 自动给每个 Pod 创建独立 PVC
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: ["ReadWriteOnce"] # 读写单次挂载模式
resources:
requests:
storage: 5Gi # 每个节点分配5Gi存储空间
2.创建StatefulSet
bash
kubectl apply -f mysql.yaml
3.验证是否创建成功
bash
kubectl get pods
kubectl get pvc
kubectl get svc


- 可以看到Pod名字是固定名字,不是随机字符串!
- PVC状态处于Bound状态
- Service的
ClusterIP为Node
4.验证数据库是否能登录
bash
kubectl exec -it mysql-0 -- mysql -uroot -p'hyz123!@#'

二、搭建主从复制
1.进入主库 mysql-0
bash
kubectl exec -it mysql-0 -- mysql -uroot -p'hyz123!@#'
2.创建复制账号
mysql
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
bash
kubectl exec -it mysql-1 -- mysql -uroot -p'hyz123!@#'
4.建立主从复制关系
mysql
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.验证状态
mysql
SHOW SLAVE STATUS\G
成功标准✅ :
mysql
#你必须看到这两个都是YES
Slave_IO_Running: Yes
Slave_SQL_Running: Yes

6.配置从库只读,不允许写入数据
MySQL
SET GLOBAL read_only = ON;
SET GLOBAL super_read_only = ON;
三、测试
1.主库创建数据库,看从库有没有同步
①进入主库执行
mysql
create database test_final;
②进入从库执行
mysql
show databases;

2.测试从库只读功能
①主库mysql-0 执行
mysql
--选库
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 执行
mysql
use test_final;
SELECT * FROM user;
可以正常查询到数据

尝试写入数据
mysql
insert into user(name) values('test123');
返回结果:从库无法写入,只有只读权限
MySQL
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 里的,非常不安全!
yaml
env:
- name: MYSQL_ROOT_PASSWORD
value: "hyz123!@#" # 环境变量:设置MySQL root密码
我们今天改成 K8s 生产标准用法:
-
密码 → Secret
-
配置 → ConfigMap
①创建MySQL密码Secret
bash
kubectl create secret generic mysql-secret \
--from-literal=root-password=hyz123!@#
查看是否创建成功:
bash
kubectl get secret
看到 mysql-secret 就成功 ✅
②修改YAML文件
yaml
# ==============================
# MySQL 无头 Service
# ==============================
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
ports:
- port: 3306
targetPort: 3306
selector:
app: mysql
clusterIP: None # 无头服务:不分配ClusterIP,直接暴露Pod IP,适用于有状态应用
# ==============================
# MySQL 配置文件(ConfigMap)
# 作用:存放 my.cnf 配置,不写在容器里
# ==============================
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
my.cnf: |
[mysqld]
log-bin=mysql-bin # 启用二进制日志,用于主从复制和数据恢复
binlog-format=ROW # 设置二进制日志格式为行模式,更安全精确
skip-name-resolve # 跳过DNS解析,提高连接速度和稳定性
# ==============================
# MySQL StatefulSet
# 密码从 Secret 取
# 配置从 ConfigMap 取
# ==============================
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: default
spec:
serviceName: mysql # 关联上面定义的Service,用于Pod网络标识
replicas: 2 # 部署2个MySQL实例,实现主从架构
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0 # 使用MySQL 8.0官方镜像
# 从 ConfigMap 加载配置
volumeMounts:
- name: mysql-config
mountPath: /etc/mysql/conf.d/my.cnf # 将ConfigMap挂载为配置文件
subPath: my.cnf # 只挂载特定文件,避免覆盖整个目录
- name: mysql-data
mountPath: /var/lib/mysql # 挂载数据卷,持久化MySQL数据
# 环境变量:密码从 Secret 取(不硬编码)
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret # 从名为mysql-secret的Secret获取
key: root-password # 获取root-password键的值
ports:
- containerPort: 3306 # 暴露MySQL默认端口
# 挂载 ConfigMap
volumes:
- name: mysql-config
configMap:
name: mysql-config # 引用上面定义的ConfigMap
# 自动独立PVC
volumeClaimTemplates:
- metadata:
name: mysql-data # 与volumeMounts中定义的名称一致
spec:
accessModes: ["ReadWriteOnce"] # 单节点读写访问模式
resources:
requests:
storage: 5Gi # 每个实例分配5GB存储空间
③滚动更新MySQL
bash
kubectl apply -f mysql.yaml
④验证是否生效
bash
kubectl get pods
等 mysql-0 mysql-1 都 Running 就成功了 ✅
2.配置健康检查(探针)
①K8s 三种探针对比
| 探针类型 | 核心作用 | 检测失败处理 | 典型场景 | 关键配置要点 |
|---|---|---|---|---|
| LivenessProbe | 检测容器是否卡死/僵死/进程挂掉 | 直接重启Pod | MySQL卡死、Java假死、进程崩溃但未退出 | 需设置合理initialDelaySeconds避免误杀 |
| ReadinessProbe | 检测容器是否完全启动并能提供服务 | 停止转发流量,不重启 | MySQL启动慢、Java加载资源中、服务未就绪 | failureThreshold需高于启动时间 |
| StartupProbe | 专为启动超慢服务设计的临时探针 | 启动阶段失败会重启,启动完成后失效 | 大内存Java应用、数据库初始化、冷启动耗时服务 | 必须配合livenessProbe使用,设置足够timeout |
②修改YAML文件
MySQL 官方容器标配:mysqladmin ping (就是指用 root 用户 + 密码,执行 mysqladmin ping能通→健康,不通→不健康)
yaml
# 无头Service
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
ports:
- port: 3306
targetPort: 3306
selector:
app: mysql
clusterIP: None
---
# 配置存放:ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
my.cnf: |
[mysqld]
server-id=1
log-bin=mysql-bin
binlog-format=ROW
skip-name-resolve
---
# MySQL 有状态服务 + 健康探针
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: default
spec:
serviceName: mysql
replicas: 2
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
# 密码从Secret读取,不硬编码
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
# 挂载配置文件 + 数据卷
volumeMounts:
- name: mysql-config
mountPath: /etc/mysql/conf.d/
- name: mysql-data
mountPath: /var/lib/mysql
# ========== 存活探针:检测MySQL是否卡死 ==========
livenessProbe:
exec:
command: #mysqladmin ping
- mysqladmin
- -uroot
- -p$(MYSQL_ROOT_PASSWORD)
- ping
initialDelaySeconds: 30 # 容器启动30秒后开始检测
periodSeconds: 10 # 每10秒检测一次
timeoutSeconds: 5 # 单次检测超时5秒
failureThreshold: 3 # 失败3次就重启Pod
# ========== 就绪探针:检测MySQL是否可正常连接 ==========
readinessProbe:
exec:
command: #mysqladmin ping
- mysqladmin
- -uroot
- -p$(MYSQL_ROOT_PASSWORD)
- ping
initialDelaySeconds: 15 # 启动15秒后开始
periodSeconds: 5 # 每5秒检测
timeoutSeconds: 3
failureThreshold: 2
volumes:
- name: mysql-config
configMap:
name: mysql-config
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
③部署生效
bash
kubectl apply -f mysql.yaml
等待 Pod 重建完成:
bash
kubectl get pods -w
④验证探针是否生效
查看 Pod 详细信息(看探针配置)
bsah
kubectl describe pod mysql-0
看到liveness和readiness代表配置已经注入!

⑤制造人为故障测试探针
我们删除主库的mysqld.sock文件,让数据库连接失败
bash
kubectl exec mysql-0 -- rm -rf /var/run/mysqld/mysqld.sock
另开一个终端,观察探针 + 自愈的全过程
bash
kubectl get pod -w

这说明我们配置探针!这就是生产级健康检查的真正威力:服务异常 → 探针自动发现 → 自动重启恢复 → 全程无需人工干预
3.配置容器资源限制( CPU / 内存)
为什么要加资源限制?
- requests(请求):告诉 K8s "我至少需要多少资源",用于调度和保证基础性能。
- limits(限制):防止 MySQL/Java 把整个节点的 CPU、内存吃爆,影响其他服务。
①修改YAML文件
yaml
# 无头 Service:给 StatefulSet 提供稳定域名
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
ports:
- port: 3306
targetPort: 3306
selector:
app: mysql
clusterIP: None
---
# ConfigMap:存放非敏感配置
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
my.cnf: |
[mysqld]
server-id=1
log-bin=mysql-bin
binlog-format=ROW
skip-name-resolve
---
# StatefulSet:MySQL 有状态服务 + 资源限制 + 探针
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: default
spec:
serviceName: mysql
replicas: 2
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
# 资源限制:防止吃爆服务器
resources:
requests:
cpu: "100m" # 至少 0.1 核 CPU
memory: "256Mi" # 至少 256MB 内存
limits:
cpu: "500m" # 最多 0.5 核 CPU
memory: "512Mi" # 最多 512MB 内存
# 密码从 Secret 安全读取
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
# 挂载配置文件 + 数据卷
volumeMounts:
- name: mysql-config
mountPath: /etc/mysql/conf.d/
- name: mysql-data
mountPath: /var/lib/mysql
# 存活探针:服务挂了自动重启
livenessProbe:
exec:
command:
- mysqladmin
- -uroot
- -p$(MYSQL_ROOT_PASSWORD)
- ping
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# 就绪探针:没准备好就不给流量
readinessProbe:
exec:
command:
- mysqladmin
- -uroot
- -p$(MYSQL_ROOT_PASSWORD)
- ping
initialDelaySeconds: 15
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 2
volumes:
- name: mysql-config
configMap:
name: mysql-config
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
②部署生效
bash
kubectl apply -f mysql.yaml
然后等 Pod 重建完成:
bash
kubectl get pods -w
③验证资源限制是否生效
bash
kubectl describe pod mysql-0

这里可以看到资源控制已经生效了,防止服务把服务器吃爆!
4.MySQL主从手动故障切换
场景模拟:
- 原主库:原主库 mysql-0 彻底挂掉(宕机、断网、损坏、无法修复、连不上)
- 原从库:
mysql-1→ 升级为新主库
①登录从库(即将升为新主库)
bash
kubectl exec -it mysql-1 -- mysql -uroot -p
②停止从库复制(关键)
MySQL8.0 必须关两个:
mysql
SET GLOBAL read_only = OFF;
SET GLOBAL super_read_only = OFF;
③清除复制信息,独立成一台全新主库
mysql
RESET MASTER;
作用:清空旧主库的 binlog 位点、复制账号信息,彻底和旧主切割。
④确认当前新主库状态
mysql
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 修好、启动正常后,不能直接当主库用,要改成新从库:
⑦登录旧主库
bash
kubectl exec -it mysql-0 -- mysql -uroot -p'hyz123!@#'
⑧清空旧复制配置
mysql
STOP SLAVE;
RESET MASTER;
⑨查看新主库 mysql-1的 binlog 点位
去 mysql-1 执行:
mysql
SHOW MASTER STATUS;
拿到:File、Position

⑩旧主库反向指向新主库
mysql
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;
开启同步+只读
mysql
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 / 内存使用上限,防止单个服务吃爆服务器资源,保障集群稳定。 |
| 主从故障手动切换 | 手动提升从库为新主库 + 业务流量切换 | 当原主库故障时,将从库提升为新主库,修改业务连接地址,快速恢复写入能力,实现业务持续可用。 |