在K8S中部署MySQL主从

1 概述

在K8S环境中,配置单个MySQL是非常方便的,但如果需要配置MySQL主从,则比较麻烦。
配置MySQL主从有下面三个特殊的地方:

  • 主库的配置文件和从库的配置文件,也就是需要根据主从的不同使用不同的配置文件。
  • 从库开始的时候,主库可能已经有数据了,需要备份这些数据到从库作为初始数据。
  • 从库启动前,需要连接主库并指定同步文件的名称和从哪个位置开始同步数据,然后启动从库。
    如果是手工搭建MySQL主从,上面这些特殊地方手工一个个完成即可。但在K8S环境中,MySQL主从属于有状态的,需要由statefulset来编排,也就是上面那些特殊地方,需要用Pod中的容器来协助完成,才能够纳入到statefulset中进行编排,否则就无法做到自动多一个或者少一个从库。

2 配置

2.1 yaml配置

1、设定namespace
采用了自定义namespace,好处是pod、statefulset、service等的名称不怕重复,这也是推荐的一种实践。下面除了PV对象之外,其它对象都加上此namespace,方便管理,缺点是后面查询对象的命令都需要加上-n mysql-demo03参数指定namespace来查询。所有对象都加上相同的label,方便需要全部查询对象的场景。

XML 复制代码
# mysql-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: mysql-demo03
  labels:
    app: mysql

2、主从库的配置文件

主库主要是开启binlog,使得从库可以同步binlog文件来保证数据与主库一致。从库则需要设置只读,不支持写入数据。

XML 复制代码
# mysql-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
  namespace: mysql-demo03
  labels:
    app: mysql
data:
  master.cnf: |
    [mysqld]
    log-bin 
  slave.cnf: |
    [mysqld]
    super-read-only

3、设置MySQL数据库的访问密码

使用Secret类型时,数据需要用Base64编码,可以在Linux命令行中执行命令进行Base64编码:echo -n "123456" | base64

XML 复制代码
# mysql-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
  namespace: mysql-demo03
  labels:
    app: mysql
type: Opaque
data:
  password: MTIzNDU2

4、开放访问端口

使用NodePort类型的Service,支持从外部访问MySQL,注意在Windows系统需要使用WSL的IP,Linux则可以使用宿主机的IP。

XML 复制代码
# mysql-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: mysql-demo03
  labels:
    app: mysql
spec:
  type: NodePort
  ports:
  - name: mysql
    port: 3306
    nodePort: 31306
  selector:
    app: mysql
---
apiVersion: v1
kind: Service
metadata:
  name: mysql-read
  namespace: mysql-demo03
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  selector:
    app: mysql

5、定义文件目录类型的PV

作为例子,使用宿主机上的目录作为PV最简单。注意Windows系统中需要用WSL里面的的目录,Linux上可以直接使用宿主机上的目录。也可以配置NFS之类的来提供PV。注意storageClassName,需要在下面是statefulset中映射上,否则PVC无法正确绑定PV。volumeClaimTemplates中补充storageClassName的配置,用于匹配PV。

XML 复制代码
# mysql-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: demo03-mysql-pv01
  labels:
    app: mysql
spec:
  storageClassName: demo03-mysql-pv
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 1Gi
  hostPath:
    path: /mnt/host/d/mysql_demo03/volume-pv01
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: demo03-mysql-pv02
  labels:
    app: mysql
spec:
  storageClassName: demo03-mysql-pv
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 1Gi
  hostPath:
    path: /mnt/host/d/mysql_demo03/volume-pv02
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: demo03-mysql-pv03
  labels:
    app: mysql
spec:
  storageClassName: demo03-mysql-pv
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 1Gi
  hostPath:
    path: /mnt/host/d/mysql_demo03/volume-pv03

6、编排容器

使用statefulset编排容器,使用两个initContainers,一个通过提取容器的编号来区分主从,从而获取不同的配置;第二个组装启动从库的同步SQL。还提供了一个常规容器xtrabackup来备份数据,备份数据用来初始化从库数据和提供同步初始信息。

XML 复制代码
# mysql-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: mysql-demo03
  labels:
    app: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql
  replicas: 3
  template:
    metadata:
      labels:
        app: mysql
    spec:
      initContainers:
      - name: init-mysql
        image: ubuntu:20.04
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
        command:
        - bash
        - "-c"
        - |
          set -ex
          # statefulset为pod生成hostname,里面包含序号,提取序号来作为server-id,并区分主从(第0个为主、其它为从)
          [[ $(hostname) =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          echo [mysqld] > /mnt/conf.d/server-id.cnf
          # 由于server-id不能为 0,因此给ID加个数来避开它
          echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
          # 如果pod序号为0则为主,其它为从,区分主从获取不同的配置
          if [[ ${ordinal} -eq 0 ]]; then
            cp /mnt/config-map/master.cnf /mnt/conf.d
          else
            cp /mnt/config-map/slave.cnf /mnt/conf.d
          fi
        volumeMounts:
        - name: conf
          mountPath: /mnt/conf.d
        - name: config-map
          mountPath: /mnt/config-map
      - name: clone-mysql
        image: ist0ne/xtrabackup:1.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
        command:
        - bash
        - "-c"
        - |
          set -ex
          # 一个新的MySQL需要拷贝已有的数据,如果数据已经存在,就不需要再次拷贝了
          [[ -d /var/lib/mysql/mysql ]] && exit 0
          # 从hostname中提取pod序号
          [[ $(hostname) =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          # 主MySQL不用拷贝数据
          [[ $ordinal == 0 ]] && exit 0
          # 从前一个序号的MySQL中拷贝数据
          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_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
        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", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
        readinessProbe:
          exec:
            command: ["mysqladmin", "ping", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]
          initialDelaySeconds: 5
          periodSeconds: 2
          timeoutSeconds: 1
      - name: xtrabackup
        image: ist0ne/xtrabackup:1.0
        ports:
        - name: xtrabackup
          containerPort: 3307
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
        command:
        - bash
        - "-c"
        - |
          set -ex
          cd /var/lib/mysql
          # 启动从库前,需要执行"CHANGE MASTER TO" SQL,里面需要提供MASTER_LOG_FILE(binlog文件名)和MASTER_LOG_POS(同步数据位置)
          # 如果通过xtrabackup备份了数据文件,则可以从备份文件里获取到这两个信息
          if [[ -f xtrabackup_slave_info ]]; then
            # xtrabackup_slave_info文件来自于从库,文件里已经包含"CHANGE MASTER TO" SQL,改名字后直接使用即可
            mv xtrabackup_slave_info change_master_to.sql.in
            rm -f xtrabackup_binlog_info
          elif [[ -f xtrabackup_binlog_info ]]; then
            # xtrabackup_binlog_info文件来自于主库,需要从中提取MASTER_LOG_FILE和MASTER_LOG_POS并组装"CHANGE MASTER TO" SQL
            [[ $(cat xtrabackup_binlog_info) =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
            rm xtrabackup_binlog_info
            echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\
                  MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
          fi
          if [[ -f change_master_to.sql.in ]]; then
            # 等待MySQL容器ready之后才能执行上面组装的SQL
            echo "Waiting for mysqld to be ready(accepting connections)"
            until mysql -uroot -p${MYSQL_ROOT_PASSWORD} -e "SELECT 1"; do sleep 1; done
            echo "Initializing replication from clone position"
            mv change_master_to.sql.in change_master_to.sql.orig
            # 执行"CHANGE MASTER TO" SQL,并启动Slave库同步
            mysql -uroot -p${MYSQL_ROOT_PASSWORD} << EOF
          $(< change_master_to.sql.orig),
            MASTER_HOST='mysql-0.mysql.weixnie',
            MASTER_USER='root',
            MASTER_PASSWORD='${MYSQL_ROOT_PASSWORD}',
            MASTER_CONNECT_RETRY=10;
          START SLAVE;
          EOF
          fi
          # ncat监听3307端口,有新数据则备份
          exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \
            "xtrabackup --backup --slave-info --stream=xbstream --user=root --password=${MYSQL_ROOT_PASSWORD}"
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
      volumes:
      - name: conf
        emptyDir: {}
      - name: config-map
        configMap:
          name: mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi
      storageClassName: demo03-mysql-pv
      volumeMode: Filesystem

通过kubectl apply -f xx.yaml,按顺序执行上面yaml文件即可。

2.2 一些问题

1、容器init-mysql需要使用正确的镜像。

这个容器中用到了bash脚本,需要使用提供bash命令的镜像,比如上例换成了ubuntu:20.04镜像,提供完整的Linux命令,方便测试时可以多一些命令可以使用,缺点是镜像比较大。正式使用时可以选用小一点的镜像。

2、Service需要在Statefulset之前执行

Statefulset中从库需要连接主库,由于Pod本身的IP是不确定的,所以需要Service来提供稳定的连接服务。如果Service后执行,需要重启Statefulset。

3、不能使用127.0.0.1 IP

Statefulset里面有bash脚本中访问MySQL库的时候,如果用了-h 127.0.0.1,带着这个参数实际是访问不了的,在Pod中并不认这个IP,应该使用Service名称访问。

4、配置好PV

没有PV的时候,Pod也启动不起来。需要先配置好PV,最简单的配置PV方式就是使用宿主机的目录,如果是Windows系统,需要注意要把宿主机的目录转为WSL里的目录,上例使用的就是Windows的目录。

2.3 检验

1、使用kubectl get pod -n mysql-demo03查看Pod的状态,观察STATUS字段需要为Running,READY字段里的两个数字要相等,这样Pod才正常运行。

2、使用kubectl exec -it mysql-0 -n mysql-demo03 -- /bin/bash 进入Pod,用mysql -uroot -p命令登录MySQL,在里面能够正常执行SQL命令。主库可查可写,从库只能查不能写。

3、修改statefulset里的replicas字段值,增减从库数量,通过kubectl rollout status sts/mysql 命令查看从库的增减过程。

4、如果遇到问题,则使用kubectl logs pod_name -c container_name -n mysql-demo03来查看具体的日志,根据日志解决问题。

3 小结

使用K8S来部署主从,有点在yaml里写shell脚本的嫌疑,如果不使用Go语言来自定义Operator,就只能先用这种方式了。

相关推荐
回忆是昨天里的海5 小时前
k8s部署容器化应用-tomcat
云原生·容器·kubernetes·1024程序员节
Shannon Law6 小时前
Docker连接超时的解决方法
docker·容器
野犬寒鸦7 小时前
从零起步学习MySQL || 第九章:从数据页的角度看B+树及MySQL中数据的底层存储原理(结合常见面试题深度解析)
java·服务器·数据库·后端·mysql·oracle·1024程序员节
方二华9 小时前
5 mysql源码中B+树的构建
数据库·mysql·1024程序员节
helloworddm11 小时前
Orleans + Kubernetes + Istio 服务网格集成深度解析
容器·kubernetes·istio
小彭律师11 小时前
Docker/K8s部署MySQL的创新实践与优化技巧大纲
mysql·docker·kubernetes
lastHertz11 小时前
Docker 占用导致 C 盘空间不足的排查与解决
运维·docker·容器
专家大圣11 小时前
Docker+Redis监控新方案:cpolar让远程管理“零配置”
网络·redis·docker·容器·内网穿透