指尖上的记忆指尖上的记忆
首页
  • 基础
  • Laravel框架
  • Symfony框架
  • 基础
  • Gin框架
  • 基础
  • Spring框架
  • 命令
  • Nginx
  • Ai
  • Deploy
  • Docker
  • K8s
  • Micro
  • RabbitMQ
  • Mysql
  • PostgreSsql
  • Redis
  • MongoDb
  • Html
  • Js
  • 前端
  • 后端
  • Git
  • 知识扫盲
  • Golang
🌟 gitHub
首页
  • 基础
  • Laravel框架
  • Symfony框架
  • 基础
  • Gin框架
  • 基础
  • Spring框架
  • 命令
  • Nginx
  • Ai
  • Deploy
  • Docker
  • K8s
  • Micro
  • RabbitMQ
  • Mysql
  • PostgreSsql
  • Redis
  • MongoDb
  • Html
  • Js
  • 前端
  • 后端
  • Git
  • 知识扫盲
  • Golang
🌟 gitHub

mysql基于k8s的主从配置:

使用minikube 搭建mysql8.0的集群,一个主节点两个从节点
1.启动minikube
minikube start
  
必须要启动,否则后面 kubectl apply 的时候,会报如下错误:
error: error validating "mysql-rep-cluster.yaml": error validating data: failed to download openapi: Get "https://192.168.49.2:8443/openapi/v2?timeout=32s": dial tcp 192.168.49.2:8443: connect: no route to host; if you choose to ignore these errors, turn validation off with --validate=false

2.编写yaml文件,本次文件命名为 mysql-rep-cluster.yaml , 内容如下:

# mysql-master-config ConfigMap (主节点配置)
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-master-config
  namespace: default
data:
  my.cnf: |
    [mysqld]
    user=mysql
    default_authentication_plugin=mysql_native_password
    bind-address=0.0.0.0
    log_bin=mysql-bin
    server-id=1
    binlog-format=ROW
    expire_logs_days=7
    max_binlog_size=100M
    sql_mode=STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
    # 禁用默认的 includedir 指令
    loose_mysqlx_port=33060
    !includedir /etc/mysql/conf.d/
    !includedir /etc/mysql/mysql.conf.d/
---
# mysql-slave1-config ConfigMap (从节点1配置)
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-slave1-config
  namespace: default
data:
  my.cnf: |
    [mysqld]
    user=mysql
    default_authentication_plugin=mysql_native_password
    bind-address=0.0.0.0
    server-id=2
    relay-log=relay-log-bin
    read-only=1
    log_slave_updates=1
    max_binlog_size=100M
    sql_mode=STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
    # 禁用默认的 includedir 指令
    loose_mysqlx_port=33060
    !includedir /etc/mysql/conf.d/
    !includedir /etc/mysql/mysql.conf.d/
---
# mysql-slave2-config ConfigMap (从节点2配置)
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-slave2-config
  namespace: default
data:
  my.cnf: |
    [mysqld]
    user=mysql
    default_authentication_plugin=mysql_native_password
    bind-address=0.0.0.0
    server-id=3
    relay-log=relay-log-bin
    read-only=1
    log_slave_updates=1
    max_binlog_size=100M
    sql_mode=STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
    # 禁用默认的 includedir 指令
    loose_mysqlx_port=33060
    !includedir /etc/mysql/conf.d/
    !includedir /etc/mysql/mysql.conf.d/
---
# StatefulSet 配置
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql-cluster
  namespace: default
spec:
  serviceName: mysql
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      initContainers:
      - name: init-mysql
        image: busybox:1.36
        command:
        - sh
        - -c
        - |
          # 创建必要的目录结构
          mkdir -p /etc/mysql/conf.d
          mkdir -p /etc/mysql/mysql.conf.d
          
          # 根据主机名选择配置
          if [ "${HOSTNAME}" = "mysql-cluster-0" ]; then
            cp /mnt/master-config/my.cnf /etc/mysql/my.cnf
          elif [ "${HOSTNAME}" = "mysql-cluster-1" ]; then
            cp /mnt/slave1-config/my.cnf /etc/mysql/my.cnf
          else
            cp /mnt/slave2-config/my.cnf /etc/mysql/my.cnf
          fi
          
          # 设置正确的权限
          chmod -R 755 /etc/mysql
          chown -R 999:999 /etc/mysql
        volumeMounts:
        - name: mysql-master-config
          mountPath: /mnt/master-config
        - name: mysql-slave1-config
          mountPath: /mnt/slave1-config
        - name: mysql-slave2-config
          mountPath: /mnt/slave2-config
        - name: config-dir
          mountPath: /etc/mysql
      containers:
      - name: mysql
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "rootpassword"
        volumeMounts:
        - name: config-dir
          mountPath: /etc/mysql
        - name: mysql-data
          mountPath: /var/lib/mysql
        ports:
        - containerPort: 3306
          name: mysql
      volumes:
      - name: mysql-master-config
        configMap:
          name: mysql-master-config
      - name: mysql-slave1-config
        configMap:
          name: mysql-slave1-config
      - name: mysql-slave2-config
        configMap:
          name: mysql-slave2-config
      - name: config-dir
        emptyDir: {}
  volumeClaimTemplates:
  - metadata:
      name: mysql-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi
---
# Headless Service
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: default
spec:
  clusterIP: None
  ports:
  - port: 3306
  selector:
    app: mysql
---
# ClusterIP Service
apiVersion: v1
kind: Service
metadata:
  name: mysql-cluster
  namespace: default
spec:
  ports:
  - port: 3306
    targetPort: 3306
  selector:
    app: mysql
这个 yaml 文件我花了两天时间搞定,其中的 ConfigMap 一开始没有分成三个,只是简单的配置了两个,而且在后面使用的时候,还只是配置了 mysql-master-config, 导致最后三个节点的 server-id=1 都是1,然后在后续查看slave节点的状态的时候,报:
Last_IO_Error: Fatal error: The replica I/O thread stops because source and replica have equal MySQL server ids; these ids must be different for replication to work (or the --replicate-same-server-id option must be used on replica but this does not always make sense; please check the manual before using it). 这意味着要为所有的节点配置不同的 server-id,我这里有三个节点,那就必须要有三个 ConfigMap,以及在后面 StatefulSet 中分别使用这三个配置。
  
3.使用yaml文件创建集群
$ kubectl apply -f mysql-rep-cluster.yaml
  
4.查看pods状态
以下是几种可能出现的情况
  
1>
$ kubectl get pods //一直找不到任何东西
No resources found in default namespace.
  
那么就需要取查看错误了
$ kubectl get statefulsets //这个主要是排除yaml文件错误的,因为很复杂,所以很容易出错
NAME            READY   AGE
mysql-cluster   0/3     9s
  
$ kubectl describe statefulset mysql-cluster  // 这个 mysql-cluster 是上面statefulset的名称
Name:               mysql-cluster
Namespace:          default
CreationTimestamp:  Sat, 28 Dec 2024 00:25:12 +0800
Selector:           app=mysql
Labels:             <none>
Annotations:        <none>
Replicas:           3 desired | 0 total
Update Strategy:    RollingUpdate
  Partition:        0
Pods Status:        0 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  app=mysql
  Containers:
   mysql:
    Image:      mysql:8.0
    Port:       <none>
    Host Port:  <none>
    Environment:
      MYSQL_ROOT_PASSWORD:  rootpassword
      MYSQL_DATABASE:       mysql
    Mounts:
      /etc/mysql/custom-my.cnf from mysql-config-volume (rw,path="my.cnf")
      /var/lib/mysql-data from mysql-data (rw)
  Volumes:         <none>
  Node-Selectors:  <none>
  Tolerations:     <none>
Volume Claims:
  Name:          mysql-data
  StorageClass:  
  Labels:        <none>
  Annotations:   <none>
  Capacity:      1Gi
  Access Modes:  [ReadWriteOnce]
Events:
  Type     Reason        Age                From                    Message
  ----     ------        ----               ----                    -------
  Warning  FailedCreate  5s (x12 over 16s)  statefulset-controller  create Pod mysql-cluster-0 in StatefulSet mysql-cluster failed error: Pod "mysql-cluster-0" is invalid: spec.containers[0].volumeMounts[0].name: Not found: "mysql-config-volume"
  
这个错误是因为StatefulSet的配置结构不正确。在StatefulSet中,volumes 应该定义在 Pod 模板(spec.template.spec)中,而不是直接在StatefulSet的 spec 下。将 volumes 从 StatefulSet 的 spec 层级移到了 spec.template.spec 下。这个错误其实是我最开始的一个版本里的,我把 volumes 放到了外层。
  
2>
$ kubectl get pods
NAME      READY   STATUS                  RESTARTS   AGE
mysql-0   0/1     Init:ImagePullBackOff   0          2m53s  // 注意刚开始是这样命名的,这是我之前的一个错误,这里只是贴出来
  
然后可以执行如下命令查看这个pod的详细情况,一般都会有异常信息
$ kubectl describe pod mysql-0
Name:             mysql-0
Namespace:        default
Priority:         0
Service Account:  default
Node:             minikube/192.168.49.2
Start Time:       Wed, 25 Dec 2024 23:29:14 +0800
Labels:           app=mysql
                  apps.kubernetes.io/pod-index=0
                  controller-revision-hash=mysql-6759676ccb
                  statefulset.kubernetes.io/pod-name=mysql-0
Annotations:      <none>
Status:           Pending
IP:               10.244.0.29
IPs:
  IP:           10.244.0.29
Controlled By:  StatefulSet/mysql
Init Containers:
  init-mysql:
    Container ID:  
    Image:         mysql:8.0
    Image ID:      
    Port:          <none>
    Host Port:     <none>
    Command:
      bash
      -c
      set -ex
      [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
      ordinal=${BASH_REMATCH[1]}
      echo [mysqld] > /mnt/conf.d/server-id.cnf
      echo server-id=$((ordinal + 1)) >> /mnt/conf.d/server-id.cnf
      if [[ $ordinal -gt 0 ]]; then
        cat << EOF >> /mnt/conf.d/repl.cnf
        [mysqld]
        gtid_mode=ON
        enforce-gtid-consistency=ON
        EOF
      fi
      if [[ $ordinal -eq 0 ]]; then
        cat << EOF >> /mnt/conf.d/master.cnf
        [mysqld]
        gtid_mode=ON
        enforce-gtid-consistency=ON
        log-bin=mysql-bin
        binlog_format=ROW
        EOF
      fi
    State:          Waiting
      Reason:       ImagePullBackOff
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /mnt/conf.d/ from conf (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-l2pw2 (ro)
  clone-mysql:
    Container ID:  
    Image:         busybox
    Image ID:      
    Port:          <none>
    Host Port:     <none>
    Command:
      /bin/sh
      -c
      if [ ! -d /var/lib/mysql-k8s/mysql ]; then mkdir -p /var/lib/mysql-k8s; fi
    State:          Waiting
      Reason:       PodInitializing
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/lib/mysql-k8s from data (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-l2pw2 (ro)
Containers:
  mysql:
    Container ID:  
    Image:         mysql:8.0
    Image ID:      
    Port:          3307/TCP
    Host Port:     0/TCP
    Args:
      --datadir=/var/lib/mysql-k8s
      --default-authentication-plugin=mysql_native_password
    State:          Waiting
      Reason:       PodInitializing
    Ready:          False
    Restart Count:  0
    Environment:
      MYSQL_ROOT_PASSWORD:  password123
    Mounts:
      /docker-entrypoint-initdb.d from config-map (rw)
      /etc/mysql/conf.d-k8s from conf (rw)
      /var/lib/mysql-k8s from data (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-l2pw2 (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True 
  Initialized                 False 
  Ready                       False 
  ContainersReady             False 
  PodScheduled                True 
Volumes:
  data:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  data-mysql-0
    ReadOnly:   false
  conf:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:     
    SizeLimit:  <unset>
  config-map:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      mysql-config
    Optional:  false
  kube-api-access-l2pw2:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason            Age                    From               Message
  ----     ------            ----                   ----               -------
  Warning  FailedScheduling  4m43s                  default-scheduler  0/1 nodes are available: pod has unbound immediate PersistentVolumeClaims. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling.
  Normal   Scheduled         4m42s                  default-scheduler  Successfully assigned default/mysql-0 to minikube
  Normal   Pulling           2m21s (x4 over 4m40s)  kubelet            Pulling image "mysql:8.0"
  Warning  Failed            95s (x4 over 4m25s)    kubelet            Failed to pull image "mysql:8.0": Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
  Warning  Failed            95s (x4 over 4m25s)    kubelet            Error: ErrImagePull
  Warning  Failed            81s (x6 over 4m24s)    kubelet            Error: ImagePullBackOff
  Normal   BackOff           68s (x7 over 4m24s)    kubelet            Back-off pulling image "mysql:8.0"
  
这里明显的可以看到一个问题,镜像拉不下来,其实这个问题的主要原因是,执行 kubectl apply 的时候,会在 minikube 创建的容器环境下执行,在这个 minikube 的容器环境下还有一套 dcoker 环境,但是这个dockr环境目前还不能直接和宿主机的docker 环境一样访问hub docker 仓库,官方文档说是可以设置代理,然而并没有什么用,所以唯一的方法就是在宿主机上拉取镜像到本地,如下:
$ docker pull mysql:8.0
8.0: Pulling from library/mysql
2c0a233485c3: Pull complete 
b746eccf8a0b: Pull complete 
570d30cf82c5: Pull complete 
c7d84c48f09d: Pull complete 
e9ecf1ccdd2a: Pull complete 
6331406986f7: Pull complete 
f93598758d10: Pull complete 
6c136cb242f2: Pull complete 
d255d476cd34: Pull complete 
dbfe60d9fe24: Pull complete 
9cb9659be67b: Pull complete 
Digest: sha256:d58ac93387f644e4e040c636b8f50494e78e5afc27ca0a87348b2f577da2b7ff
Status: Downloaded newer image for mysql:8.0
docker.io/library/mysql:8.0
  
minikube image load mysql:8.0 //必须要执行这个,把宿主机上拉取的镜像加载到 minikube的docker环境,包括后面的 busybox 也有相似的问题,我直接限定了 busybox:1.36 版本的镜像,然后以相同的方法处理镜像下载的问题,这里固定版本的好处,方便开版本的一致,最好不要使用latest,因为那样会变化。
  
3>
$ kubectl get pods
NAME              READY   STATUS    RESTARTS   AGE
mysql-cluster-0   1/1     Running   0          18s
mysql-cluster-1   1/1     Running   0          14s
mysql-cluster-2   1/1     Running   0          7s
  
这个是正常的,说明成功了。
  
这个:
$ kubectl get statefulsets
NAME            READY   AGE
mysql-cluster   3/3     115m
  
也是正常的了.
  
注意每次处理完错误之后,需要重新apply这个yaml文件,但是需要先执行如下命令:
$ kubectl delete -f mysql-rep-cluster.yaml
  
如果不删除的话,可能会报如下错误:
statefulset.apps/mysql-cluster configured
The Service "mysql-cluster" is invalid: spec.clusterIPs[0]: Invalid value: "None": may not be set to 'None' for NodePort services
  
然后再次执行如下命令:
$ kubectl apply -f mysql-rep-cluster.yaml
  
4.准备配置主从
1>先进入主节点
$ kubectl exec -it mysql-cluster-0 -- mysql -uroot -p  //这里把 mysql-cluster-0 当作主节点
Enter password: 
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
command terminated with exit code 1
  
2>.查看日志
$ kubectl logs mysql-cluster-0
2024-12-27 15:02:30+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.40-1.el9 started.
2024-12-27 15:02:45+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
2024-12-27 15:02:45+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.40-1.el9 started.
2024-12-27T15:02:45.731465Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.40) starting as process 1
2024-12-27T15:02:45.731504Z 0 [ERROR] [MY-010338] [Server] Can't find error-message file '/usr/share/mysql-8.0/errmsg.sys'. Check error-message file location and 'lc-messages-dir' configuration directive.
2024-12-27T15:02:46.140549Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
2024-12-27T15:02:53.781089Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
2024-12-27T15:02:58.264090Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed.
2024-12-27T15:02:58.264138Z 0 [System] [MY-013602] [Server] Channel mysql_main configured to support TLS. Encrypted connections are now supported for this channel.
2024-12-27T15:02:58.443162Z 0 [Warning] [MY-011810] [Server] Insecure configuration for --pid-file: Location '/var/lib/mysql' in the path is accessible to all OS users. Consider choosing a different directory.
2024-12-27T15:02:58.794415Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Bind-address: '::' port: 33060, socket: /var/run/mysqld/mysqlx.sock
2024-12-27T15:02:58.794503Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.40'  socket: '/var/lib/mysql/mysql.sock'  port: 3306  MySQL Community Server - GPL.
  
3>.通过/var/lib/mysql/mysql.sock sock文件连接 (默认的是 /var/run/mysqld/mysqld.sock, 但是集群配置的不是这个路径)
$ kubectl exec -it mysql-cluster-0 -- mysql -uroot -p --socket=/var/lib/mysql/mysql.sock
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.40 MySQL Community Server - GPL
  
Copyright (c) 2000, 2024, Oracle and/or its affiliates.
  
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
  
4>.创建一个复制用户
CREATE USER 'replicator'@'%' IDENTIFIED BY 'replica_password';
GRANT REPLICATION SLAVE ON *.* TO 'replicator'@'%';
FLUSH PRIVILEGES;
  
mysql> show master status\G
*************************** 1. row ***************************
             File: mysql-bin.000002
         Position: 2095
     Binlog_Do_DB: 
 Binlog_Ignore_DB: 
Executed_Gtid_Set: 
1 row in set (0.00 sec)
  
5>进入第一个从节点
$ kubectl exec -it mysql-cluster-1 -- mysql -uroot -p --socket=/var/lib/mysql/mysql.sock
mysql> SHOW VARIABLES LIKE 'server_id';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| server_id     | 2     |
+---------------+-------+
1 row in set (0.02 sec)

mysql> SHOW VARIABLES LIKE 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin       | ON    |
+---------------+-------+
1 row in set (0.01 sec)
  
之前的错误配置导致server_id=1,导致很多问题
  
然后配置主从同步账号:
mysql> CHANGE MASTER TO 
    ->     MASTER_HOST='10.244.0.93',
    ->     MASTER_USER='replica_user',
    ->     MASTER_PASSWORD='replica_password',
    ->     MASTER_LOG_FILE='mysql-bin.000002',
    ->     MASTER_LOG_POS=843;
Query OK, 0 rows affected, 8 warnings (0.81 sec)

mysql> START SLAVE;
Query OK, 0 rows affected, 1 warning (0.29 sec)
  
mysql> SHOW SLAVE STATUS\G
主要看下面两个参数是不是Yes,说明同步配置的没问题,如果不是,就说明有问题
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
  
下麦年就是有问题的:
最开始我的写法如下
mysql> CHANGE MASTER TO
    ->   MASTER_HOST='mysql-cluster-0.mysql.default.svc.cluster.local',
    ->   MASTER_USER='replica_user',
    ->   MASTER_PASSWORD='replica_password',
    ->   MASTER_LOG_FILE='mysql-bin.000001',  # 
    ->   MASTER_LOG_POS=4;
  
然后slave的状态:
mysql> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
               Slave_IO_State: Connecting to source
                  Master_Host: mysql-cluster-0.mysql.default.svc.cluster.local
                  Master_User: replica_user
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000001
          Read_Master_Log_Pos: 4
               Relay_Log_File: mysql-cluster-1-relay-bin.000001
                Relay_Log_Pos: 4
        Relay_Master_Log_File: mysql-bin.000001
             Slave_IO_Running: Connecting
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 4
              Relay_Log_Space: 157
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: NULL
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 2005
                Last_IO_Error: Error connecting to source 'replica_user@mysql-cluster-0.mysql.default.svc.cluster.local:3306'. This was attempt 1/86400, with a delay of 60 seconds between attempts. Message: Unknown MySQL server host 'mysql-cluster-0.mysql.default.svc.cluster.local' (-2)
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 0
                  Master_UUID: 
             Master_Info_File: mysql.slave_master_info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Replica has read all relay log; waiting for more updates
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 241227 16:53:13
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: 
                Auto_Position: 0
         Replicate_Rewrite_DB: 
                 Channel_Name: 
           Master_TLS_Version: 
       Master_public_key_path: 
        Get_master_public_key: 0
            Network_Namespace: 
1 row in set, 1 warning (0.00 sec)
  
这里有个 Last_IO_Error,就是不能连接master的问题,然后不管我怎么修改MASTER_HOST,都有问题,最后才找到下面的答案:
DNS 解析的主机名问题:
mysql-cluster-0.mysql.default.svc.cluster.local 是通过 ClusterIP 类型的 Service 提供的 DNS 解析。当 Service 类型为 NodePort 时,它不直接支持这种集群内的主机名解析。
  
跨 Pod 通信的问题:
NodePort 主要用于从集群外部访问服务,而不是集群内部 Pod 的通信。在 StatefulSet 的场景中,主从之间的通信通常依赖于 ClusterIP。
  
因为我最开始是将service 配置的NodePort类型,所以一直有问题,后来改了还几个版本,使用ClusterIP类型才解决问题
  
最后我决定直接查看master的ip,将host配置为ip,如下:
$ kubectl describe svc mysql-cluster
Name:                     mysql-cluster
Namespace:                default
Labels:                   <none>
Annotations:              <none>
Selector:                 app=mysql
Type:                     ClusterIP
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.102.80.128
IPs:                      10.102.80.128
Port:                     <unset>  3306/TCP
TargetPort:               3306/TCP
Endpoints:                10.244.0.93:3306,10.244.0.94:3306,10.244.0.95:3306
Session Affinity:         None
Internal Traffic Policy:  Cluster
Events:                   <none>
  
10.244.0.93 就是master的ip,但是这样有个缺点,就是ip可能会变化,所以配置成 mysql-cluster-0.mysql.default.svc.cluster.local 最好,但是好像还是有问题,关于这个问题还需要继续探讨...
  
当配置为ip之前,执行如下操作:
mysql> stop slave;
Query OK, 0 rows affected, 1 warning (0.19 sec)
  
然后:
mysql> CHANGE MASTER TO 
    ->     MASTER_HOST='10.244.0.93',
    ->     MASTER_USER='replica_user',
    ->     MASTER_PASSWORD='replica_password',
    ->     MASTER_LOG_FILE='mysql-bin.000002',
    ->     MASTER_LOG_POS=843;
Query OK, 0 rows affected, 8 warnings (0.81 sec)
  
mysql> start slave;
Query OK, 0 rows affected, 1 warning (0.19 sec)
  
mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for source to send event
                  Master_Host: 10.244.0.93
                  Master_User: replica_user
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000002
          Read_Master_Log_Pos: 843
               Relay_Log_File: relay-log-bin.000002
                Relay_Log_Pos: 326
        Relay_Master_Log_File: mysql-bin.000002
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 843
              Relay_Log_Space: 534
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 1
                  Master_UUID: 16b0a150-c397-11ef-ba7e-c6314bb59e6f
             Master_Info_File: mysql.slave_master_info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Replica has read all relay log; waiting for more updates
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: 
                Auto_Position: 0
         Replicate_Rewrite_DB: 
                 Channel_Name: 
           Master_TLS_Version: 
       Master_public_key_path: 
        Get_master_public_key: 0
            Network_Namespace: 
1 row in set, 1 warning (0.00 sec)
  
现在都是Yes了,基本可以确定主从配置完成,对第二个从节点也是类似的操作

  
[一天之后],我重新启动宿主机,发现 show slave status\G 报如下错误:
Last_IO_Error: Error connecting to source 'replica_user@10.244.0.93:3306'. This was attempt 10/86400, with a delay of 60 seconds between attempts. Message: Can't connect to MySQL server on '10.244.0.93:3306' (113)

果真,重启服务器之后,master 的 ip 发生了变化, slave 无法连接,所以这个 master_host 要配置为名称,经过我的测试,改为如下配置就可以:
mysql> CHANGE MASTER TO 
    ->     MASTER_HOST='mysql-cluster-0.mysql',
    ->     MASTER_USER='replica_user',
    ->     MASTER_PASSWORD='replica_password',
    ->     MASTER_LOG_FILE='mysql-bin.000003',
    ->     MASTER_LOG_POS=157;
  
配置为 mysql-cluster-0.mysql, 而不是 mysql-cluster-0.mysql.default.svc.cluster.local,
再次查看:
mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for source to send event
                  Master_Host: mysql-cluster-0.mysql
                  Master_User: replica_user
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000003
          Read_Master_Log_Pos: 472
               Relay_Log_File: relay-log-bin.000002
                Relay_Log_Pos: 326
        Relay_Master_Log_File: mysql-bin.000003
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 472
              Relay_Log_Space: 534
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 1
                  Master_UUID: 16b0a150-c397-11ef-ba7e-c6314bb59e6f
             Master_Info_File: mysql.slave_master_info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Replica has read all relay log; waiting for more updates
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: 
                Auto_Position: 0
         Replicate_Rewrite_DB: 
                 Channel_Name: 
           Master_TLS_Version: 
       Master_public_key_path: 
        Get_master_public_key: 0
            Network_Namespace: 
1 row in set, 1 warning (0.00 sec)


  
数据验证:
主:
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| testdb             |
+--------------------+
5 rows in set (0.00 sec)
  
mysql> 
mysql> 
mysql> use testdb;
Database changed
mysql> 
mysql> show tables;
Empty set (0.00 sec)
  
mysql> 
mysql> 
mysql> CREATE TABLE users (
    ->     id INT PRIMARY KEY AUTO_INCREMENT,
    ->     name VARCHAR(50),
    ->     created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    -> );
Query OK, 0 rows affected (1.13 sec)
  
mysql> 
mysql> 
mysql> INSERT INTO users (name) VALUES ('test1'), ('test2');
Query OK, 2 rows affected (0.14 sec)
Records: 2  Duplicates: 0  Warnings: 0
  
mysql> SELECT * FROM users;
+----+-------+---------------------+
| id | name  | created_at          |
+----+-------+---------------------+
|  1 | test1 | 2024-12-28 14:13:19 |
|  2 | test2 | 2024-12-28 14:13:19 |
+----+-------+---------------------+
2 rows in set (0.00 sec)


  
从:
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| testdb             |
+--------------------+
5 rows in set (0.00 sec)
  
mysql> 
mysql> 
mysql> use testdb;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
  
Database changed
mysql> 
mysql> 
mysql> show tables;
+------------------+
| Tables_in_testdb |
+------------------+
| users            |
+------------------+
1 row in set (0.00 sec)
  
mysql> 
mysql> 
mysql> select * from users;
+----+-------+---------------------+
| id | name  | created_at          |
+----+-------+---------------------+
|  1 | test1 | 2024-12-28 14:13:19 |
|  2 | test2 | 2024-12-28 14:13:19 |
+----+-------+---------------------+
2 rows in set (0.00 sec)

  
从库用户设置:
设置了read_only的slave节点:
mysql> SHOW VARIABLES LIKE 'read_only';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| read_only     | ON    |
+---------------+-------+
1 row in set (0.00 sec)
  
mysql> SHOW VARIABLES LIKE 'super_read_only';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| super_read_only | OFF   |
+-----------------+-------+
1 row in set (0.00 sec)

  
发现可以删除数据:
mysql> select * from users;
+----+-------+---------------------+
| id | name  | created_at          |
+----+-------+---------------------+
|  1 | test1 | 2024-12-28 14:13:19 |
|  3 | test3 | 2024-12-28 14:17:24 |
|  4 | test4 | 2024-12-28 14:17:24 |
+----+-------+---------------------+
3 rows in set (0.00 sec)

  
mysql> delete from users where id =3;
Query OK, 1 row affected (0.09 sec)
  
mysql> 
mysql> 
mysql> select * from users;
+----+-------+---------------------+
| id | name  | created_at          |
+----+-------+---------------------+
|  1 | test1 | 2024-12-28 14:13:19 |
|  4 | test4 | 2024-12-28 14:17:24 |
+----+-------+---------------------+
2 rows in set (0.00 sec)

  
创建一个普通用户:
CREATE USER 'readonly_user1'@'%' IDENTIFIED BY 'password_read_only';
GRANT SELECT ON *.* TO 'readonly_user1'@'%';
FLUSH PRIVILEGES;
  
SHOW GRANTS FOR 'readonly_user1'@'%';
  
mysql> CREATE USER 'readonly_user1'@'%' IDENTIFIED BY 'password_read_only';
Query OK, 0 rows affected (0.09 sec)
  
mysql> GRANT SELECT ON *.* TO 'readonly_user1'@'%';
Query OK, 0 rows affected (0.07 sec)
  
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.02 sec)
  
mysql> SHOW GRANTS FOR 'readonly_user1'@'%';
+---------------------------------------------+
| Grants for readonly_user1@%                 |
+---------------------------------------------+
| GRANT SELECT ON *.* TO `readonly_user1`@`%` |
+---------------------------------------------+
1 row in set (0.00 sec)


  
使用这个普通用户登陆:
$ kubectl exec -it mysql-cluster-2 -- mysql -ureadonly_user1 -p --socket=/var/lib/mysql/mysql.sock
  
mysql> delete from users where id = 4;
ERROR 1290 (HY000): The MySQL server is running with the --read-only option so it cannot execute this statement
  
现在普通用户不能删除数据,符合需求. 对于普通用户 read_only 设置会生效,如果设置了ON, 那么这个用户只能取数据。但是对于 super user 比如 root 是不生效的,super user 是由 super_read_only 来决定的。


  
资源查看:
$ kubectl get statefulsets
NAME            READY   AGE
mysql-cluster   3/3     174m

  
$ kubectl get svc
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
kubernetes      ClusterIP   10.96.0.1       <none>        443/TCP    18d
mysql           ClusterIP   None            <none>        3306/TCP   174m
mysql-cluster   ClusterIP   10.102.80.128   <none>        3306/TCP   174m

  
$ kubectl get pods
NAME              READY   STATUS    RESTARTS   AGE
mysql-cluster-0   1/1     Running   0          175m
mysql-cluster-1   1/1     Running   0          175m
mysql-cluster-2   1/1     Running   0          175m

  
linux命令补充:
vim批量删除操作:
批量部分删除
v 选行,然后 d
  
批量全部删除
:%d