前言

        好久没写文章了,最近由于需要在云平台搭建一个K8s集群,并在K8s上部署一个MySQL集群,在这个过程中遇到了挺多的问题,想着还是写一下吧,毕竟这些问题都挺麻烦的还。虽然有些解决的也不是很好吧,但勉强把服务搭起来了。这两篇文章重在记录搭建过程中遇到的各种问题。安装过程简单介绍。本篇文章将介绍MySQL部分,K8s部分的内容参考我的另一篇文章,这里假设大家已经都部署好了K8s集群。

K8s集群部署MySQL

1. 安装nfs

1.1 安装软件

在所有节点中执行

yum -y install nfs-utils rpcbind
systemctl enable rpcbind && systemctl start nfs
systemctl enable nfs-server && systemctl start rpcbind

1.2 共享文件

# 创建共享目录
mkdir /home/data
# 设置环境变量
vi /etc/exports
# 添加以下内容
/home/data *(insecure,rw,sync,no_subtree_check,no_root_squash)
# 重启服务
systemctl restart nfs rpcbind
#查看共享目录
showmount -e

1.3 在slaver节点上挂载

#创建共享目录
mkdir -p ~/volume/node1
# 挂载
mount -t nfs nfs服务器的IP地址:/home/data ~/volume/node1
# 查看挂载结果
df -h

2. 使用nfs实现持久化

2.1 rbac.yaml

应该是没有要修改的内容

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io

部署rbac配置文件

kubectl apply -f rbac.yaml

2.2 deployment.yaml

修改nfs服务器的地址和共享文件夹

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: quay.io/external_storage/nfs-client-provisioner:latest
          volumeMounts:
            - name: nfs-client-root
              mountPath: /home/data
          env:
            - name: PROVISIONER_NAME
              value: nfs-diy # StorageClass 三个重要字段之一 Provisioner,名字自己指定
            - name: NFS_SERVER
              value: 192.168.123.4 # nfs 服务器地址
            - name: NFS_PATH
              value: /home/data # nfs 共享文件夹
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.123.4 # nfs 服务器地址
            path: /home/data # nfs 共享文件夹

部署Deployment

kubectl apply -f deployment.yaml

2.3 class.yaml

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-storage # 这个名字很重要,后面要用到
provisioner: nfs-diy # 和 deployment.yaml 中 env.PROVISIONER_NAME 保持一致
reclaimPolicy: Retain
parameters:
  archiveOnDelete: "false"

部署class

kubectl apply -f class.yaml

3. 部署MySQL

note:关于在K8s上部署MySQL集群我们确实能搜到不少文章,包括官方也都有提供一些,但是这些文章里提供的配置文件,不知道是因为何种原因,在我这里总是出现各种各样的错误。最后我们在mysql主从同步(k8s版)的基础上进行了些改动,终于是跑通了。

3.1 namespace.yaml

在K8s中创建一个新的命名空间mysql

apiVersion: v1
kind: Namespace
metadata:
  name: mysql
  labels:
    app: mysql

部署namespace.yaml

kubectl apply -f namespace.yaml

3.2 secret.yaml

用于数据库连接的账号和密码,这里设置为root和123456

apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
  namespace: mysql
  labels:
    app: mysql
type: Opaque
data:
  password: MTIzNDU2 # echo -n "123456" | base64

部署secret.yaml

kubectl apply -f secret.yaml

3.3 mysql-configmap.yaml

主从数据库的权限配置文件

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
  namespace: mysql
  labels:
    app: mysql
data:
  master.cnf: |
    # Master
    [mysqld]
    log-bin=mysqllog
    skip-name-resolve
  slave.cnf: |
    # Slave
    [mysqld]
    super-read-only
    skip-name-resolve
    log-bin=mysql-bin
    replicate-ignore-db=mysql

部署mysql-configmap.yaml

kubectl apply -f mysql-configmap.yaml

3.4 mysql-service.yaml

在这里可以设置各个端口

apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: mysql
  labels:
    app: mysql
spec:
  type: NodePort
  ports:
  - name: mysql
    port: 3306
    nodePort: 30002 #对外暴露的端口
  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

部署mysql-service.yaml

kubectl apply -f mysql-service.yaml

3.5 mysql-statefulset.yaml

这是最复杂的一个配置文件,也是遇到问题最多的一个。这里给出的已经是调整过之后的版本,如果大家拿过去用有什么问题,可以参考原版中对应的内容。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: mysql
  labels:
    app: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql
  replicas: 2 # 副本数量,集群中的数量
  template:
    metadata:
      labels:
        app: mysql
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:5.7.33
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
        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
          # 由于 server-id 不能为 0,因此给 ID 加 100 来避开它
          echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
          # 如果 Pod 的序号为 0,说明它是 Master 节点,从 ConfigMap 里把 Master 的配置文件拷贝到 /mnt/conf.d 目录下
          # 否则,拷贝 ConfigMap 里的 Slave 的配置文件
          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: gcr.io/google-samples/xtrabackup:1.0
        image: registry.cn-shenzhen.aliyuncs.com/jbjb/csi:xtrabackup-1.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
        command:
        - bash
        - "-c"
        - |
          set -ex
          # 拷贝操作只需要在第一次启动时进行,所以数据已经存在则跳过
          [[ -d /var/lib/mysql/mysql ]] && exit 0
          # Master 节点(序号为 0)不需要这个操作
          [[ $(hostname) =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          [[ $ordinal == 0 ]] && exit 0
          # 使用 ncat 指令,远程地从前一个节点拷贝数据到本地
          ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
          # 执行 --prepare,这样拷贝来的数据就可以用作恢复了
          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.33
        env:
#        - name: MYSQL_ALLOW_EMPTY_PASSWORD
#          value: "1"
        - 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: gcr.io/google-samples/xtrabackup:1.0
        image: registry.cn-shenzhen.aliyuncs.com/jbjb/csi: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
          # 从备份信息文件里读取 MASTER_LOG_FILE 和 MASTER_LOG_POS 这 2 个字段的值,用来拼装集群初始化 SQL
          if [[ -f xtrabackup_slave_info ]]; then
            # 如果 xtrabackup_slave_info 文件存在,说明这个备份数据来自于另一个 Slave 节点
            # 这种情况下,XtraBackup 工具在备份的时候,就已经在这个文件里自动生成了 "CHANGE MASTER TO" SQL 语句
            # 所以,只需要把这个文件重命名为 change_master_to.sql.in,后面直接使用即可
            mv xtrabackup_slave_info change_master_to.sql.in
            # 所以,也就用不着 xtrabackup_binlog_info 了
            rm -f xtrabackup_binlog_info
          elif [[ -f xtrabackup_binlog_info ]]; then
            # 如果只是存在 xtrabackup_binlog_info 文件,说明备份来自于 Master 节点,就需要解析这个备份信息文件,读取所需的两个字段的值
            [[ $(cat xtrabackup_binlog_info) =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
            rm xtrabackup_binlog_info
            # 把两个字段的值拼装成 SQL,写入 change_master_to.sql.in 文件
            echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\
                  MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
          fi
          # 如果存在 change_master_to.sql.in,就意味着需要做集群初始化工作
          if [[ -f change_master_to.sql.in ]]; then
            # 但一定要先等 MySQL 容器启动之后才能进行下一步连接 MySQL 的操作
            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"
            # 将文件 change_master_to.sql.in 改个名字
            # 防止这个 Container 重启的时候,因为又找到了 change_master_to.sql.in,从而重复执行一遍初始化流程
            mv change_master_to.sql.in change_master_to.sql.orig
            # 使用 change_master_to.sql.orig 的内容,也就是前面拼装的 SQL,组成一个完整的初始化和启动 Slave 的 SQL 语句
            mysql -h 127.0.0.1 -uroot -p${MYSQL_ROOT_PASSWORD} << EOF
          $(< change_master_to.sql.orig),
            MASTER_HOST='mysql-0.mysql.mysql',
            MASTER_USER='root',
            MASTER_PASSWORD='${MYSQL_ROOT_PASSWORD}',
            MASTER_CONNECT_RETRY=10;
          START SLAVE;
          EOF
          fi
          # 使用 ncat 监听 3307 端口。
          # 它的作用是,在收到传输请求的时候,直接执行 xtrabackup --backup 命令,备份 MySQL 的数据并发送给请求者
          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
      #annotations:
        #volume.beta.kubernetes.io/storage-class: nfs-csi
    spec:
      accessModes:
      - "ReadWriteOnce"
      storageClassName: nfs-storage #需要和你自己创建的class.yaml保持名称一致
      resources:
        requests:
          storage: 5Gi

部署mysql-statefulset.yaml

kubectl apply -f mysql-statefulset.yaml

3.6 成果检验

查看容器数目

kubectl get pods -n mysql

在这里插入图片描述
检验主从状态

kubectl -n mysql exec mysql-1 -c mysql -- bash -c "mysql -uroot -p123456 -e 'show slave status \G'"

在这里插入图片描述
通过主库访问,创建表,插入数据

kubectl exec -it mysql-0 -n mysql /bin/bash
mysql -uroot -p123456
create database test;
use test;
create table table1(Name text);
insert into table1 values("Tom");

通过从库访问,获取插入的数据

kubectl exec -it mysql-1 -n mysql /bin/bash
mysql -uroot -p123456
use test;
select * from table1;

4. 遇到的问题

note: 首先是我们在查错的时候常用的一些命令

# 查看pod信息
kubectl describe pods/pod的名称 -n namespace的名称 
#如:
kubectl describe pods/mysql-0 -n mysql
# 查看pods日志
kubectl logs pod名称 -n namespace名称 --container pods内的container的名称 
#如:
kubectl logs mysql-0 -n mysql --container init-mysql

4.1 failed, reason given by server: No such file or directory

问题描述:failed, reason given by server: No such file or directory
在这里插入图片描述
这个问题目前来看只有我遇到了,没有看到其他人有类似的经历,解决办法也很简单,直接手动创建这个缺失的文件

mkdir /home/data/文件名

4.2 Unable to attach or amount volume: unmounnted volume=[data]……

问题描述:Unable to attach or amount volume: unmounnted volume=[data], unattached volume= [ data config kube-api-access-phjxb]: timed out waiting for the condition
pulling image “docker.io/bitnami/mysql:8.0.29-debin-11-r9”
在这里插入图片描述

这个问题出现的也很玄学,是在我使用了一个8.0版本的mysql时出现的,更换镜像版本后,该问题随之消失。

建议大家还是使用5.7版本的吧,问题少。

4.3 failed to create subpath directory for volumeMount “data” of container “clone-mysql”

问题描述:failed to create subpath directory for volumeMount “data” of container “clone-mysql”
在这里插入图片描述
子路径创建失败,怀疑是nfs权限设置的问题,但是修改之后无效,这里直接在statefulset.yaml里删除subpath就可以解决。

4.4 找不到hostname

这里忘了截图了,先看下这张吧
在这里插入图片描述
这个问题出现的也很奇怪,同样是在statefuleset.yaml里,在创建容器时command命令里我们用到了hostname命令,但是却提示找不到,看遍网上各个教程,都这么写的,大家也都没有遇到这个问题。好在它的作用好像不是很大,直接注释掉就解决了。

4.5 changing ownership of ‘var/lib/mysql/’: Operation not permitter

问题描述:changing ownership of ‘var/lib/mysql/’: Operation not permitter
在这里插入图片描述
这个问题是nfs权限的问题,修改/etc/exports,在里面添加no_root_squash,重启nfs服务解决

4.6 nodes are avilable: 2 pod has unboud immedite PersistentVolumeCLaims.

问题描述:nodes are avilable: 2 pod has unboud immedite PersistentVolumeCLaims.
在这里插入图片描述
节点挂载失败,没有持久化。
修改/etc/kubernetes/mainfests/kube-apiserver.yaml,添加图中这一行
在这里插入图片描述

4.7 镜像下载失败

这个忘了截图了。也是statefulest.yaml里的错误,由于众所周知的原因,镜像下载失败,修改文件,换成相应的国内镜像就行。

参考

[1] K8S 配置 storageclass 使用 nfs 动态申领本地磁盘空间
[2] k8s入门:存储(storage)
[3] K8S 配置 storageclass 使用 nfs 动态申领本地磁盘空间
[4] mysql主从同步(k8s版)

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐