MySQL高阶讲座之:`MySQL`在`K8s`中的`StatefulSet`:如何实现持久化存储与高可用部署。

各位观众老爷,晚上好!我是你们的老朋友,今天咱们不聊风花雪月,就来聊聊MySQL在K8s这个大舞台上的那些事儿。今天的主题是:MySQLK8s中的StatefulSet:如何实现持久化存储与高可用部署。

简单来说,就是教大家如何在K8s里,用一种比较靠谱的方式,把MySQL搭起来,并且保证数据不丢,服务稳定。

一、 为什么是StatefulSet?

想象一下,如果你要把一群小猫(MySQL实例)放到一个猫舍(K8s集群)里。每只猫都需要有自己的名字,自己的房间(持久化存储),并且不能随便换房间,否则它会找不到自己的猫粮(数据)。

  • Deployment: 如果你用Deployment,K8s会随机创建和删除Pod,每次Pod重启,都是一个新的Pod。这就意味着,你的MySQL实例会丢失所有数据,这显然不行。Deployment适合管理无状态应用,比如Nginx,它不需要记住任何东西。

  • StatefulSet: StatefulSet就是为这种有状态应用而生的。它保证了:

    • 稳定的网络标识: 每个Pod都有一个固定的域名,比如mysql-0.mysql.default.svc.cluster.local。即使Pod重启,域名也不会变。
    • 稳定的持久化存储: 每个Pod都绑定一个PersistentVolumeClaim (PVC),PVC会关联到实际的存储(比如云硬盘)。即使Pod重启,存储也会被重新挂载到新的Pod上。
    • 有序的部署和删除: Pod会按照0, 1, 2...的顺序创建,并且按照相反的顺序删除。这样可以保证数据一致性。

二、 准备工作

在开始之前,你需要确保你的K8s集群已经搭建好,并且安装了kubectl。还需要一个可以提供持久化存储的StorageClass。大部分云厂商都会提供默认的StorageClass,如果没有,你需要自己创建一个。

你可以通过下面的命令查看现有的StorageClass:

kubectl get storageclass

如果你的集群没有默认的StorageClass,或者你需要使用特定的StorageClass,可以创建一个:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: my-storage-class
provisioner: kubernetes.io/aws-ebs  # 根据你的云平台修改
parameters:
  type: gp2  # 根据你的云平台修改
reclaimPolicy: Delete # Delete/Retain, 删除PVC时是否删除存储卷

三、 YAML文件:StatefulSet定义

接下来,就是重头戏了:编写StatefulSet的YAML文件。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql # 创建一个headless service
  replicas: 3
  template:
    metadata:
      labels:
        app: mysql
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
          name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "your_root_password"
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
  volumeClaimTemplates:
  - metadata:
      name: mysql-data
    spec:
      accessModes: [ "ReadWriteOnce" ] # 读写权限
      resources:
        requests:
          storage: 10Gi # 存储大小
      storageClassName: my-storage-class # 使用刚才创建的StorageClass

代码解读:

  • apiVersion: apps/v1: 指定API版本,StatefulSet属于apps/v1版本。
  • kind: StatefulSet: 指定资源类型为StatefulSet。
  • metadata.name: mysql: StatefulSet的名称。
  • spec.selector: 用于匹配Pod的标签。
  • spec.serviceName: mysql: 指定一个Headless Service的名称。Headless Service不会分配Cluster IP,而是直接返回Pod的IP地址。这对于StatefulSet来说非常重要,因为我们需要通过Pod的域名来访问它们。
  • spec.replicas: 3: 指定创建3个MySQL实例。
  • spec.template: Pod的模板,定义了Pod的各种属性。
  • spec.terminationGracePeriodSeconds: 10: Pod终止前的缓冲时间,单位是秒。
  • spec.containers: 容器的定义。
    • name: mysql: 容器的名称。
    • image: mysql:8.0: 使用的MySQL镜像。
    • ports: 暴露的端口。
    • env: 环境变量,这里设置了MySQL的root密码。
    • volumeMounts: 挂载存储卷。
      • name: mysql-data: 存储卷的名称,与volumeClaimTemplates中定义的名称一致。
      • mountPath: /var/lib/mysql: 将存储卷挂载到容器内的/var/lib/mysql目录,这是MySQL默认的数据存储目录。
  • volumeClaimTemplates: 定义PVC的模板。K8s会为每个Pod创建一个PVC,并使用这个模板来创建。
    • metadata.name: mysql-data: PVC的名称。
    • spec.accessModes: [ "ReadWriteOnce" ]: 指定访问模式为ReadWriteOnce,表示只能被一个Pod读写。
    • spec.resources: 存储资源请求。
      • requests.storage: 10Gi: 请求10Gi的存储空间。
    • spec.storageClassName: my-storage-class: 指定使用的StorageClass。

四、 YAML文件:Headless Service定义

StatefulSet需要一个Headless Service才能正常工作。Headless Service不会分配Cluster IP,而是直接返回Pod的IP地址。

apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  clusterIP: None # 表明这是一个Headless Service
  selector:
    app: mysql
  ports:
  - port: 3306
    name: mysql

代码解读:

  • apiVersion: v1: 指定API版本。
  • kind: Service: 指定资源类型为Service。
  • metadata.name: mysql: Service的名称,与StatefulSet中的spec.serviceName一致。
  • spec.clusterIP: None: 将clusterIP设置为None,表明这是一个Headless Service。
  • spec.selector: 用于匹配Pod的标签。
  • ports: 暴露的端口。

五、 应用YAML文件

保存上面的两个YAML文件,分别命名为mysql-statefulset.yamlmysql-service.yaml。然后,使用kubectl命令来应用它们:

kubectl apply -f mysql-service.yaml
kubectl apply -f mysql-statefulset.yaml

稍等片刻,K8s就会创建StatefulSet和Headless Service。你可以使用下面的命令来查看Pod的状态:

kubectl get pods

你应该看到类似下面的输出:

NAME      READY   STATUS    RESTARTS   AGE
mysql-0   1/1     Running   0          1m
mysql-1   1/1     Running   0          50s
mysql-2   1/1     Running   0          40s

注意,Pod的名称是有序的,分别是mysql-0, mysql-1, mysql-2

你也可以查看PVC的状态:

kubectl get pvc

你应该看到类似下面的输出:

NAME              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
mysql-data-mysql-0   Bound    pvc-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx   10Gi       RWO            my-storage-class   1m
mysql-data-mysql-1   Bound    pvc-yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy   10Gi       RWO            my-storage-class   50s
mysql-data-mysql-2   Bound    pvc-zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz   10Gi       RWO            my-storage-class   40s

每个Pod都有一个对应的PVC,并且PVC已经绑定到了实际的存储卷。

六、 连接MySQL

现在,你可以通过Pod的域名来连接MySQL了。比如,你可以使用mysql-0.mysql.default.svc.cluster.local来连接第一个MySQL实例。

首先,进入集群内部的Pod:

kubectl exec -it mysql-0 -- bash

然后在Pod内部,使用mysql客户端连接MySQL:

mysql -h mysql-0.mysql.default.svc.cluster.local -u root -p

输入你在YAML文件中设置的root密码,就可以成功连接到MySQL了。

七、 高可用部署

上面的例子只是一个简单的部署,并没有实现真正的高可用。要实现高可用,你需要使用MySQL的主从复制功能。

  1. 修改镜像: 使用支持主从复制的MySQL镜像,或者自己构建一个。
  2. 初始化脚本: 编写一个初始化脚本,用于配置主从复制。
  3. 修改StatefulSet: 修改StatefulSet的YAML文件,添加初始化脚本。

这是一个更复杂的例子,包含了主从复制的配置:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql
  replicas: 3
  template:
    metadata:
      labels:
        app: mysql
    spec:
      terminationGracePeriodSeconds: 10
      initContainers:
      - name: init-mysql
        image: mysql:8.0
        command:
        - bash
        - "-c"
        - |
          set -ex
          # 生成一个随机的server-id,避免冲突
          SERVER_ID=$((RANDOM % 10000 + 10000))
          echo "server-id = $SERVER_ID" > /mnt/conf.d/server-id.cnf
          # 如果是第一个Pod,则设置为主节点
          if [ "$(hostname)" = "mysql-0" ]; then
            echo "skip-slave-start" > /mnt/conf.d/master.cnf
          else
            echo "log-bin" > /mnt/conf.d/slave.cnf
            echo "log-slave-updates" >> /mnt/conf.d/slave.cnf
          fi
        volumeMounts:
        - name: mysql-data
          mountPath: /mnt/conf.d
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
          name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "your_root_password"
        - name: MYSQL_REPLICATION_USER
          value: "repl"
        - name: MYSQL_REPLICATION_PASSWORD
          value: "your_replication_password"
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
        - name: config-volume
          mountPath: /etc/mysql/conf.d
      volumes:
      - name: config-volume
        emptyDir: {}
  volumeClaimTemplates:
  - metadata:
      name: mysql-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 10Gi
      storageClassName: my-storage-class

代码解读:

  • initContainers: 初始化容器,会在主容器启动之前运行。
    • name: init-mysql: 初始化容器的名称。
    • image: mysql:8.0: 使用的MySQL镜像。
    • command: 执行的命令。
      • SERVER_ID: 生成一个随机的server-id,避免冲突。
      • if [ "$(hostname)" = "mysql-0" ]: 判断是否是第一个Pod(主节点)。
      • skip-slave-start: 如果为主节点,则跳过从节点的启动。
      • log-bin: 如果为从节点,则启用二进制日志。
      • log-slave-updates: 如果为从节点,则记录从节点更新的日志。
    • volumeMounts: 挂载存储卷。
      • /mnt/conf.d: 将存储卷挂载到初始化容器内的/mnt/conf.d目录,用于存放MySQL的配置文件。
  • volumes: 定义共享卷,用于在初始化容器和主容器之间共享数据。
    • name: config-volume: 共享卷的名称。
    • emptyDir: {}: 创建一个空的目录,用于存放MySQL的配置文件。
  • volumeMounts: 主容器的存储卷挂载。
    • /etc/mysql/conf.d: 将共享卷挂载到主容器内的/etc/mysql/conf.d目录,用于读取MySQL的配置文件。
  • MYSQL_REPLICATION_USER, MYSQL_REPLICATION_PASSWORD: 用于主从复制的用户和密码,需要在MySQL服务器上创建。

接下来,你需要手动配置主从复制关系。

  1. 进入主节点(mysql-0)的Pod:

    kubectl exec -it mysql-0 -- bash
  2. 登录MySQL:

    mysql -u root -p
  3. 创建复制用户:

    CREATE USER 'repl'@'%' IDENTIFIED BY 'your_replication_password';
    GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
    FLUSH PRIVILEGES;
    SHOW MASTER STATUS;

    记录下FilePosition的值,稍后会用到。

  4. 进入从节点(mysql-1 或 mysql-2)的Pod:

    kubectl exec -it mysql-1 -- bash
  5. 登录MySQL:

    mysql -u root -p
  6. 配置从节点:

    CHANGE MASTER TO
        MASTER_HOST='mysql-0.mysql.default.svc.cluster.local',
        MASTER_USER='repl',
        MASTER_PASSWORD='your_replication_password',
        MASTER_LOG_FILE='记录下来的File值',
        MASTER_LOG_POS=记录下来的Position值;
    START SLAVE;
    SHOW SLAVE STATUSG

    查看Slave_IO_RunningSlave_SQL_Running是否为Yes

重复步骤4-6,配置其他的从节点。

八、 总结

今天我们一起学习了如何在K8s中使用StatefulSet部署MySQL,并且实现了持久化存储和高可用部署。

  • StatefulSet是管理有状态应用的最佳选择。
  • Headless Service是StatefulSet的必备组件。
  • 主从复制是实现MySQL高可用的常用方法。

当然,这只是一个简单的例子,实际部署中还需要考虑更多的因素,比如:

  • 数据备份与恢复
  • 监控与告警
  • 性能优化

希望今天的讲座对大家有所帮助。下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注