基于K8S的StatefulSet部署mysql主从

StatefulSet特性

StatefulSet的网络状态

拓扑状态 :应用的多个实例必须按照某种顺序启动,并且必须成组存在,例如一个应用中必须存在一个A Pod和两个B Pod,且A Pod必须先于B Pod启动的场景
存储状态:应用存在多个实例,但每个实例绑定的存储数据单位时间内不一定相同,那么对于一个Pod来说,无论它是否被重新创建,它读到的数据状态应该是一致的

Service两种访问方式

VIP方式 :通过将服务绑定到Kubernetes虚拟的IP地址,提供给外部调用,通过虚拟IP地址隐藏了服务的具体实现与地址
DNS方式 :与虚拟IP地址类似,外部通过访问DNS记录的方式实现对具体Service的转发,包括下面两种
Normal Service :将DNS地址绑定到虚拟IP地址,从而复用虚拟IP地址的设计和逻辑
Headless Service :将DNS地址直接代理到Pod

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

bash 复制代码
<pod-name>.<svc-name>.<namespace>.svc.cluster.local

这个DNS就是Kubernetes为Pod分配的唯一可解析身份,只要有了Pod的名字和Service的名字,就能唯一确定能够访问这个Pod的DNS地址了。只要使用DNS记录来访问StatefulSet控制器控制下的Pod,即使Pod发生了宕机和重启,DNS记录对应的Pod记录本身是不会发生变化的,同一个"名字-编号"组合的Pod在StatefulSet中总是稳定地对外提供服务,进而实现了整个"网络状态"的稳定

StatefulSet的存储状态

StatefulSet控制器通过volumeClaimTemplates解决这一问题如果为一个StatefulSet配置了volumeClaimTemplates,那么这个控制器中管理的每个Pod都会自动声明一个自己 ID所对应的PVC,而这个PVC定义所需的属性,则均来自于volumeClaimTemplates中的声明

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

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

MySQL主从安装部署

主从同步基本架构原理

MySQL主从安装前需要注意的问题

1、启动顺序:Master的Pod必须先于Slave的Pod起来

2、如果某个Pod挂掉了,应该自动重新启动一个Pod,这个新建的Pod应该沿用原来的数据

3、Master与Slave的配置文件不同,特别是server_id

4、Master与Slave在服务启动之后还需要执行一些命令,具体为:

  1. Master需要执行授权命令,授权的用户名和密码希望用户自己来配置,而不是写死
  2. Slave需要执行CHANGE MASTER命令,需要知道Master的地址

创建NFS数据目录

bash 复制代码
$ sudo mkdir -p /data/nfs/{mysqldata1,mysqldata2,mysqldata3,mysqldata4,mysqldata5}

配置名称空间文件

bash 复制代码
$ vim mysql-ns.yaml
apiVersion: v1
kind: Namespace
metadata:
name: mysql-ns
$ kubectl create -f mysql-ns.yaml
$ kubectl get ns mysql-ns

配置PV文件

bash 复制代码
$ vim mysql-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysqldata1
namespace: mysql-ns
spec:
capacity:
storage: 5Gi
accessModes: ["ReadWriteMany","ReadWriteOnce"]
persistentVolumeReclaimPolicy: Retain
nfs:
path: /data/nfs/mysqldata1
server: 192.168.3.132
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysqldata2
namespace: mysql-ns
spec:
capacity:
storage: 5Gi
accessModes: ["ReadWriteMany","ReadWriteOnce"]
persistentVolumeReclaimPolicy: Retain
nfs:
path: /data/nfs/mysqldata2
server: 192.168.3.132
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysqldata3
namespace: mysql-ns
spec:
capacity:
storage: 5Gi
accessModes: ["ReadWriteMany","ReadWriteOnce"]
persistentVolumeReclaimPolicy: Retain
nfs:
path: /data/nfs/mysqldata3
server: 192.168.3.132
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysqldata4
namespace: mysql-ns
spec:
capacity:
storage: 5Gi
accessModes: ["ReadWriteMany","ReadWriteOnce"]
persistentVolumeReclaimPolicy: Retain
nfs:
path: /data/nfs/mysqldata4
server: 192.168.3.132
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysqldata5
namespace: mysql-ns
spec:
capacity:
storage: 5Gi
accessModes: ["ReadWriteMany","ReadWriteOnce"]
persistentVolumeReclaimPolicy: Retain
nfs:
path: /data/nfs/mysqldata5
server: 192.168.3.132
bash 复制代码
$ kubectl create -f mysql-pv.yaml
bash 复制代码
$ kubectl get pv -n mysql-ns -o wide

配置ConfigMap文件

bash 复制代码
$ vim mysql-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql
namespace: mysql-ns
labels:
app: mysql
data:
# 仅在主服务器上应用此配置
master.cnf: |
[mysqld]
lower_case_table_names=1
log_error = /var/lib/mysql/log-err.log
log-bin
binlog-format = ROW
# 仅在副本服务器上应用此配置
slave.cnf: |
[mysqld]
super-read-only
log_error = /var/lib/mysql/log-err.log
bash 复制代码
$ kubectl create -f mysql-cm.yaml
bash 复制代码
$ kubectl get cm mysql -n mysql-ns

配置Secret文件

先用命令生成yaml格式配置

bash 复制代码
$ kubectl create secret generic mysql-secret --namespace=mysql-ns --from-literal=user_name=root --from-literal=user_password=123456 -o yaml
bash 复制代码
$ vim mysql-secret.yaml
apiVersion: v1
data:
user_name: cm9vdA==
user_password: MTIzNDU2
kind: Secret
metadata:
name: mysql-secret
namespace: mysql-ns
type: Opaque
bash 复制代码
$ kubectl create -f mysql-secret.yaml
bash 复制代码
$ kubectl get secret -n mysql-ns

配置Service文件

bash 复制代码
$ vim mysql-svc.yaml
# 无头服务用于连接mysql主库,进行读写操作
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: mysql-ns
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
clusterIP: None
selector:
app: mysql
---
# 用于连接到任一 MySQL 实例执行读操作的客户端服务
# 对于写操作,你必须连接到主服务器:mysql-0.mysql
apiVersion: v1
kind: Service
metadata:
name: mysql-read
namespace: mysql-ns
labels:
app: mysql
readonly: "true"
spec:
ports:
- name: mysql
port: 3306
selector:
app: mysql
bash 复制代码
$ kubectl create -f mysql-svc.yaml
bash 复制代码
$ kubectl get svc -n mysql-ns


说明:用户所有写请求,必须以DNS记录的方式直接访问到Master节点,也就是mysql-0.mysql这条DNS 记录
用户所有读请求,必须访问自动分配的DNS记录可以被转发到任意一个Master或Slave节点上,也就是mysql-read这条DNS记录

配置StatefulSet文件

bash 复制代码
$ vim mysql-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: mysql-ns
spec:
selector:
matchLabels:
app: mysql
serviceName: mysql
replicas: 3
template:
metadata:
labels:
app: mysql
spec:
initContainers:
- name: init-mysql
image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/mysql:5.7.40
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: user_password
optional: true
command:
- bash
- "-c"
- |
set -ex
# 基于Pod序号生成MySQL服务器的server-id
[[ $HOSTNAME =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
echo [mysqld] > /mnt/conf.d/server-id.cnf
# 添加偏移量以避免使用server-id=0这一保留值
echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
# 将合适的conf.d文件从config-map复制到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: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/gcr.io/google-samples/xtrabackup:1.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: user_password
optional: true
command:
- bash
- "-c"
- |
set -ex
# 如果已有数据,则跳过克隆。
[[ -d /var/lib/mysql/mysql ]] && exit 0
# 跳过主实例(server-id为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
containers:
- name: mysql
image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/mysql:5.7.40
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: user_password
optional: true
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", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}", "ping"] 
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
# readiness探针会使得容器初始化出现问题,根据需求更改参数
#readinessProbe:
#exec:
# 检查是否可以通过TCP执行查询(skip-networking是关闭的)。
#command: ["mysql", "-uroot", "-p${MYSQL_ROOT_PASSWORD}", "-e", "SELECT 1"]
#initialDelaySeconds: 5
#periodSeconds: 2
#timeoutSeconds: 1
- name: xtrabackup
image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/gcr.io/google-samples/xtrabackup:1.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: user_password
optional: true
ports:
- name: xtrabackup
containerPort: 3307
command:
- bash
- "-c"
- |
set -ex
cd /var/lib/mysql
# 从备份信息文件里读取MASTER_LOG_FILEM和MASTER_LOG_POS这两个字段的值
if [[ -f xtrabackup_slave_info && "x$(<xtrabackup_slave_info)" != "x" ]]; then
# 如果xtrabackup_slave_info文件存在,说明这个备份数据来自于另一个Slave节点,
# 已经在这个文件里自动生成了"CHANGE MASTER TO" SQL语句。
# 所以只需要把这个文件重命名为change_master_to.sql.in,后面直接使用即可
cat xtrabackup_slave_info | sed -E 's/;$//g' > change_master_to.sql.in
# 要删除xtrabackup_binlog_info,它已经没用了
rm -f xtrabackup_slave_info xtrabackup_binlog_info
elif [[ -f xtrabackup_binlog_info ]]; then
# 如果存在xtrabackup_binlog_info文件,说明备份来自于Master节点,
# 就需要解析这个备份信息文件,读取所需的两个字段值
[[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
rm -f xtrabackup_binlog_info xtrabackup_slave_info
echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\
MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
fi
# 如果存在change_master_to.sql.in,就意味着需要做集群初始化工作
if [[ -f change_master_to.sql.in ]]; then
echo "Waiting for mysqld to be ready (accepting connections)"
until mysql -u root -p${MYSQL_ROOT_PASSWORD} -h 127.0.0.1 -
e "SELECT 1"; do sleep 1; done
echo "Initializing replication from clone position"
mysql -u root -p${MYSQL_ROOT_PASSWORD} -h 127.0.0.1 \
-e "$(<change_master_to.sql.in), \
MASTER_HOST='mysql-0.mysql', \
MASTER_USER='root', \
MASTER_PASSWORD="${MYSQL_ROOT_PASSWORD}", \
MASTER_CONNECT_RETRY=10; \
START SLAVE;" || exit 1
# 将文件change_master_to.sql.in改个名字,防止容器重启时再次检查到
mv change_master_to.sql.in change_master_to.sql.orig
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
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: 5Gi
bash 复制代码
$ kubectl create -f mysql-statefulset.yaml
bash 复制代码
$ kubectl get pod -n mysql-ns --watch

查看PVC和PV绑定情况

bash 复制代码
$ kubectl get pvc -n mysql-ns
bash 复制代码
$ kubectl get pv -n mysql-ns

至此mysql主从部署完成

当应用程序或其他客户端连接主从复制集群时,形成了如下工作原理的架构图

测试主从复制功能

bash 复制代码
$ kubectl exec -it mysql-0 -n mysql-ns -- bash


进入任意slave节点查看主从复制是否正常

bash 复制代码
$ kubectl exec -it mysql-1 -n mysql-ns -- bash

在master上创建测试数据

bash 复制代码
$ kubectl exec -it mysql-0 -n mysql-ns -- bash
bash 复制代码
bash-4.2# mysql -uroot -p
mysql> CREATE DATABASE mydb;
mysql> CREATE TABLE mydb.messages (message VARCHAR(250));
mysql> INSERT INTO mydb.messages VALUES ('hello');

在两个slave节点上分别查看

bash 复制代码
$ kubectl exec -it mysql-1 -n mysql-ns -c mysql -- bash -c "mysql -uroot -p'' -
e 'select * from mydb.messages;'"
bash 复制代码
$ kubectl exec -it mysql-2 -n mysql-ns -c mysql -- bash -c "mysql -uroot -p'' -
e 'select * from mydb.messages;'"

故障转移测试

过程简介

故障转移测试,简单的说就是模拟当mysql主库发生故障后,statefulset是否会创建新的pod,并将原来旧master的ID绑定到新的pod上,其他的slave是否会重新和新的master同步数据,再次保持数据一致性和可用性,应用程序访问是否会受到影响等

测试

首先监控mysql主从集群各个pod状态

bash 复制代码
$ kubectl get pod -n mysql-ns --watch

StatefulSet部署的mysql主从集群,默认是把编号为0的数据库pod作为master,那么这时开始删除这个mysql-0的pod

bash 复制代码
$ kubectl delete pod mysql-0 -n mysql-ns

删除过程中,可以看到statefulset启用了新的pod并重新绑定了

查看PV和PVC的状态,各个数据库pod绑定的PV没有发生变化

接下来查看新的pod启动后,旧的数据是否完整,主从复制是否正常

bash 复制代码
$ kubectl exec -it mysql-0 -n mysql-ns -- bash -c "mysql -uroot -p'' -e 'select * from mydb.messages;'"
bash 复制代码
$ kubectl exec -it mysql-1 -c mysql -n mysql-ns -- bash -c "mysql -uroot -p'' -e 'select * from mydb.messages;'"
bash 复制代码
$ kubectl exec -it mysql-1 -n mysql-ns -- bash -c "mysql -uroot -p'' -e 'show slave status\G;'"

以上可以看出当旧的master被删除,新的pod重新作为master后,原有数据和主从复制都是正常状态,下面测试主从写入新数据是否正常

主库新建数据库dbtest

bash 复制代码
$ kubectl exec -it mysql-0 -c mysql -n mysql-ns -- bash -c "mysql -uroot -p'' -e 'create database dbtest;'"
$ kubectl exec -it mysql-0 -c mysql -n mysql-ns -- bash -c "mysql -uroot -p'' -e 'show databases;'"

分别查询两个slave

bash 复制代码
$ kubectl exec -it mysql-1 -c mysql -n mysql-ns -- bash -c "mysql -uroot -p'' -e 'show databases';"
bash 复制代码
$ kubectl exec -it mysql-2 -c mysql -n mysql-ns -- bash -c "mysql -uroot -p'' -e 'show databases';"

通过测试,查看到各个数据库数据,以及主从复制都是正常运行的,证明statefulset对有状态应用的管理模式是有效可用的,但需要注意的是故障转移测试中新pod的产生和数据挂载等动作都是需要时间的,那在生产过程中,故障切换的时间多久是所被允许的范围内,是需要谨慎考虑的

相关推荐
EverydayJoy^v^5 小时前
RH134学习进程——十二.运行容器(1)
linux·运维·容器
岁岁种桃花儿6 小时前
MySQL从入门到精通系列:InnoDB记录存储结构
数据库·mysql
java_logo6 小时前
OpenProject Docker 容器化部署指南:从快速启动到生产环境配置
docker·容器·openproject·openproject部署·openproject部署手册·openproject部署方案·openproject部署教程
Exquisite.8 小时前
企业高性能web服务器(4)
运维·服务器·前端·网络·mysql
cg_ssh9 小时前
Docker 下启动 Nacos 3.1.1 单机模式
运维·docker·容器
修己xj9 小时前
使用 Docker 部署 SQL Server 并导入 .mdb 文件的完整指南
运维·docker·容器
萧曵 丶10 小时前
MySQL 语句书写顺序与执行顺序对比速记表
数据库·mysql
Wiktok10 小时前
MySQL的常用数据类型
数据库·mysql
夹锌饼干12 小时前
mysql死锁排查流程--(处理mysql阻塞问题)
java·mysql
蓝队云计算12 小时前
蓝队云部署OpenClaw深度指南:避坑、优化与安全配置,从能用做到好用
运维·安全·云计算