kavin

基于K8S的StatefulSet部署MySQL集群

kavin 运维技术 2022-11-18 380浏览 0

基于K8S的StatefulSet部署MySQL集群

环境:

基于K8S的StatefulSet部署MySQL集群

# 采用NFS存储卷的方式 持久化存储mysql数据目录

需求:

展示如何使用 StatefulSet 控制器运行一个有状态的应用程序。此例是多副本的 MySQL 数据库。 示例应用的拓扑结构有一个主服务器和多个副本,使用异步的基于行(Row-Based) 的数据复制。

基于K8S的StatefulSet部署MySQL集群

  • 搭建一个“主从复制”(Maser-Slave Replication)的 MySQL 集群
  • 存在一个主节点【master】,有多个从节点【slave】
  • 从节点可以水平拓展
  • 所有的写操作,只能在主节点上执行
  • 读操作可以在所有节点上执行

一、部署NFS服务器

#服务器安装nfs服务,提供nfs存储功能
1、安装nfs-utils
yuminstallnfs-utils(centos)
或者apt-getinstallnfs-kernel-server(ubuntu)

2、启动服务
systemctlenablenfs-server
systemctlstartnfs-server

3、创建共享目录完成共享配置
mkdir/home/nfs#创建共享目录

4、编辑共享配置
vim/etc/exports
#语法格式:共享文件路径客户机地址(权限)#这里的客户机地址可以是IP,网段,域名,也可以是任意*
/home/nfs*(rw,async,no_root_squash)
#服务器安装nfs服务,提供nfs存储功能
1、安装nfs-utils
yuminstallnfs-utils(centos)
或者apt-getinstallnfs-kernel-server(ubuntu)

2、启动服务
systemctlenablenfs-server
systemctlstartnfs-server

3、创建共享目录完成共享配置
mkdir/home/nfs#创建共享目录

4、编辑共享配置
vim/etc/exports
#语法格式:共享文件路径客户机地址(权限)#这里的客户机地址可以是IP,网段,域名,也可以是任意*
/home/nfs*(rw,async,no_root_squash)
服务自检命令
exportfs-arv


5、重启服务
systemctlrestartnfs-server


6、本机查看nfs共享目录
#showmount-e服务器IP地址(如果提示命令不存在,则需要yuminstallshowmount)

showmount-e127.0.0.1
/home/nfs*



7、客户端模拟挂载[所有k8s的节点都需要安装客户端]
[root@master-1~]#yuminstallnfs-utils(centos)
或者apt-getinstallnfs-common(ubuntu)
[root@master-1~]#mkdir/test
[root@master-1~]#mount-tnfs172.16.201.209:/home/nfs/test

#取消挂载
[root@master-1~]#umount/test

二、配置PV 动态供给(NFS StorageClass),创建pvc

部署NFS实现自动创建PV插件: 一共设计到4个yaml 文件 ,官方的文档有详细的说明

https://github.com/kubernetes-incubator/external-storage

基于K8S的StatefulSet部署MySQL集群 基于K8S的StatefulSet部署MySQL集群

root@k8s-master1:~#mkdir/root/pvc
root@k8s-master1:~#cd/root/pvc

创建rbac.yaml 文件

root@k8s-master1:pvc#catrbac.yaml
kind:ServiceAccount
apiVersion:v1
metadata:
name:nfs-client-provisioner
---
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
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
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
subjects:
-kind:ServiceAccount
name:nfs-client-provisioner
#replacewithnamespacewhereprovisionerisdeployed
namespace:default
roleRef:
kind:Role
name:leader-locking-nfs-client-provisioner
apiGroup:rbac.authorization.k8s.io

创建deployment.yaml 文件

官方默认的镜像地址,国内可能无法下载,可以使用 image:

fxkjnj/nfs-client-provisioner:latest

#定义NFS 服务器的地址,共享目录名称

root@k8s-master1:pvc#catdeployment.yaml
apiVersion:v1
kind:ServiceAccount
metadata:
name:nfs-client-provisioner
---
kind:Deployment
apiVersion:apps/v1
metadata:
name:nfs-client-provisioner
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:fxkjnj/nfs-client-provisioner:latest
volumeMounts:
-name:nfs-client-root
mountPath:/persistentvolumes
env:
-name:PROVISIONER_NAME
value:fuseim.pri/ifs
-name:NFS_SERVER
value:172.16.201.209
-name:NFS_PATH
value:/home/nfs
volumes:
-name:nfs-client-root
nfs:
server:172.16.201.209
path:/home/nfs

创建class.yaml

root@k8s-master1:pvc#catclass.yaml
apiVersion:storage.k8s.io/v1
kind:StorageClass
metadata:
name:managed-nfs-storage
provisioner:fuseim.pri/ifs#orchooseanothername,mustmatchdeployment'senvPROVISIONER_NAME'
parameters:
archiveOnDelete:"true"

部署

root@k8s-master1:pvc#kubectlapply-f.


#查看存储卷
root@k8s-master1:pvc#kubectlgetsc
NAMEPROVISIONERRECLAIMPOLICYVOLUMEBINDINGMODEALLOWVOLUMEEXPANSIONAGE
managed-nfs-storagefuseim.pri/ifsDeleteImmediatefalse25h

三、编写mysql 相关yaml文件

MySQL 示例部署包含一个 ConfigMap、两个 Service 与一个 StatefulSet。

ConfigMap:

vim mysql-configmap.yaml

apiVersion:v1
kind:ConfigMap
metadata:
name:mysql
labels:
app:mysql
data:
master.cnf:|
#Applythisconfigonlyonthemaster.
[mysqld]
log-bin
slave.cnf:|
#Applythisconfigonlyonslaves.
[mysqld]
super-read-only

说明:

在这里,我们定义了 master.cnf 和 slave.cnf 两个 MySQL 的配置文件

  • master.cnf 开启了log-bin,可以使用二进制日志文件的方式进行主从复制.
  • slave.cnf 开启了 super-read-only ,表示从节点只接受主节点的数据同步的所有写的操作,拒绝其他的写入操作,对于用户来说就是只读的
  • master.cnf 和 slave.cnf 已配置文件的形式挂载到容器的目录中

Service:

vim mysql-services.yaml

#HeadlessserviceforstableDNSentriesofStatefulSetmembers.
apiVersion:v1
kind:Service
metadata:
name:mysql
labels:
app:mysql
spec:
ports:
-name:mysql
port:3306
clusterIP:None
selector:
app:mysql
---
#ClientserviceforconnectingtoanyMySQLinstanceforreads.
#Forwrites,youmustinsteadconnecttothemaster:mysql-0.mysql.
apiVersion:v1
kind:Service
metadata:
name:mysql-read
labels:
app:mysql
spec:
ports:
-name:mysql
port:3306
selector:
app:mysql

说明:

clusterIP: None,使用无头服务 Headless Service(相比普通Service只是将spec.clusterIP定义为None,也就是没有clusterIP,直接使用endport 来通信)来维护Pod网络身份,会为每个Pod分配一个数字编号并且按照编号顺序部署。还需要在StatefulSet添加serviceName: “mysql”字段指定StatefulSet控制器

另外statefulset控制器网络标识,体现在主机名和Pod A记录:

• 主机名:<statefulset名称>-<编号>

例如: mysql-0

• Pod DNS A记录:<statefulset名称-编号>.<service-name> .<namespace>.svc.cluster.local (POD 之间通过DNS A 记录互相通信)

例如:

mysql-0.mysql.default.svc.cluster.local

StatefulSet:

vim mysql-statefulset.yaml

apiVersion:apps/v1
kind:StatefulSet
metadata:
name:mysql
spec:
selector:
matchLabels:
app:mysql
serviceName:mysql
replicas:3
template:
metadata:
labels:
app:mysql
spec:
initContainers:
-name:init-mysql
image:mysql:5.7
command:
-bash
-"-c"
-|
set-ex
#Generatemysqlserver-idfrompodordinalindex.
[[`hostname`=~-([0-9]+)$]]||exit1
ordinal=${BASH_REMATCH[1]}
echo[mysqld]>/mnt/conf.d/server-id.cnf
#Addanoffsettoavoidreservedserver-id=0value.
echoserver-id=$((100+$ordinal))>>/mnt/conf.d/server-id.cnf
#Copyappropriateconf.dfilesfromconfig-maptoemptyDir.
if[[$ordinal-eq0]];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:fxkjnj/xtrabackup:1.0
command:
-bash
-"-c"
-|
set-ex
#Skipthecloneifdataalreadyexists.
[[-d/var/lib/mysql/mysql]]&&exit0
#Skipthecloneonmaster(ordinalindex0).
[[`hostname`=~-([0-9]+)$]]||exit1
ordinal=${BASH_REMATCH[1]}
[[$ordinal-eq0]]&&exit0
#Clonedatafrompreviouspeer.
ncat--recv-onlymysql-$(($ordinal-1)).mysql3307|xbstream-x-C/var/lib/mysql
#Preparethebackup.
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
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:
#CheckwecanexecutequeriesoverTCP(skip-networkingisoff).
command:["mysql","-h","127.0.0.1","-e","SELECT1"]
initialDelaySeconds:5
periodSeconds:2
timeoutSeconds:1
-name:xtrabackup
image:fxkjnj/xtrabackup:1.0
ports:
-name:xtrabackup
containerPort:3307
command:
-bash
-"-c"
-|
set-ex
cd/var/lib/mysql

#Determinebinlogpositionofcloneddata,ifany.
if[[-fxtrabackup_slave_info&&"x$(<xtrabackup_slave_info)"!="x"]];then
#XtraBackupalreadygeneratedapartial"CHANGEMASTERTO"query
#becausewe'recloningfromanexistingslave.(Needtoremovethetailingsemicolon!)
catxtrabackup_slave_info|sed-E's/;$//g'>change_master_to.sql.in
#Ignorextrabackup_binlog_infointhiscase(it'suseless).
rm-fxtrabackup_slave_infoxtrabackup_binlog_info
elif[[-fxtrabackup_binlog_info]];then
#We'recloningdirectlyfrommaster.Parsebinlogposition.
[[`catxtrabackup_binlog_info`=~^(.*?)[[:space:]]+(.*?)$]]||exit1
rm-fxtrabackup_binlog_infoxtrabackup_slave_info
echo"CHANGEMASTERTOMASTER_LOG_FILE='${BASH_REMATCH[1]}',\
MASTER_LOG_POS=${BASH_REMATCH[2]}">change_master_to.sql.in
fi

#Checkifweneedtocompleteaclonebystartingreplication.
if[[-fchange_master_to.sql.in]];then
echo"Waitingformysqldtobeready(acceptingconnections)"
untilmysql-h127.0.0.1-e"SELECT1";dosleep1;done

echo"Initializingreplicationfromcloneposition"
mysql-h127.0.0.1\
-e"$(<change_master_to.sql.in),\
MASTER_HOST='mysql-0.mysql',\
MASTER_USER='root',\
MASTER_PASSWORD='',\
MASTER_CONNECT_RETRY=10;\
STARTSLAVE;"||exit1
#Incaseofcontainerrestart,attemptthisat-most-once.
mvchange_master_to.sql.inchange_master_to.sql.orig
fi

#Startaservertosendbackupswhenrequestedbypeers.
execncat--listen--keep-open--send-only--max-conns=13307-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:
storageClassName:"managed-nfs-storage"
accessModes:["ReadWriteOnce"]
resources:
requests:
storage:0.5Gi

说明:

  • 使用xtrbackup 工具进行容器初始化数据的备份,https://www.toutiao.com/i6999565563710292484
  • 使用linux 自带的ncat 工具进行容器初始化数据的拷贝[使用ncat指令,远程地从前一个节点拷贝数据到本地] https://www.cnblogs.com/chengd/p/7565280.html
  • 使用mysql的binlog 主从复制 来保证主从之间的数据一致
  • 利用pod的主机名的序号来判断当前节点为主节点还是从节点,再根据对于节点拷贝不同的配置文件到指定位置
  • 使用mysqladmin的ping 作为数据库的健康检测方式
  • 使用nfs存储的 PV 动态供给(StorageClass),持久化mysql的数据文件

四、部署并测试

root@k8s-master1:~/kubernetes/mysql#ll
total24
drwxr-xr-x2rootroot4096Nov316:42./
drwxr-xr-x8rootroot4096Nov313:33../
-rw-r--r--1rootroot278Nov222:15mysql-configmap.yaml
-rw-r--r--1rootroot556Nov222:08mysql-services.yaml
-rw-r--r--1rootroot5917Nov314:22mysql-statefulset.yaml

root@k8s-master1:~/kubernetes/mysql#kubectlapply-f.
configmap/mysqlcreate
service/mysqlcreate
service/mysql-readcreate
statefulset.apps/mysqlcreate


#动态追踪查看Pod的状态:
root@k8s-master1:~/kubernetes/mysql#kubectlgetpods-lapp=mysql--watch
NAMEREADYSTATUSRESTARTSAGE
mysql-02/2Running03h12m
mysql-12/2Running03h11m
mysql-22/2Running03h10m

可以看到,StatefulSet 启动成功后,将会有三个 Pod 运行。

接下来,我们可以尝试向这个 MySQL 集群发起请求,执行一些 SQL 操作来验证它是否正常:

kubectlrunmysql-client--image=mysql:5.7-i--rm--restart=Never--\
mysql-hmysql-0.mysql<<EOF
CREATEDATABASEtest;
CREATETABLEtest.messages(messageVARCHAR(250));
INSERTINTOtest.messagesVALUES('hello');
EOF

如上所示,我们通过启动一个容器,使用 MySQL client 执行了创建数据库和表、以及插入数据的操作。需要注意的是,我们连接的 MySQL 的地址必须是 mysql-0.mysql(即:Master 节点的 DNS A 记录, 因为POD 之间通过DNS A 记录互相通信)只有 Master 节点才能处理写操作。

而通过连接 mysql-read 这个 Service,我们就可以用 SQL 进行读操作,如下所示:

kubectlrunmysql-client--image=mysql:5.7-i-t--rm--restart=Never--\
mysql-hmysql-read-e"SELECT*FROMtest.messages"


#你应该获得如下输出:
Waitingforpoddefault/mysql-clienttoberunning,statusisPending,podready:false
+---------+
|message|
+---------+
|hello|
+---------+
pod"mysql-client"deleted

或者:

root@k8s-master1:~/kubernetes/mysql#kubectlrun-it--rm--image=mysql:5.7--restart=Nevermysql-client--mysql-hmysql-read
Ifyoudon'tseeacommandprompt,trypressingenter.
WelcometotheMySQLmonitor.Commandsendwith;or\g.
YourMySQLconnectionidis7251
Serverversion:5.7.36MySQLCommunityServer(GPL)

Copyright(c)2000,2021,Oracleand/oritsaffiliates.

OracleisaregisteredtrademarkofOracleCorporationand/orits
affiliates.Othernamesmaybetrademarksoftheirrespective
owners.

Type'help;'or'\h'forhelp.Type'\c'toclearthecurrentinputstatement.

mysql>SELECT*FROMtest.messages;
+---------+
|message|
+---------+
|hello|
+---------+
1rowinset(0.00sec)

mysql>

继续浏览有关 系统运维 的文章
发表评论