Kubernetes实战:使用StatefulSet部署高可用MySQL集群

Kubernetes实战:使用StatefulSet部署高可用MySQL集群

website Kubernetes website and documentation repo: website 项目地址: https://gitcode.com/gh_mirrors/webs/website

概述

在Kubernetes中部署有状态应用一直是个挑战,特别是像MySQL这样的数据库系统。本文将详细介绍如何使用Kubernetes的StatefulSet控制器来部署一个高可用的MySQL集群,包含一个主节点和多个副本节点,采用基于行的异步复制机制。

请注意,本文展示的配置不适合生产环境,MySQL设置保留了不安全的默认值,目的是聚焦于在Kubernetes中运行有状态应用的通用模式。

前置条件

在开始之前,请确保您具备以下条件:

  1. 已经安装并配置好Kubernetes集群
  2. 集群已配置默认的StorageClass
  3. 熟悉Kubernetes核心概念,包括PersistentVolumes、StatefulSets、Pods、Services和ConfigMaps
  4. 了解MySQL基础知识会有帮助,但不是必须的
  5. 使用默认命名空间或没有冲突对象的其他命名空间
  6. 使用AMD64兼容的CPU

部署架构

我们将部署的MySQL集群包含以下组件:

  1. ConfigMap:存储MySQL配置
  2. 两个Service
    • 一个无头(Headless)Service用于Pod间直接通信
    • 一个普通Service用于读请求负载均衡
  3. StatefulSet:管理MySQL Pod的生命周期

详细部署步骤

1. 创建ConfigMap

首先创建ConfigMap来存储MySQL配置:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
data:
  primary.cnf: |
    [mysqld]
    log-bin
  replica.cnf: |
    [mysqld]
    super-read-only

这个ConfigMap为主节点和副本节点提供了不同的配置:

  • 主节点启用二进制日志(binlog)
  • 副本节点设置为只读模式

2. 创建Service

接下来创建两个Service:

apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  clusterIP: None
  selector:
    app: mysql
---
apiVersion: v1
kind: Service
metadata:
  name: mysql-read
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  selector:
    app: mysql
  • mysql是无头Service,为每个Pod提供稳定的DNS名称
  • mysql-read是普通Service,为读请求提供负载均衡

3. 创建StatefulSet

最后创建StatefulSet来管理MySQL Pod:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql
  replicas: 3
  template:
    metadata:
      labels:
        app: mysql
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:5.7
        command:
        - bash
        - "-c"
        - |
          set -ex
          # 基于Pod序号生成server-id
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          echo [mysqld] > /mnt/conf.d/server-id.cnf
          echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
          # 将适当的conf.d文件从config-map复制到emptyDir
          if [[ $ordinal -eq 0 ]]; then
            cp /mnt/config-map/primary.cnf /mnt/conf.d/
          else
            cp /mnt/config-map/replica.cnf /mnt/conf.d/
          fi
        volumeMounts:
        - name: conf
          mountPath: /mnt/conf.d
        - name: config-map
          mountPath: /mnt/config-map
      - name: clone-mysql
        image: gcr.io/google-samples/xtrabackup:1.0
        command:
        - bash
        - "-c"
        - |
          set -ex
          # 如果数据已经存在,跳过克隆
          [[ -d /var/lib/mysql/mysql ]] && exit 0
          # 跳过主实例(序号0)的克隆
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          [[ $ordinal -eq 0 ]] && exit 0
          # 从序号减1的Pod克隆数据
          ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
          # 准备备份
          xtrabackup --prepare --target-dir=/var/lib/mysql
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
      containers:
      - name: mysql
        image: mysql:5.7
        env:
        - name: MYSQL_ALLOW_EMPTY_PASSWORD
          value: "1"
        ports:
        - name: mysql
          containerPort: 3306
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: 500m
            memory: 1Gi
        livenessProbe:
          exec:
            command: ["mysqladmin", "ping"]
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
        readinessProbe:
          exec:
            command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
          initialDelaySeconds: 5
          periodSeconds: 2
          timeoutSeconds: 1
      - name: xtrabackup
        image: gcr.io/google-samples/xtrabackup:1.0
        ports:
        - name: xtrabackup
          containerPort: 3307
        command:
        - bash
        - "-c"
        - |
          set -ex
          cd /var/lib/mysql
          # 确定主服务器是否已经有数据
          if [[ -f xtrabackup_slave_info ]]; then
            # 如果已经配置为副本,则应用备份
            xtrabackup --prepare --target-dir=/var/lib/mysql
            # 启动复制
            mv xtrabackup_slave_info change_master_to.sql.in
            rm -f xtrabackup_binlog_info
          elif [[ -f xtrabackup_binlog_info ]]; then
            # 如果这是主服务器,只记住binlog位置
            [[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
            rm -f xtrabackup_binlog_info xtrabackup_slave_info
            echo "SET GLOBAL gtid_purged='$2';" > change_master_to.sql.in
            echo "CHANGE MASTER TO MASTER_LOG_FILE='$1', MASTER_LOG_POS=$2;" >> change_master_to.sql.in
          fi
          # 检查是否需要初始化MySQL
          if [[ ! -d /var/lib/mysql/mysql ]]; then
            mysql_install_db --user=mysql --datadir=/var/lib/mysql
          fi
          # 启动临时服务器
          mysqld --datadir=/var/lib/mysql --socket=/var/run/mysqld/mysqld.sock \
            --port=3306 --log-error=/var/log/mysql/error.log \
            --pid-file=/var/run/mysqld/mysqld.pid \
            --daemonize
          # 等待MySQL启动
          for i in {1..30}; do
            mysql -S /var/run/mysqld/mysqld.sock -e "SELECT 1" && break
            sleep 1
          done
          # 设置复制
          if [[ -f change_master_to.sql.in ]]; then
            mysql -S /var/run/mysqld/mysqld.sock -e "$(cat change_master_to.sql.in)"
          fi
          # 停止临时服务器
          mysqladmin -S /var/run/mysqld/mysqld.sock shutdown
          # 开始监听克隆请求
          exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \
            "xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root"
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
      volumes:
      - name: conf
        emptyDir: {}
      - name: config-map
        configMap:
          name: mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi

这个StatefulSet定义包含几个关键部分:

  1. initContainers

    • init-mysql:基于Pod序号配置MySQL server-id
    • clone-mysql:从之前的Pod克隆数据(使用Percona XtraBackup)
  2. Containers

    • mysql:运行MySQL服务器的主容器
    • xtrabackup:处理复制和克隆的sidecar容器
  3. volumeClaimTemplates:为每个Pod创建独立的PersistentVolume

工作原理详解

Pod初始化流程

  1. init-mysql容器

    • 从Pod名称中提取序号(如mysql-0的0)
    • 生成server-id(100 + 序号)
    • 根据序号决定使用主节点还是副本节点配置
  2. clone-mysql容器

    • 主节点(mysql-0)跳过克隆
    • 副本节点从序号减1的Pod克隆数据(如mysql-1从mysql-0克隆)
    • 使用XtraBackup准备数据
  3. mysql容器

    • 启动MySQL服务器
    • 使用配置好的server-id和主/副本配置
  4. xtrabackup容器

    • 如果是副本节点,配置复制关系
    • 监听来自新Pod的克隆请求

稳定的网络标识

StatefulSet为每个Pod提供稳定的网络标识:

  • Pod名称:mysql-0, mysql-1, mysql-2
  • DNS名称:mysql-0.mysql, mysql-1.mysql, mysql-2.mysql

这种稳定性对于数据库复制至关重要,因为副本需要始终知道如何连接到主节点。

测试MySQL集群

写入测试

连接到主节点(mysql-0.mysql)执行写操作:

kubectl run mysql-client --image=mysql:5.7 -i --rm --restart=Never -- \
  mysql -h mysql-0.mysql <<EOF
CREATE DATABASE test;
CREATE TABLE test.messages (message VARCHAR(250));
INSERT INTO test.messages VALUES ('hello');
EOF

读取测试

通过mysql-read Service执行读操作,请求会被负载均衡到所有可用的副本:

kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never -- \
  mysql -h mysql-read -e "SELECT * FROM test.messages"

观察负载均衡

运行以下命令观察读请求如何分布到不同副本:

kubectl run mysql-client-loop --image=mysql:5.7 -i -t --rm --restart=Never -- \
  bash -ic "while sleep 1; do mysql -h mysql-read -e 'SELECT @@server_id,NOW()'; done"

故障恢复测试

模拟Pod故障

  1. 破坏readiness探针

    kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql /usr/bin/mysql.off
    

    观察该Pod变为NotReady,但读服务仍然可用

  2. 恢复Pod

    kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql.off /usr/bin/mysql
    

删除Pod

kubectl delete pod mysql-2

StatefulSet会自动重建Pod并保持原有身份和存储

节点维护

  1. 排空节点:
    kubectl drain <node-name> --force --delete-emptydir-data --ignore-daemonsets
    
  2. 观察Pod在其他节点重建
  3. 取消节点排空:
    kubectl uncordon <node-name>
    

扩缩容操作

扩容

kubectl scale statefulset mysql --replicas=5

新创建的副本会自动从现有Pod克隆数据并加入复制拓扑

缩容

kubectl scale statefulset mysql --replicas=3

注意:缩容不会自动删除PVC,需要手动清理不需要的PVC

清理资源

# 删除StatefulSet和Pod
kubectl delete statefulset mysql

# 删除Service
kubectl delete service mysql mysql-read

# 删除ConfigMap
kubectl delete configmap mysql

# 删除PVC(可选)
kubectl delete pvc -l app=mysql

总结

通过本文,我们学习了如何在Kubernetes中使用StatefulSet部署高可用的MySQL集群。关键点包括:

  1. StatefulSet为有状态应用提供稳定的网络标识和存储
  2. 精心设计的初始化流程确保数据一致性和正确复制
  3. 读写分离架构提高查询性能
  4. 自动故障恢复保障服务可用性
  5. 灵活的扩缩容能力适应业务需求变化

这种模式不仅适用于MySQL,也可以应用于其他需要持久化存储和复制功能的有状态应用。

website Kubernetes website and documentation repo: website 项目地址: https://gitcode.com/gh_mirrors/webs/website

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孙典将Phyllis

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值