基于StatefulSet控制器在Kubernetes上部署MySQL一主多从

一、前提--StatefuSet特性

1.1 有状态的节点控制器 -- StatefulSet 及其网络状态

容器的解决方案是针对无状态应用场景的最佳实践,但对于有状态应用来说,就并非如此了。Kubernetes 用 StatefulSet 解决了有状态应用编排的问题,本文我们就来初步认识一下 StatefulSet。

StatefulSet 将应用设计抽象为了两种状态:

拓扑状态:

       应用的多个实例必须按照某种顺序启动,并且必须成组存在,例如一个应用中必须存在一个 A Pod 和两个 B Pod,且 A Pod 必须先于 B Pod 启动的场景。

存储状态:

      应用存在多个实例,但每个实例绑定的存储数据不同,那么对于一个 Pod 来说,无论它是否被重新创建,它读到的数据状态应该是一致的。

Kubernetes 的 Service 就是对外提供的可访问服务,它有两种访问方式:

  1. VIP 方式:它是 Virtual IP 的缩写,通过将服务绑定到 Kubernetes 虚拟的 IP 地址,提供给外部调用,通过虚拟 IP 地址隐藏了服务的具体实现与地址。

  2. DNS 方式:与虚拟 IP 地址类似,外部通过访问 DNS 记录的方式实现对具体 Service 的转发。

DNS 的两种处理方式

  1. Normal Service:将 DNS 地址绑定到虚拟 IP 地址,从而复用虚拟 IP 地址的设计和逻辑;

  2. Headless Service:将 DNS 地址直接代理到 Pod。

clusterIP 设置为了 None,表示不为这个 Service 分配 VIP,而是通过 Headless DNS 的方式来处理该 Service 的调用。

<pod-name>.<svc-name>.<namespace>.svc.cluster.local

这个 DNS 就是 Kubernetes 为 Pod 分配的唯一可解析身份,这样一来,只要有了 Pod 的名字和 Service 的名字,我们就能唯一确定一个能够访问这个 Pod 的 DNS 地址了。

只要我们使用 DNS 记录来访问 StatefulSet 控制器控制下的 Pod,即使 Pod 发生了宕机和重启,DNS 记录对应的Pod记录本身是不会发生变化的,同一个“名字-编号”组合的 Pod 在 StatefulSet 中总是稳定地对外提供服务的,进而实现了整个“网络状态”的稳定。

1.2 有状态的节点控制器 StatefulSet 的存储状态

StatefulSet 通过为每一个 pod 分配有粘性的 ID,并且在 pod 发生变更时,维持 ID 的稳定,从而保证了网络状态下不对等关系的各个 Pod 在启动、删除和重建过程中能够始终保持稳定。

但在实际的使用场景中,我们不仅仅需要维护网络拓扑的稳定性,Pod 与分布式存储的存储节点之间关系的稳定性往往也是非常重要的,而这也正是 StatefulSet 的另一个优势。

对于一个 Pod 来说,它需要挂载和使用的分布式存储节点必须是稳定的。Id 为 web-0 的 Pod 如果在某一时刻挂载了 web-1 Pod 对应的存储资源,结果可能是不堪设想的。

StatefulSet 控制器通过 volumeClaimTemplates 解决了这一问题。

如果我们为一个 StatefulSet 配置了 volumeClaimTemplates,那么就意味着,这个控制器中管理的每个 Pod 都会自动声明一个自己 ID 所对应的 PVC,而这个 PVC 定义所需的属性,则均来自于 volumeClaimTemplates 中的声明。

StatefuSet里声明了volumeClaimTemplates后,该StatefulSet 创建出来的所有 Pod,都会声明使用编号的 PVC。比如,在名叫 web-0 的 Pod 的 volumes 字段,它会声明使用名叫 www-web-0 的 PVC,从而挂载到这个 PVC 所绑定的 PV。

当 web-0 Pod 向挂载给他的 PV 节点中写入数据后,即使 web-0 Pod 发生宕机或重启,从而被一个全新的同样 ID 为 web-0 的 Pod 替换后,由于新的 Pod 挂载的仍然是 Id 为 www-web-0 的 PVC,所以,它依然可以读取到此前 web-0 Pod 写入的数据。

二、有状态应用典型案例--MySQL主从

mysql 集群是一个非常典型的有状态应用,怎么在kubernetes集群上部署mysql集群,就需要使用statefuset控制器和持久数据PV、PVC。

对于 mysql 集群来说,我们首先要选取主节点,并且启动它,如果这是一个已有数据 mysql 节点,还需要考虑如何备份 mysql 主节点上的数据。

此后,我们需要用另一套配置来启动若干从节点,并且在这些从节点上恢复上一步中主节点上的备份数据。

完成上述配置之后,我们还必须考虑如何保证只让主节点处理写请求,而读请求则可以在任意节点上执行。

由此可见,mysql 主从集群的构建具有网络状态 -- 主节点必须先行启动,并且具有存储状态 -- 每个节点需要有自己独立的存储,很显然,用 Deployment 作为控制器来进行 mysql 集群的搭建是无法实现的,而这恰恰是 StatefulSet 擅长处理的场景。

三、主从节点的区分 -- 配置与读写

3.1 主从节点不同的配置文件

mysql 主节点与从节点拥有完全不同的配置,主节点需要开启 log-bin 通过二进制的方式导出 bin-log 来实现主从复制,从节点需要配置主节点的信息外,还需要配置 super-read-only 来实现从节点的只读。

在 Kubernetes 中我们只需要在 ConfigMap 中定义两套配置,然后在 pod 描述中依据不同的 pod 序号选择挂载不同的配置即可。

下面是一个 ConfigMap 的示例:

apiVersion: v1
kind: ConfigMap
metadata:
 name: mysql
 labels:
   app: mysql
data:
  master.cnf: |
   # 主节点MySQL的配置文件
   [mysqld]
   log-bin
  slave.cnf: |
   # 从节点MySQL的配置文件
   [mysqld]
   super-read-only

3.2 用 Service 实现主从的读写分离

apiVersion: v1
kind: Service
metadata:
 name: mysql
 labels:
   app: mysql
spec:
 ports:
  - name: mysql
    port: 3306
 clusterIP: None
 selector:
  app: mysql
---
apiVersion: v1
kind: Service
metadata:
 name: mysql-read
 labels:
  app: mysql
spec:
 ports:
  - name: mysql
    port: 3306
 selector:
  app: mysql

由于第一个 Service 配置了 clusterIP: None,所以它是一个 Headless Service,我们只用它代理编号为 0 的节点,也就是主节点。

而第二个 Service,由于在 selector 中指定了 app: mysql,所以它会代理所有具有这个 label 的节点,也就是集群中的所有节点。

四、集群初始化

集群启动前,集群初始化步骤有:

  1. 各个节点正确获取对应的 ConfigMap 中的配置文件,并且放置在 mysql 配置文件所在的路径。

  2. 如果节点是从节点,那么需要先将主节点的数据全量拷贝到对应路径下。

  3. 在从节点上执行数据初始化命令。

这些操作我们可以通过定义一系列 InitContainers 来实现。

4.1 根据pod节点名称获取对应的配置文件

对于 StatefulSet 而言,每个 pod 各自的 hostname 中所具有的序号就是它们的唯一 id,因此我们可以通过正则表达式来获取这个 id,并且规定 id 为 0 表示主节点,于是,通过判断 server 的 id,就可以对 ConfigMap 中不同的配置进行获取:

   initContainers:
      - name: init-mysql
        image: harbor.jnlikai.cc/mysql/mysql:5.7.36
        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 来避开 0
          echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
           # 如果Pod序号是0,说明它是Master节点,拷贝 master 配置
           # 否则,拷贝 Slave 的配置
          [[ $ordinal -eq 0 ]] && cp /mnt/config-map/master.cnf /mnt/conf.d/ || cp /mnt/config-map/slave.cnf /mnt/conf.d/
        volumeMounts:
        - name: conf
          mountPath: /mnt/conf.d
        - name: config-map
          mountPath: /mnt/config-map

4.2 从节点copy前一个节点上的全量数据

- name: clone-mysql
         image: harbor.jnlikai.cc/mysql/xtrabackup:1.0
         command:
         - bash
         - "-c"
         - |
           set -ex
            # 拷贝操作只需要在第一次启动时进行,所以如果数据已经存在,跳过
            [[ -d /var/lib/mysql/mysql ]] && exit 0
            # Master节点(序号为0)不需要做这个操作
            [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
            ordinal=${BASH_REMATCH[1]}
            [[ $ordinal -eq 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

在这一部分,我们使用了 ncat 命令实现从上一个已经启动的节点拷贝数据到当前节点,并且使用了第三方的备份还原工具 xtrabackup 来实现数据的恢复。

五、MySQL容器启动

5.1 从节点上数据恢复和每个节点上开启实时同步的端口

在 initContainers 中,我们实现了在从节点中,将上一个节点的备份数据拷贝到当前节点的工作,那么,接下来我们就要去恢复这个数据了。

与此同时,我们还需要在 mysql 的实际运行中实时执行数据的同步、恢复与备份工作。上文提到的 xtrabackup 很方便地实现了这一系列功能。我们可以将这个集成工具作为一个 sidecar 启动,完成上述这些操作:

   - name: xtrabackup
       image: harbor.jnlikai.cc/mysql/xtrabackup:1.0
       ports:
       - name: xtrabackup
         containerPort: 3307
         command:
         - bash
         - "-c"
         - |
         set -ex
         cd /var/lib/mysql
          # 从备份信息文件里读取MASTER_LOG_FILEM和MASTER_LOG_POS这两个字段的值,用来拼装集群初始化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
          # 所以在这个示例中,我们使用了 ncat 命令实现从上一个已经启动的节点拷贝数据到当前节点,并且使用了第三方的备份还原工具 xtrabackup 来实现数据的恢复。,也就用不着xtra    backup_binlog_info了
         rm -f xtrabackup_binlog_info
         elif [[ -f xtrabackup_binlog_info ]]; then
          # 如果只存在xtrabackup_binlog_inf文件,那说明备份来自于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 -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 <<EOF
         $(<change_master_to.sql.orig),
          MASTER_HOST='mysql-0.mysql',
          MASTER_USER='root',
          MASTER_PASSWORD='',
          MASTER_CONNECT_RETRY=10;
         START SLAVE;
         EOF
         fi
         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

5.2 MySQL业务容器启动

         - name: mysql
        image: harbor.jnlikai.cc/mysql/mysql:5.7.36
        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:
           # 通过TCP连接的方式进行健康检查
            command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
          initialDelaySeconds: 5
          periodSeconds: 2
          timeoutSeconds: 1
   volumes:
       - name: conf
         emptyDir: {}
       - name: config-map
         configMap:
           name: mysql
 volumeClaimTemplates:
   - metadata:
       name: data
     spec:
       accessModes: ["ReadWriteOnce"]
       resources:
         requests:
           storage: 10Gi

业务容器里会用到PVC,所以在启动此statefuset控制器之前需要先创建足够的PV,使用NFS server做数据存储端,在10.49.33.147机器上NFS共享如下目录;

apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql
spec:
  capacity:
    storage: 50Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    path: /data/mysql
    server: 10.49.33.147
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql1
spec:
  capacity:
    storage: 50Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    path: /data/mysql1
    server: 10.49.33.147

六、测试MySQL集群高可用

755bf78ff63a4a9faf2e38f781da155c.png

测试登录mysql-1可以看到刚才在msyql-0上创建的数据库mysqltests

d8cd0d4bb05d4f8b9b5d7dcaea95ffaa.png在从节点上测试写入会报错:

c500f06bac5b4bf3bd13d70bce2ed9a6.png

手动删除mysql-0 statefuset控制器会立即重建mysql-0,重建之后数据还会存在,因为重建的mysql-0还会挂载原来的PVC

769d77616e514f689eface14613e1c4a.png

2e86d00f285a4f34ad238349451350b3.png

392dc5b2c1f54c6a98864aaef6784ab9.png

参考文章:

实战 Kubernetes StatefulSet -- MySQL 主从集群搭建

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/603168.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GitHub介绍,GitHub如何订阅充值?

一、GitHub介绍 GitHub是一个面向开源及私有软件项目的托管平台&#xff0c;因为只支持git 作为唯一的版本库格式进行托管&#xff0c;故名Github。 GitHub于2008年4月10日正式上线&#xff0c;除了git代码仓库托管及基本的Web管理界面以外&#xff0c;还提供了订阅、讨论组、…

爬取深圳2024年链家二手房数据,共3000条数据(其他城市也可)

文章目录 专栏导读1.目标2.导入相关库3.获取每个二手房的链接4.获取每个链接中的相关数据5.保存数据6.数据展示 专栏导读 ✍ 作者简介&#xff1a;i阿极&#xff0c;CSDN 数据分析领域优质创作者&#xff0c;专注于分享python数据分析领域知识。 ✍ 本文录入于《python网络爬虫…

探索数据结构

什么是数据结构 数据结构是由&#xff1a;“数据”与“结构”两部分组成 数据与结构 数据&#xff1a;如我们所看见的广告、图片、视频等&#xff0c;常见的数值&#xff0c;教务系统里的&#xff08;姓名、性别、学号、学历等等&#xff09;&#xff1b; 结构&#xff1a;当…

Pandas进阶

文章目录 第1关&#xff1a;Pandas 分组聚合第2关&#xff1a;Pandas 创建透视表和交叉表 第1关&#xff1a;Pandas 分组聚合 编程要求 使用 Pandas 中的 read_csv() 函数读取 step1/drinks.csv 中的数据&#xff0c;数据的列名如下表所示&#xff0c;请根据 continent 分组并…

VMware 虚拟机自定义规范 - 更优雅的虚拟机开局

介绍 虚拟机自定义规范可以在你克隆虚拟机的时候在vCenter 的Web界面设定虚拟机的主机名、单/多网卡IP的IP和网关、DNS服务器、唯一标识符重置&#xff08;SID等&#xff09;、硬盘分区自动扩容、设定密码、密钥、时区等信息。 让管理员不需要进入虚拟机系统内部进行配置&…

10000字讲解IoC 思想以及五大注解

文章目录 IoC 思想通过案例讲解 IoC1.传统的开发方式 SpringIoC 和 DI五大注解ControllerServiceComponentRepositoryConfiguration 为什么要有这么多的类注解类注解之间的关系方法注解 Bean重命名 bean扫描路径 IoC 思想 什么是 Spring 呢&#xff1f; 我们经常听到的都是说…

Android 13 aosp 默认关闭SELinux

通过adb修改 adb root adb shell setenforce 0 // 开SELinux&#xff0c;设置成模式permissive adb shell setenforce 1 // 关SELinux&#xff0c;设置成模式enforce adb shell getenforce // 获取当前SELinux状态源码修改 Android_source/system/core/init/selinu…

JS-导入导出

export和export default是ES6中导出模块中变量的语法 导入导出变量 //导出方法&#xff08;js文件中&#xff09; export const 变量名值//导入方法 对应导入的变量&#xff0c;一定要加花括号 import {变量名} from js文件路径 导入导出函数 //导出方法&#xff08;js文件中…

2024.1IDEA 到2026年

链接&#xff1a;https://pan.baidu.com/s/1hjJEV5A5k1Z9JbPyBXywSw?pwd9g4i 提取码&#xff1a;9g4i解压之后,按照 操作说明.txt 操作; IntelliJ IDEA 2024.1 (Ultimate Edition) Build #IU-241.14494.240, built on March 28, 2024 Licensed to gurgles tumbles You have…

福汇美股开户教程

福汇作为全球知名的外汇交易平台&#xff0c;也提供美股交易服务。在福汇交易美股&#xff0c;首先需要开立一个福汇账户。本教程将详细介绍福汇美股开户流程。 第一步&#xff1a;访问福汇官网并填写开户表格 访问福汇美股入口点击页面顶部的“开户”按钮。选择您的国籍&…

JetsonNano —— Windows下对Nano板卡烧录刷机(官方教程)

介绍 NVIDIA Jetson Nano™ 开发者套件是一款面向创客、学习者和开发人员的小型 AI 计算机。按照这个简短的指南&#xff0c;你就可以开始构建实用的 AI 应用程序、酷炫的 AI 机器人等了。 烧录刷机 1、下载 Jetson Nano开发者套件SD卡映像&#xff0c;并记下它在计算机上的保存…

初探MFC程序混合使用QT

一、背景 随着操作系统国产化替代的趋势越发明显&#xff0c;软件支持国际化、跨平台&#xff0c;已然是必须做的一件事情。原有的软件UI层用的是MFC&#xff0c;将其换成QT&#xff0c;想必是一种较好的方案。对于大型软件&#xff0c;特别是已发布&#xff0c;但还处于不断迭…

vue 开发环境的搭建

一、整个流程&#xff1a; 安装nodejs >> 安装vue >> 安装vue-cli >> 初始化 webpack(生成代码) >> 安装依赖 >> 运行vue程序 二、详细安装流程&#xff1a; 1.安装nodejs 下载&#xff1a;https://nodejs.org/dist/v12.18.3/node-v12.18.3-x…

《米小圈上学记》|快乐读书,从身边的人身边的事开始!

时间&#xff0c;抓住了就是黄金&#xff0c;虚度了就是流水;书&#xff0c;看了就是学问&#xff0c;没看就是废纸:抱负&#xff0c;努力了才叫幻想&#xff0c;放弃了那只是妄想。读书&#xff0c;不一定能转变命运&#xff0c;但肯定能让我们安静&#xff0c;安静本身就是一…

【触摸案例-手势解锁案例-连线到按钮 Objective-C语言】

一、接下来,我们接着来说这个,连线的问题啊, 1.连线的问题啊,也就是说,我现在点击一个按钮, 在移动到下一个按钮的时候,在两个按钮中间,在两个按钮都亮起来的时候呢,我们肯定是让它去画一条线的,那么, 1)首先,如果我现在从第一个按钮,连到第二个按钮,那么,这条…

二叉树的实现(详解,数据结构)

目录 一&#xff0c;二叉树需要实现的功能 二&#xff0c;下面是各功能详解 0.思想&#xff1a; 1.创建二叉树结点&#xff1a; 2.通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 3.二叉树销毁&#xff1a; 4.前序遍历&#xff1a; 5.中序遍历&#xff1a;…

小红书「打工人」生存现状实录

近年来&#xff0c;“打工人”一词频繁出现在内容场景中。调研小红书数据显示&#xff0c;近一年“打工人、职场”相关笔记声量增长440%&#xff0c;预估互动总量增长128%&#xff0c;热度高涨。 职场年轻人正披着“打工人”的外壳&#xff0c;不断自嘲中寻求身份认同。基于此&…

各种流量包特征

[CVE-2013-1966] Apache Struts2 远程命令执行漏洞 要执行的命令在exec里面&#xff0c;而且回显数据包里面有明显执行结果回显 [CVE-2017-8046] Spring Data Rest 远程命令执行漏洞 回显不明显&#xff0c;考试提供的解码工具不能解密&#xff0c; [CVE-2017-12149] JBOSS…

java多线程编码应用1——java多线程CompletableFuture使用技巧

在实际项目开发过程中&#xff0c;大部分程序的执行顺序都是按照代码编写的先后顺序&#xff0c;依次从上往下挨个执行的&#xff0c;但是对于统计或者批量操作数据时&#xff0c;是否有更好的方案呢&#xff1f;这时候就可以考虑使用多线程编程&#xff0c;异步并行执行多个任…

Flink checkpoint 源码分析- Checkpoint snapshot 处理流程

背景 在上一篇博客中我们分析了代码中barrier的是如何流动改的。Flink checkpoint 源码分析- Checkpoint barrier 传递源码分析-CSDN博客 最后跟踪到了代码org.apache.flink.streaming.runtime.io.checkpointing.CheckpointedInputGate#handleEvent 现在我们接着跟踪相应代…