K8S 实战系列: MySQL 主从

[TOC]

5 MySQL 主从

完整项目地址:https://github.com/As9530272755/k8sOfMysql

5.1 什么是 statefulset

我们之前也说过,对于我们的 docker 来说更适合的是运行我们的无状态服务。那有状态服务怎么办呢,docker 给我们的解决方案是一个以存储卷的方案去加载对应的数据,但是在 K8S 里它不仅有存储卷,还给了我们一个特殊的控制器叫 statefulset ,去完成我们的有状态服务的解决。其实现在还是有很多有状态的应用程序不方便去部署进我们的 statefulset 中,或是不方便部署进我们的 k8s 中。比如我们的 mysql ,其实至今没有一个太好的解决方案可以把 mysql 这么一个有状态服务部署进我们的 K8s。

statefulset 作为 controller 为 pod 提供唯一的标识。它可以保证部署和 scale(比例) 的顺序

statefulset 是为了解决有状态服务的问题 (对应 Deployments 和 ReplicaSets 是为无状态服务而设计),其应用场景包括:

  • 稳定的持久化存储:即 pod 重新调度后还是能够访问到相同的持久化数据,基于 PVC 来实现。假设现在我有几个我们的容器,是被我们的 statefulset 去部署的,部署完成以后呢可能都用到了我们同一个存储卷,假如有一天这个 pod 死亡了,但我们的 statefulset 为了维持这个副本数就会重新创建对应的 pod ,这时候新创建的 pod 也会继续使用上一个 pod 退出时使用到的存储卷。也就这里的持久化数据并不会丢失,这就是我们所谓的稳定的持久化存储方案。

  • 稳定的网络标识:即 pod 重新调度以后其 pod name 和 host name 不变(这个很重要,在我们的很多服务里我们去创建对应的一些服务的链接方案的时候,都会以我们的 pod name 为链接对象,或者 host name 为链接对象,如果我们的 pod 死亡被重新创建以后它的名称发生改变了这个对于我们后面的自动化流程来说时非常不友好的。但是对于我们的 statefulset 来说它可以确保每一个 pod 它的 pod name 和 host name 在 statefulset 生命周期里完全不变),基于 headless service 无头服务实现(也就是没有 cluster IP 的 service,可以理解为没有 IP 地址和端口 )来实现

  • 有序部署,有序扩展:即 pod 是有顺序的,在部署或者扩展的时候要依据定义的顺序依次进行(即从 0 到 N1,在下一个 pod 运行之前所有的 pod 必须是 running 和 ready 状态),基于 init containers 来实现。好这里需要注意以下为啥是基于 init containers 而不是基于我们所谓的 start 或 stop ,原因是不管是 start 、stop 他都需要去更改我们 pod 内部的容器镜像,但是对于 init containers 初始化容器来说是不需要更改的,只需要在 statefulset 的 pod 里面的容器运行之前加一个 init C ,并不会对我们的原有 pod 结构发生改变。所以这是他的解决方案怎么去实现这里的 running 和 ready。

  • 有序收缩,有序删除(即从 N1 到 0):会发现在扩展部署的时候是 0 到 N1 的,在删除的时候 N1 到 0 的,这是一个倒序关系。我们可以观察下图:

现在我有这么一个结构,底下是我们一个数据库 mysql ,上面运行了我们一些 php-fpm,再上面运行了我们的一个 nginx 实现了我们的一个反向代理,这样的一个结构我们会发现在部署的时候应该从底往上部署。也就意味着他是先部署我们的 mysql –> php-fpm –> nginx ,这是我们的部署顺序。那如果我们想把这个服务停止的话它的顺序正好是相反的,我们应该先停我们的 nginx –> php-fpm –> mysql ,这时候有可能有的朋友就会想为什么不是先停我们的 mysql , 假如我们先把 mysql 停了,那这里的 php-fpm 和 nginx 还是在运行状态。用户的请求有可能是会到我们的 nginx 那就有可能到我们的 php-fpm 就有可能报错。所以它的部署顺序和它的启动顺序恰好是相反的。

  • 为了解决有状态服务问题,也就是服务本身是有集群关系,同步主从

  • 他所管理的 pod 拥有固定的 pod 名称,主机名,启停顺序

  • 创建一个 statefulset 类型的 pod , 并指定 serviceName ,创建 headless 类型的 svc

  • statefulset 类型启动 pod 一定会先起第一个然后再起后面的 pod ,他是有严格的启动顺序

statefulset 特点:

  • 给每个 pod 分配固定且唯一的网络标识符(pod 名称不会改变)
    • 比如是第启动了第一个 pod,那么这个 pod 的名称就是 podName-0 ,并且通过每个 pod 名字来访问 pod
  • 给每个 pod 分配固定且持久化的外部存储
    • 每个 pod 使用的 pv 和 pvc 是固定的绑定关系,并且存入到 ETCD 不管 pod 怎么删除都不会打乱这种绑定关系
    • 由此每个 pod 的数据是唯一的
  • 对 pod 进行有序的部署和扩展
    • 假如定义了 3 pod 副本就,如果前面的 pod0 没有起来那么后面的 pod1、pod2 就不会启动
  • 对 pod 进有序的删除和终止
    • 删除 pod 也是进行有序的删除,并且是从后往前面删除,由 podN 向 pod0 进行删除
  • 对 pod 进有序的自动滚动更新

5.2 如何实现 MySQL 主从

基于 statefulset 运行 MySQL 主从,服务的特殊性就在于我们的 mysql 是主从的那到底谁是主谁是从

  • 是一个“主从复制”(Maser-Slave Replication)的 MySQL 集群;
  • 有 1 个主节点(Master);
  • 有多个从节点(Slave);
  • 从节点需要能水平扩展;
  • 所有的写操作,只能在主节点上执行;
  • 读操作可以在所有节点上执行。

在常规环境里,部署这样一个主从模式的 MySQL 集群的主要难点在于:如何让从节点能够拥有主节点的数据,

即:如何配置主(Master)从(Slave)节点的复制与同步。

MySQL 主从注意事项:

我们将 mysql 做了读写分离的话就会让读操作通过从节点实现,而写操作就通过主节点实现

其他客户端也可以通过 svc 进行访问,但是我们要保证 svc 不能只有一个,而实每个 pod 有一个 svc 或者说 master 有对应的 svc ,slave 也有自己对应的 svc

如果一个 svc 的话后面就是绑定的所有 pod ,因为这些 pod 绑定的 svc labels 是一样的,但是这样的话就会导致数据不一致,读数据的话还好,如果是写入数据的本来是向主库写入,结果把这些请求转给了 slave 上。就会导致数据写入失败,即使 slave 写入成功也会出现数据的不一致

所以我们在写入数据的话直接都是写 pod 名称,直接通过 master 节点写入。而读取数据的话就是通过 salve 读取

因为我们都知道 statefulset + headless service,都是通过 DNS 解析,即使 pod 的 ip 发生变化但是依旧不会影响我们对 pod 的解析

  • statefulset(绑定唯一标识): 是用来管理有状态应用的工作负载 API 对象。

    StatefulSet 用来管理某 Pod 集合的部署和扩缩, 并为这些 Pod 提供持久存储和持久标识符。

    Deployment 类似, StatefulSet 管理基于相同容器规约的一组 Pod。但和 Deployment 不同的是, StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的, 但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。

    如果希望使用存储卷为工作负载提供持久存储,可以使用 StatefulSet 作为解决方案的一部分。 尽管 StatefulSet 中的单个 Pod 仍可能出现故障, 但持久的 Pod 标识符使得将现有卷与替换已失败 Pod 的新 Pod 相匹配变得更加容易。

  • headless service(通过 DNS 解析):有时不需要或不想要负载均衡,以及单独的 service IP 。遇到这种情况,可以通过指定 cluster IP(spec.cluster IP)的值为 none 来创建 headless service(无头服务)。这类的 service 并不会分配 cluster IP ,kube-proxy 不会处理他们,而且平台也不会为它们进行负载均衡和路由。而是使用 pod 的 ip

5.3 MySQL 结构图

  1. 一定要先启动一个 pod-0 为 master ,写数据就往该 pod 上写入
  2. 紧接着 pod-0 起来之后会启动 pod-1、pod-2 的 slave 节点,读数据的话就往 slave 节点上进行读取

而且我们需要两个镜像:

  • 一个是 MySQL 用来做基础镜像
  • 一个是 xtrabackup 用来做数据同步

5.4 操作流程

5.4.1 准备镜像

将下载下来的镜像上传至本地 harbor ,因为这样就不会让其他节点在到外网上下载

1.先准备 mysql:5.7 镜像

# 下载镜像
[16:32:11 root@k8s-master k8sOfMysql]#docker pull mysql:5.7

# 打 tag
[16:35:42 root@k8s-master k8sOfMysql]#docker tag docker.io/library/mysql:5.7 hub.zhangguiyuan.com/baseimage/mysql:5.7

# 上传镜像
[16:36:24 root@k8s-master k8sOfMysql]#docker push hub.zhangguiyuan.com/baseimage/mysql:5.7 

2.下载 xtrabackup 镜像

# 下载镜像
[16:44:24 root@k8s-master k8sOfMysql]#docker pull  registry.cn-hangzhou.aliyuncs.com/hxpdocker/xtrabackup:1.0

# 打 tag
[16:45:11 root@k8s-master k8sOfMysql]#docker tag  registry.cn-hangzhou.aliyuncs.com/hxpdocker/xtrabackup:1.0 hub.zhangguiyuan.com/baseimage/xtrabackup:1.0

# 上传
[16:45:31 root@k8s-master k8sOfMysql]#docker push hub.zhangguiyuan.com/baseimage/xtrabackup:1.0 

5.4.2 创建 NS

1.编写 yaml

[16:56:49 root@k8s-master mysqlYaml]#vim mysql-ns.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: mysql

2.创建

[16:58:04 root@k8s-master mysqlYaml]#kubectl apply -f mysql-ns.yaml

5.4.2 创建 pv

每个 pv 都是给每个不同的 pod 来使用,因为 mysql 主从 pod 的数据都是一个 pod 对应一个 pv ,工作中计划跑几个 pod 就创建几个 pv

1.编写 yaml 文件

[16:52:45 root@k8s-master pv]#vim mysql-persistentvolume.yaml 
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-datadir-1
  namespace: mysql
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteOnce
  nfs:
    path: /data/k8sdata/mysql/mysql-datadir-1 
    server: 10.0.0.133
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-datadir-2
  namespace: mysql
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteOnce
  nfs:
    path: /data/k8sdata/mysql/mysql-datadir-2
    server: 10.0.0.133
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-datadir-3
  namespace: mysql
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteOnce
  nfs:
    path: /data/k8sdata/mysql/mysql-datadir-3
    server: 10.0.0.133
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-datadir-4
  namespace: mysql
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteOnce
  nfs:
    path: /data/k8sdata/mysql/mysql-datadir-4
    server: 10.0.0.133
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-datadir-5
  namespace: mysql
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteOnce
  nfs:
    path: /data/k8sdata/mysql/mysql-datadir-5
    server: 10.0.0.133

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-datadir-6
  namespace: mysql
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteOnce
  nfs:
    path: /data/k8sdata/mysql/mysql-datadir-6
    server: 10.0.0.133

2.在 NFS 上创建挂载目录

[16:51:36 root@harbor-nfs ~]#mkdir -p /data/k8sdata/mysql/mysql-datadir-{1..6}

3.创建 pv

[17:06:25 root@k8s-master mysqlYaml]#kubectl apply -f mysql-persistentvolume.yaml 

5.4.3 创建 configmap

1.编写配置,这个 configmap 会挂到对应的 pod 上,后期我们想修改 mysql 的配置文件直接修改 configmap 即可

[16:58:08 root@k8s-master mysqlYaml]#vim mysql-configmap.yaml 

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
  namespace: mysql
  labels:
    app: mysql
data:
  # master 配置文件
  master.cnf: |
    # Apply this config only on the master.
    [mysqld]
    log-bin                             # 开启二进制日志
    log_bin_trust_function_creators=1   # MySQL不会对创建存储实施限制。
    lower_case_table_names=1            # 表名存储在磁盘是小写的,但是比较的时候是不区分大小写

  # slave 节点配置文件
  slave.cnf: |
    # Apply this config only on slaves.
    [mysqld]
    super-read-only                     # 只读
    log_bin_trust_function_creators=1

2.创建

[17:06:42 root@k8s-master mysqlYaml]#kubectl apply -f mysql-configmap.yaml 

5.4.4 创建 svc

这里我创建的 svc 都是基于 headless svc ,因为通过 dns 来解析,

[17:07:23 root@k8s-master mysqlYaml]#vim mysql-services.yaml 
# Headless service for stable DNS entries of StatefulSet members.
# 提供给 MySQL 的 master pod 用来实现写数据
apiVersion: v1
kind: Service
metadata:
  namespace: mysql
  name: mysql
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  clusterIP: None
  selector:
    app: mysql
---
# Client service for connecting to any MySQL instance for reads.
# For writes, you must instead connect to the master: mysql-0.mysql.

# 提供给 MySQL 的 slave pod 用来实现读取数据
apiVersion: v1
kind: Service
metadata:
  name: mysql-read
  namespace: mysql
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  selector:
    app: mysql

2.创建

[17:10:54 root@k8s-master mysqlYaml]#kubectl apply -f mysql-services.yaml 
service/mysql created
service/mysql-read created

5.4.5 创建 statefulset

通过这种方式,每次他的 slave pod 都会基于前面的一个 pod 来进行同步数据,除了 master pod 是自己生成的,如上图我在红框中标出的一样,他只会通过 pod-id -1 的 pod 来进行数据同步,这样的好处就是并不会给 master pod 增加太多的负载

  1. yaml 解析
[17:19:15 root@k8s-master mysqlYaml]#cat 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:
# 定义初始化容器,因为 mysql 在启动之前需要进行环境初始化,生成一些库之类的比如 user 表 mysql 库等等
      initContainers:
      - name: init-mysql
        image: hub.zhangguiyuan.com/baseimage/mysql:5.7 
        imagePullPolicy: IfNotPresent
# 在初始化容器中执行命令,给这个容器传递一些支持的命令,主要包含了下面操作:

        command:
        - bash
        - "-c"
        - |
          set -ex
          # 1.先判断 hostname 是否数字结尾
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1

          # 2.通过 BASH_REMATCH 取到第一个数,用下面对 pod 的角色判断
          ordinal=${BASH_REMATCH[1]}

          # 3.往 server-id.cnf 写入 [mysqld]
          echo [mysqld] > /mnt/conf.d/server-id.cnf

          # 4.添加偏移量以避免保留服务器 id=0 的值。因为 server-id 一定要唯一
          echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
          # 5.如果 ordinal 变量等于 0 就 cp master.cnf 
          if [[ $ordinal -eq 0 ]]; then
            cp /mnt/config-map/master.cnf /mnt/conf.d/
          else

          # 6.如果 ordinal 不为 0 就表示这 pod 是 slavc 将 slave.cnf 到 conf.d
            cp /mnt/config-map/slave.cnf /mnt/conf.d/
          fi

        # 将配置文件进行挂载
        volumeMounts:
        - name: conf
          mountPath: /mnt/conf.d
        - name: config-map
          mountPath: /mnt/config-map

      # 接着初始化 xtrabackup 容器,这个是用于克隆前一个 pod 的数据
      - name: clone-mysql
        image: hub.zhangguiyuan.com/baseimage/xtrabackup:1.0 
        imagePullPolicy: IfNotPresent
        command:
        - bash
        - "-c"
        - |
          set -ex

          # 判断 /var/lib/mysql/mysql 是否存在
          [[ -d /var/lib/mysql/mysql ]] && exit 0

          # 判断主机名的最后以为是不是数字
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1

          # 拿到第一个数
          ordinal=${BASH_REMATCH[1]}

          # 判断 ordinal 是否等于 0
          [[ $ordinal -eq 0 ]] && exit 0

          # mysql-$(($ordinal-1)).mysql 是一个 pod 的 DSN 解析
          # 接收 mysql-$(($ordinal-1)).mysql pod 的数据并备份,就是只基于前一个 mysqp pod 进行数据同步
          ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql

          # 备份数据目录
          xtrabackup --prepare --target-dir=/var/lib/mysql

        volumeMounts:
        # 挂载 pvc
        - name: data
          mountPath: /var/lib/mysql

          # 1 个 pod 中可以拉起多个容器,有时候希望将不同容器的路径挂载在存储卷volume 的子路径
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d

# 运行容器信息
      containers:
      - name: mysql
        image: hub.zhangguiyuan.com/baseimage/mysql:5.7 
        imagePullPolicy: IfNotPresent
        env:
        - name: MYSQL_ALLOW_EMPTY_PASSWORD
          value: "1"    # 值为 1 不用传递密码,改为 0 就需要传递密码
        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"]
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5

        # 就绪检测
        readinessProbe:
          exec:
            # Check we can execute queries over TCP (skip-networking is off).
            command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
          initialDelaySeconds: 5
          periodSeconds: 2
          timeoutSeconds: 1

      # 创建 xtrabackup 容器
      - name: xtrabackup
        image: hub.zhangguiyuan.com/baseimage/xtrabackup:1.0 
        imagePullPolicy: IfNotPresent
        ports:
        - name: xtrabackup
          containerPort: 3307
        command:
        - bash
        - "-c"
        - |
          set -ex
          cd /var/lib/mysql

          # 保存主日志文件以及偏移
          if [[ -f xtrabackup_slave_info ]]; then
            # XtraBackup already generated a partial "CHANGE MASTER TO" query
            # because we're cloning from an existing slave.
            mv xtrabackup_slave_info change_master_to.sql.in
            # Ignore xtrabackup_binlog_info in this case (it's useless).
            rm -f 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 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
          # 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 -e "SELECT 1"; do sleep 1; done
            echo "Initializing replication from clone position"
            # In case of container restart, attempt this at-most-once.
            mv change_master_to.sql.in change_master_to.sql.orig
            mysql -h 127.0.0.1 <<EOF
          $(<change_master_to.sql.orig),
            MASTER_HOST='mysql-0.mysql',
            MASTER_USER='root',
            MASTER_PASSWORD='',
            MASTER_CONNECT_RETRY=10;
          START SLAVE;
          EOF
          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"
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
      volumes:
      - name: conf
        emptyDir: {}
      - name: config-map
        configMap:
          name: mysql

# 调用 pv 进行存储挂载。并自动创建 pvc
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi

2.完整的 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: hub.zhangguiyuan.com/baseimage/mysql:5.7 
        imagePullPolicy: IfNotPresent
        command:
        - bash
        - "-c"
        - |
          set -ex
          # Generate mysql server-id from pod ordinal index.
          [[ `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
          # Copy appropriate conf.d files from config-map to 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: hub.zhangguiyuan.com/baseimage/xtrabackup:1.0 
        imagePullPolicy: IfNotPresent
        command:
        - bash
        - "-c"
        - |
          set -ex
          # Skip the clone if data already exists.
          [[ -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: hub.zhangguiyuan.com/baseimage/mysql:5.7 
        imagePullPolicy: IfNotPresent
        env:
        - name: MYSQL_ALLOW_EMPTY_PASSWORD
          value: "1"
        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"]
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
        readinessProbe:
          exec:
            # Check we can execute queries over TCP (skip-networking is off).
            command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
          initialDelaySeconds: 5
          periodSeconds: 2
          timeoutSeconds: 1
      - name: xtrabackup
        image: hub.zhangguiyuan.com/baseimage/xtrabackup:1.0 
        imagePullPolicy: IfNotPresent
        ports:
        - name: xtrabackup
          containerPort: 3307
        command:
        - bash
        - "-c"
        - |
          set -ex
          cd /var/lib/mysql
          # Determine binlog position of cloned data, if any.
          if [[ -f xtrabackup_slave_info ]]; then
            # XtraBackup already generated a partial "CHANGE MASTER TO" query
            # because we're cloning from an existing slave.
            mv xtrabackup_slave_info change_master_to.sql.in
            # Ignore xtrabackup_binlog_info in this case (it's useless).
            rm -f 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 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
          # 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 -e "SELECT 1"; do sleep 1; done
            echo "Initializing replication from clone position"
            # In case of container restart, attempt this at-most-once.
            mv change_master_to.sql.in change_master_to.sql.orig
            mysql -h 127.0.0.1 <<EOF
          $(<change_master_to.sql.orig),
            MASTER_HOST='mysql-0.mysql',
            MASTER_USER='root',
            MASTER_PASSWORD='',
            MASTER_CONNECT_RETRY=10;
          START SLAVE;
          EOF
          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"
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
      volumes:
      - name: conf
        emptyDir: {}
      - name: config-map
        configMap:
          name: mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi

3.创建

我们可以看到通过 statefuleset 的方式创建的 pod ,他都是按照顺序启动,如果前一个没有起来下面的 pod 也不会起来

[18:04:52 root@k8s-master mysqlYaml]#kubectl get pod -n mysql -w
NAME      READY   STATUS     RESTARTS   AGE
mysql-0   0/2     Init:0/2   0          7s
mysql-0   0/2     Init:1/2   0          12s
mysql-0   0/2     PodInitializing   0          19s
mysql-0   1/2     Running           0          20s
mysql-0   2/2     Running           0          34s
mysql-1   0/2     Pending           0          0s
mysql-1   0/2     Pending           0          0s
mysql-1   0/2     Pending           0          2s
mysql-1   0/2     Init:0/2          0          2s
mysql-1   0/2     Init:1/2          0          12s
mysql-1   0/2     Init:1/2          0          19s
mysql-1   0/2     PodInitializing   0          27s
mysql-1   1/2     Error             0          28s
mysql-1   1/2     Running           1          29s
mysql-1   2/2     Running           1          34s
mysql-2   0/2     Pending           0          0s
mysql-2   0/2     Pending           0          0s
mysql-2   0/2     Pending           0          2s
mysql-2   0/2     Init:0/2          0          2s
mysql-2   0/2     Init:1/2          0          4s
mysql-2   0/2     Init:1/2          0          5s
mysql-2   0/2     PodInitializing   0          13s
mysql-2   1/2     Error             0          14s
mysql-2   1/2     Running           1          15s
mysql-2   2/2     Running           1          20s

4.验证 pvc 已经自动绑定

[18:12:01 root@k8s-master mysqlYaml]#kubectl get pvc -n mysql 
NAME           STATUS   VOLUME            CAPACITY   ACCESS MODES   STORAGECLASS   AGE
data-mysql-0   Bound    mysql-datadir-5   50Gi       RWO                           7m17s
data-mysql-1   Bound    mysql-datadir-2   50Gi       RWO                           6m43s
data-mysql-2   Bound    mysql-datadir-3   50Gi       RWO                           6m9s

5.pod 已经启动

[18:12:03 root@k8s-master mysqlYaml]#kubectl get pod -n mysql 
NAME      READY   STATUS    RESTARTS   AGE
mysql-0   2/2     Running   0          7m44s
mysql-1   2/2     Running   1          7m10s
mysql-2   2/2     Running   1          6m36s

5.5 验证

1.进入到 mysql master pod 里面,进行状态验证

[18:12:30 root@k8s-master mysqlYaml]#kubectl exec -it -n mysql mysql-0 bash

root@mysql-0:/# mysql
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 363
Server version: 5.7.36-log MySQL Community Server (GPL)

Copyright (c) 2000, 2021, 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.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

# 查看当前 master 状态
mysql> show master status\G;
*************************** 1. row ***************************
             File: mysql-0-bin.000003
         Position: 154
     Binlog_Do_DB: 
 Binlog_Ignore_DB: 
Executed_Gtid_Set: 
1 row in set (0.00 sec)

# 并创建一个 test 数据库
mysql> create database test_db;
Query OK, 1 row affected (0.01 sec)

2.进行到 slave 节点进行验证

[18:18:40 root@k8s-master mysqlYaml]#kubectl exec -it -n mysql mysql-1 bash
root@mysql-1:/# mysql
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 469
Server version: 5.7.36 MySQL Community Server (GPL)

Copyright (c) 2000, 2021, 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.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

# 查看数据库 test_db 存在
mysql> show databases;
+------------------------+
| Database               |
+------------------------+
| information_schema     |
| mysql                  |
| performance_schema     |
| sys                    |
| test_db                |
| xtrabackup_backupfiles |
+------------------------+
6 rows in set (0.02 sec)

# 查看 slave 状态
mysql> show slave status\G;
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: mysql-0.mysql # master 节点就是通过主机名解析
                  Master_User: root
                  Master_Port: 3306
                Connect_Retry: 10
              Master_Log_File: mysql-0-bin.000003
          Read_Master_Log_Pos: 322
               Relay_Log_File: mysql-1-relay-bin.000002
                Relay_Log_Pos: 490
        Relay_Master_Log_File: mysql-0-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: 322
              Relay_Log_Space: 699
              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: 100
                  Master_UUID: 347f9607-318d-11ec-a413-5aa1e107c549
             Master_Info_File: /var/lib/mysql/master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Slave 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: 
1 row in set (0.00 sec)

3.查看 svc

# 我们可以看到 mysql 是 none 无头服务,但是 mysql-read 就有对应的 CLUSTER-IP 
[18:20:42 root@k8s-master mysqlYaml]#kubectl get svc -n mysql 
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
mysql        ClusterIP   None            <none>        3306/TCP   65m
mysql-read   ClusterIP   172.30.241.13   <none>        3306/TCP   65m

4.进入其他的 pod 中通过主机头 ping 通

# 进入 pod
[18:27:47 root@k8s-master mysqlYaml]#kubectl exec -it -n redis deploy-devops-redis /bin/bash

# 刚在我们看到 mysql 才是 svc ,而我这里直接通过 ping mysql-0 的主机头进行解析
[root@deploy-devops-redis /]# ping  mysql-0.mysql.mysql.svc.linux.local
PING mysql-0.mysql.mysql.svc.linux.local (10.10.169.140) 56(84) bytes of data.
64 bytes from mysql-0.mysql.mysql.svc.linux.local (10.10.169.140): icmp_seq=1 ttl=63 time=0.048 ms
64 bytes from mysql-0.mysql.mysql.svc.linux.local (10.10.169.140): icmp_seq=2 ttl=63 time=0.082 ms

5.先获取 coredns pod 对应的 ip

[18:33:14 root@k8s-master mysqlYaml]#kubectl get pod -n kube-system -o wide | grep coredns
coredns-6f6b8cc4f6-gnqwh                   1/1     Running   2          26h     10.10.169.137   k8s-node2    <none>           <none>
coredns-6f6b8cc4f6-qp8n4                   1/1     Running   2          26h     10.10.113.132   k8s-node     <none>           <none>

6.在获取 mysql pod 的详细信息

[18:33:44 root@k8s-master mysqlYaml]#kubectl get pod -n mysql -o wide
NAME      READY   STATUS    RESTARTS   AGE   IP              NODE        NOMINATED NODE   READINESS GATES
mysql-0   2/2     Running   0          29m   10.10.169.140   k8s-node2   <none>           <none>
mysql-1   2/2     Running   1          28m   10.10.113.135   k8s-node    <none>           <none>
mysql-2   2/2     Running   1          27m   10.10.113.136   k8s-node    <none>           <none>

7.通过 dig命令进行解析,这里 @ 后面跟的是任意一个 coredns ip

也就意味着在我们无头服务中虽然没有自己的 SVC 了,但是可以通过访问域名的方案依然可以访问至这几个不同的 pod 上去,这个就是我们无头服务的含义

[18:33:51 root@k8s-master mysqlYaml]#dig -t -A mysql.mysql.svc.linux.local. @10.10.169.137 
;; Warning, ignoring invalid type -A

; <<>> DiG 9.11.3-1ubuntu1.12-Ubuntu <<>> -t -A mysql.mysql.svc.linux.local. @10.10.169.137
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 27126
;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 9019399f0301aa0a (echoed)
;; QUESTION SECTION:
;mysql.mysql.svc.linux.local.   IN  A

# 我们会惊奇得发现每条 DNS 解析的就是对应的 mysql pod ip
;; ANSWER SECTION:
mysql.mysql.svc.linux.local. 30 IN  A   10.10.113.136
mysql.mysql.svc.linux.local. 30 IN  A   10.10.169.140
mysql.mysql.svc.linux.local. 30 IN  A   10.10.113.135

;; Query time: 1 msec
;; SERVER: 10.10.169.137#53(10.10.169.137)
;; WHEN: Wed Oct 20 18:35:06 CST 2021
;; MSG SIZE  rcvd: 197

8.并且在 NFS 服务器上也有挂载卷,从而实现了数据的持久化

[16:54:12 root@harbor-nfs ~]#ll /data/k8sdata/mysql/mysql-datadir-{1..6}
/data/k8sdata/mysql/mysql-datadir-1:
total 8
drwxr-xr-x 2 root root 4096 Oct 20 16:54 ./
drwxr-xr-x 8 root root 4096 Oct 20 16:54 ../

/data/k8sdata/mysql/mysql-datadir-2:
total 12
drwxr-xr-x 3 root root 4096 Oct 20 18:05 ./
drwxr-xr-x 8 root root 4096 Oct 20 16:54 ../
drwxr-xr-x 7  999 root 4096 Oct 20 18:18 mysql/

/data/k8sdata/mysql/mysql-datadir-3:
total 12
drwxr-xr-x 3 root root 4096 Oct 20 18:05 ./
drwxr-xr-x 8 root root 4096 Oct 20 16:54 ../
drwxr-xr-x 7  999 root 4096 Oct 20 18:18 mysql/

/data/k8sdata/mysql/mysql-datadir-4:
total 8
drwxr-xr-x 2 root root 4096 Oct 20 16:54 ./
drwxr-xr-x 8 root root 4096 Oct 20 16:54 ../

/data/k8sdata/mysql/mysql-datadir-5:
total 12
drwxr-xr-x 3 root root 4096 Oct 20 18:05 ./
drwxr-xr-x 8 root root 4096 Oct 20 16:54 ../
drwxr-xr-x 7  999 root 4096 Oct 20 18:18 mysql/

/data/k8sdata/mysql/mysql-datadir-6:
total 8
drwxr-xr-x 2 root root 4096 Oct 20 16:54 ./
drwxr-xr-x 8 root root 4096 Oct 20 16:54 ../
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇