K8S部署MySQL主从复制实现高可用数据库

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的ClusterIPNode
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

看到livenessreadiness代表配置已经注入!

⑤制造人为故障测试探针

我们删除主库的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.yamlapplication.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;

拿到:FilePosition

⑩旧主库反向指向新主库
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 / 内存使用上限,防止单个服务吃爆服务器资源,保障集群稳定。
主从故障手动切换 手动提升从库为新主库 + 业务流量切换 当原主库故障时,将从库提升为新主库,修改业务连接地址,快速恢复写入能力,实现业务持续可用。
相关推荐
zhaoshuzhaoshu2 小时前
系统架构设计师考试 — UML建模知识点与考点总结
系统架构
池佳齐2 小时前
软考高级系统架构设计师备考(二十七):软件工程—系统运行与软件维护
数据库·系统架构
海兰4 小时前
Elastic 基于 Agentic 架构与 MCP 的 Kubernetes 智能可观测性深度解析
elasticsearch·容器·架构·kubernetes
zxrhhm10 小时前
MySQL 8.4 LTS 数据库巡检脚本
数据库·mysql
雨奔10 小时前
Kubernetes DNS 完全指南:服务发现核心机制与实践
java·kubernetes·服务发现
米高梅狮子11 小时前
05.Kubernetes Volume和Kubernetes ConfigMap
云原生·容器·kubernetes
oldking呐呐13 小时前
MySQL从入门到入土 -- 2.数据库基础
后端·mysql
MAVER1CK17 小时前
Install VNC in Docker container
运维·docker·容器
Bert.Cai17 小时前
MySQL TRIM()函数详解
数据库·mysql