AWS EKS运行有状态应用Mysql8.0集群(读写分离)

阅读本文时,我认为你已经具备kubernets基础,已经创建好了AWS EKS集群,并且能够使用kubectl管理集群。

1、概述

一般我们采用mysq作为数据库时,常用的是mysql5.x或mysql8.x版本。

数据库实例类型有单机版、高可用版、读写分离集群版。 前两种都比较容易实现。

今天我们来实现mysql8.0.26版本的读写分离,并且同步复制时采用密码方式。

为了更好地适应生产环境,以及后续的便利性,我们需要用到namespace(ns)、Configmap(cm),Secret,Service(svc),StatefulSet(sts)。

首先,描述一下我们想要部署的“有状态应用”。

1.是一个“主从复制”(Maser-Slave Replication)的 MySQL 集群;

2.有 1 个主节点(Master);有多个从节点(Slave);从节点需要能水平扩展;

3.所有的写操作,只能在主节点上执行;读操作可以在所有节点上执行。

这个主从模式的 MySQL 集群非常典型。我们通过一张图来表示。

2、存储

如果是自建kubernetes集群,你不懂golang开发的话,很难编写出CSI存储插件,存储类型可能会是你非常头疼的事情,但我们采用AWS EKS,这会让我们非常省心,因为AWS EKS已经帮我们做好了storageClass。采用storageClass,能够自动创建相应的pv和pvc,非常方便。

运行有状态应用Mysql,我们需要用到块存储,即AWS EBS。

AWS EBS存储类型有:io1,gp2,gp3,sc1,st1。存储类型区别详见Amazon EBS volume types - Amazon Elastic Compute Cloud。默认值:gp2。

通过如下命令,可以查看AWS EKS帮我们做好的storageClass。默认只有gp2类型的,我们可以直接使用。

$ kubectl get storageclass   #或 $kubectl get sc

#会输出如下内容

NAME              PROVISIONER                 AGE

gp2 (default)   kubernetes.io/aws-ebs   37m

这个storageClass非常容易实现,它是由gp2-storage-class.yaml这个文件生成的,内容如下:

apiVersion: storage.k8s.io/v1

kind: StorageClass

metadata:

  name: gp2

annotations:    storageclass.kubernetes.io/is-default-class: "true"

provisioner: kubernetes.io/aws-ebs

parameters:

  type: gp2

  fsType: ext4

如果,你想用其它类型的StorageClass,可以将配置文件中的type,更改为其它,如gp3。通过如下命令,即可创建。

$ kubectl create -f gp2-storage-class.yaml

3、命名空间namespace

配置文件中,如果没有指定namespace,默认会自动使用default,在生产环境中,最好是单独创建个命名空间,创建方法

mysql-ns.yaml

apiVersion: v1

kind: Namespace

metadata:

  name: mysql

接下来使用如下命令创建namespace

$kubectl create -f mysql-ns.yaml

记得在secret、statufulset文件中,加上如下一行。

metadata

  namespace: mysql

4、密钥Secret

我们将数据库的帐号密码存在Secret中。

Secret 对象要求这些数据必须是经过 Base64 转码的,以免出现明文密码的安全隐患。这个转码操作也很简单,比如:

$ echo -n 'root' |base64

cm9vdA==

$ echo -n '123456' |base64

MTIzNDU2

然后使用如下文件如 mysql-secret.yaml

apiVersion: v1

kind: Secret

metadata:

  name: prod-db-secret

  namespace: mysql

type: Opaque

data:

  password: MTIzNDU2

  username: cm9vdA==

接下来,我们尝试一下创建这个 Secret:

$ kubectl create -f mysql-secret.yaml

5、配置ConfigMap

可能以前我们习惯在Deployment或Statufulset中写配置文件,我建议生产环境中,配置文件采用configmap。

做mysql读写分离时,Master 节点和 Slave 节点需要有不同的配置文件。

内容如下mysql-config.yaml

apiVersion: v1

kind: ConfigMap

metadata:

  name: mysql

  namespace: mysql

  labels:

    app: mysql

data:

  master.cnf: |

    [mysqld]

    log-bin

  

  slave.cnf: |

    [mysqld]   

    super-read-only

    relay_log_info_repository=table

    master_info_repository=table

    relay_log_recovery = 1

接下来,我们尝试一下创建这个 configmap:

$ kubectl create -f mysql-configmap.yaml

如果你的程序是之前一直是在mysql5.7上运行,你可能会用到如下configMap

apiVersion: v1

kind: ConfigMap

metadata:

  name: mysql

  namespace: mysql

  labels:

    app: mysql

data:

  master.cnf: |

    [client]

    default-character-set=utf8mb4

    [mysql]

    default-character-set=utf8mb4

    [mysqld]

    log-bin

    binlog_expire_logs_seconds=2592000

    max_connections=10000

    binlog_format=MIXED

    explicit_defaults_for_timestamp=true

    lower_case_table_names=1

    max_allowed_packet=128M

    innodb-log-file-size=64M

    default-time-zone='+8:00'

    default-authentication-plugin=mysql_native_password

    character-set-client-handshake=FALSE

    character-set-server=utf8mb4

    collation-server=utf8mb4_unicode_ci

    init_connect='SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci'

    secure_file_priv=/var/lib/mysql

sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

  slave.cnf: |

    [client]

    default-character-set=utf8mb4

    [mysql]

    default-character-set=utf8mb4

    [mysqld]   

    super-read-only

    max_connections=10000

    binlog_format=MIXED

    relay_log_info_repository=table

    master_info_repository=table

    relay_log_recovery = 1

    explicit_defaults_for_timestamp=true

    lower_case_table_names=1

    max_allowed_packet=128M

    innodb-log-file-size=64M

    default-time-zone='+8:00'

    default-authentication-plugin=mysql_native_password

    character-set-client-handshake=FALSE

    character-set-server=utf8mb4

    collation-server=utf8mb4_unicode_ci

    init_connect='SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci'

    secure_file_priv=/var/lib/mysql

sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

#如果CM配置文件slave.cnf部分没有relay_log_info_repository=table、master_info_repository=table和relay_log_recovery=1那么最后一个从节点,始终不会正常。

6、服务Service

6.1集群内部访问

我们通过Headless Service来实现集群内部的访问,见配置文件。

mysql-service.yaml

apiVersion: v1

kind: Service

metadata:

  name: mysql

  namespace: mysql

  labels:

    app: mysql

spec:

  ports:

  - name: mysql

    port: 3306

  clusterIP: None

  selector:

    app: mysql

---

apiVersion: v1

kind: Service

metadata:

  name: mysql-read

  namespace: mysql

  labels:

    app: mysql

spec:

  ports:

  - name: mysql

    port: 3306

  selector:

    app: mysql

6.2集群外部访问

如果你想通过navicat等工具,远程连接数据库,可以通过如下两种方法。

方法一:通过nodePort,访问方式: nodeIP:30006

mysql-service-nodeport.yaml

#可读写

apiVersion: v1

kind: Service

metadata:

  name: mysql-node-rw

  namespace: mysql

  labels:

    app: mysql

spec:

  ports:

  - port: 3306

    nodePort: 30306

    targetPort: 3306

    name: mysql

  type: NodePort

  selector:

    statefulset.kubernetes.io/pod-name: mysql-0

方法二:通过LB,推荐选用此方式,AWS会自动帮我们创建好LB,并做好配置。访问方式: lb公网IP:23306

mysql-service-lb.yaml

kind: Service

apiVersion: v1

metadata:

  name: mysql-lb-rw

  namespace: mysql

spec:

  ports:

  - port: 23306 #这个端口就是公网IP的端口

    targetPort: 3306

    #nodePort: 31258  #如果你想指定work节点的端口,可以加上这行。

  selector:

    statefulset.kubernetes.io/pod-name: mysql-0

  type: LoadBalancer

若是访问只读实例,将serivce文件中selector改为如下内容,即可

 selector:

   app: mysql

7、使用StatefulSet运行有状态应用

最关键的来了。

在此配置文件中,能看到有initContainers,它的优先级最高。它包含了mysql容器和sidecar容器,它的作用主要用来主从节点初始化,及拷贝配置文件。 containers中也包含了mysql容器和sidecar容器,后续常态化运行这个containers。

其中sidecar容器的镜像  yuanzl/xtrabackup:2.0支持mysql8.0.26,mysql8.0.26以上版本不支持,如果mysql版本更高,需要重新做Docker镜像。

非常关键的,在配置文件中,能够看到volumeClaimTemplates的配置。只要在这部分配置文件中指定,我们需要的存储类型,容量等参数,EKS集群,会自动帮我们创建并绑定好pv和pvc。

本配置文件中,我声明了,我需要gp2类型的存储,容量为10G,访问方式为ReadWriteOnce(即只能挂在1个worker节点)。  另外我声明了 resources,即容器运行时的cpu和内存,可以根据需求更改。

mysql-statefulset.yaml

apiVersion: apps/v1

kind: StatefulSet

metadata:

  name: mysql

  namespace: mysql

spec:

  selector:

    matchLabels:

      app: mysql

  serviceName: mysql

  replicas: 3

  template:

    metadata:

      labels:

        app: mysql

    spec:

      initContainers:

      - name: init-mysql

        image: mysql:8.0.26

        imagePullPolicy: IfNotPresent

        env:

        - name: TZ

          value: Asia/Shanghai

        command:

        - bash

        - "-c"

        - |

          set -ex

          # 使用pod ordinal index生成 mysql server-id.

          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1

          ordinal=${BASH_REMATCH[1]}

          echo [mysqld] > /mnt/conf.d/server-id.cnf

          # Add an offset to avoid reserved server-id=0 value.

          echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf

          # 从configmap拷贝配置 到emptyDir.

          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: yuanzl/xtrabackup:2.0

        imagePullPolicy: IfNotPresent

        command:

        - bash

        - "-c"

        - |

          set -ex

          # 如果data存在则跳过.

          [[ -d /var/lib/mysql/mysql ]] && exit 0

          # Skip the clone on master (ordinal index 0).

          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1

          ordinal=${BASH_REMATCH[1]}

          [[ $ordinal -eq 0 ]] && exit 0

          # Clone data from previous peer.

          ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql

          # Prepare the backup.

          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:8.0.26

        imagePullPolicy: IfNotPresent

        env:

        - name: TZ

          value: Asia/Shanghai

          # 使用secret

        - name: MYSQL_ROOT_PASSWORD

          valueFrom:

            secretKeyRef:

              name: prod-db-secret

              key: password

        ports:

        - name: mysql

          containerPort: 3306

        volumeMounts:

        - name: data

          mountPath: /var/lib/mysql

          subPath: mysql

        - name: conf

          mountPath: /etc/mysql/conf.d

        livenessProbe:

          exec:

            command:

            - /bin/sh

            - "-c"

            - MYSQL_PWD="${MYSQL_ROOT_PASSWORD}"

            - mysqladmin ping"

          initialDelaySeconds: 30

          periodSeconds: 10

          timeoutSeconds: 5

        readinessProbe:

          exec:

            command:

            - /bin/sh

            - "-c"

            - MYSQL_PWD="${MYSQL_ROOT_PASSWORD}"

            - mysql -h 127.0.0.1 -u root -e "SELECT 1"

          initialDelaySeconds: 5

          periodSeconds: 2

          timeoutSeconds: 1

      - name: xtrabackup

        image: yuanzl/xtrabackup:2.0

        imagePullPolicy: IfNotPresent

        ports:

        - name: xtrabackup

          containerPort: 3307

        env:

        - name: MYSQL_ROOT_PASSWORD

          valueFrom:

            secretKeyRef:

              name: prod-db-secret

              key: password

        command:

        - bash

        - "-c"

        - |

          set -ex

          cd /var/lib/mysql

          #cat /etc/hosts



          # Determine binlog position of cloned data, if any.

          if [[ -f xtrabackup_slave_info && "x$(<xtrabackup_slave_info)" != "x" ]]; then

            # XtraBackup already generated a partial "CHANGE MASTER TO" query

            # because we're cloning from an existing slave. (Need to remove the tailing semicolon!)

            cat xtrabackup_slave_info | sed -E 's/;$//g' > change_master_to.sql.in

            # Ignore xtrabackup_binlog_info in this case (it's useless).

            rm -f xtrabackup_slave_info xtrabackup_binlog_info

          elif [[ -f xtrabackup_binlog_info ]]; then

            # We're cloning directly from master. Parse binlog position.

            [[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1

            rm -f xtrabackup_binlog_info xtrabackup_slave_info

            echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\

                  MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in

          fi



          # Check if we need to complete a clone by starting replication.

          if [[ -f change_master_to.sql.in ]]; then

            echo "Waiting for mysqld to be ready (accepting connections)"

            until mysql -h 127.0.0.1 -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

            mysql -h 127.0.0.1 -uroot -p${MYSQL_ROOT_PASSWORD} <<EOF

            $(<change_master_to.sql.orig),

            MASTER_HOST='mysql-0.mysql',

            MASTER_USER='root',

            MASTER_PASSWORD='${MYSQL_ROOT_PASSWORD}',

            MASTER_CONNECT_RETRY=10;

            START SLAVE;

          EOF

          echo "master slave config ok"

          fi

          

          # Start a server to send backups when requested by peers.

          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 --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: "1G"

      storageClassName: gp2

然后,我们就可以创建这个 StatefulSet 了,如下所示:

$ kubectl create -f mysql-statefulset.yaml

$ kubectl get pod -l app=mysql -n mysql

NAME      READY     STATUS    RESTARTS   AGE

mysql-0   2/2       Running   0          2m

mysql-1   2/2       Running   0          1m

mysql-2   2/2       Running   0          1m

8、验证

现在,mysql集群已经正常运行,我们可以尝试向这个 MySQL 集群发起写请求,执行一些 SQL 操作来验证它是否正常。

注意,写请求用的是mysql-0.mysql,并且指定了namespace

$ kubectl run mysql-client --image=mysql:8.0.26 -i --rm --restart=Never -n mysql --\

   mysql -h mysql-0.mysql -uroot -p123456 <<EOF

CREATE DATABASE test;

CREATE TABLE test.messages (message VARCHAR(250));

INSERT INTO test.messages VALUES ('hello');

EOF

通过连接 mysql-read 这个 Service,我们就可以用 SQL 进行读操作,如下所示:

$ kubectl run mysql-client --image=mysql:8.0.26 -i -t --rm --restart=Never -n mysql --\

   mysql -h mysql-read  -uroot -p123456 -e "SELECT * FROM test.messages"

mysql: [Warning] Using a password on the command line interface can be insecure.

+---------+

| message |

+---------+

| hello   |

+---------+

pod "mysql-client" deleted

在互联网上,使用Navicat,通过AWS EKS,自动创建的lb的公网IP,也可以正常访问。

 

另外,我们可以通过如下命令扩展集群

$ kubectl scale statefulset mysql  --replicas=5

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值