本文介绍如何利用阿里云文件存储NAS替代K8S原生NFS系统,从而实现存储系统脱离于集群之外的可弹性扩展,高可用,高性能 的部署
搭建的结构
- 一个 master 节点和多个从 master 上异步复制数据的 slave 节点组成,即一主多从复制模型。其中,master 节点可用来处理用户的读写请求,slave 节点只能用来处理用户的读请求。
- 通过statefulset部署的mysql能通过无头服务的域名发现彼此,实现主从表同步。
- 我们除了要实现这个之外,还需要将mysql的数据存储到nas文件存储系统当中
- 将主从表的配置放在configMap中
- 通过内网的负载均衡,将只读查询通过轮询或其他规则的请求打在从表一二之上。而读写请求则可以直接访问主表。
搭建思路
-
申请阿里云NAS文件系统,使用NAS存储系统;
-
创建Storage存储类;
-
创建configMap配置字典;
-
部署headless无头服务;
-
部署statefulset应用;
-
查看状态;
-
部署对外只读服务 Read-Only;
-
部署对外读写服务 Read-Write
下面我们开始一步步开始搭建部署
具体步骤
-
创建使用NAS存储系统
- 创建nas文件系统
这里其实推荐极速型,更适用于读写频繁的数据库,因为它低时延,大概只有3ms左右
-
挂载使用,我这里只演示通用型的
挂载点:0c28c4801a-l***5.cn-hangzhou.nas.aliyuncs.com
-
在linux上挂载使用,方便管理
-
#如果您使用CentOS、Redhat、Aliyun Linux操作系统,请执行以下命令 sudo yum install nfs-utils #增加同时发起的NFS请求的数量。 #请执行以下命令,将同时发起的NFS请求数量修改为128 sudo echo "options sunrpc tcp_slot_table_entries=128" >> /etc/modprobe.d/sunrpc.conf sudo echo "options sunrpc tcp_max_slot_table_entries=128" >> /etc/modprobe.d/sunrpc.conf 查看 mount -l 查看当前文件系统的容量信息 df -h
-
下面开始把NAS挂载到集群内
创建StorageClass存储类
先创建一个命名空间为mysql,这里不详细演示;
这里创建的sc能够动态帮我们创建使用存储类的持久化存储卷声明(PVC),然后存储持久化声明通知系统它需要一个使用存储类创建的持久化存储卷(PV),它会自动帮我们在挂载NAS系统中去寻找一块可用的空间并进行绑定
kind :StorageClass
name:data
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: data
mountOptions:
- nolock,tcp,noresvport
- vers=3
parameters:
server: "2b474***-cxc20.cn-shenzhen.nas.aliyuncs.com:/nasroot1/"
driver: flexvolume
provisioner: alicloud/nas
reclaimPolicy: Delete
官方文档链接:https://help.aliyun.com/document_detail/27518.html
创建configMap配置字典
kind :ConfigMap
创建mysql的配置项
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql
labels:
app: mysql
data:
master.cnf: |
# Apply this config only on the master.
[mysqld]
log-bin
slave.cnf: |
# Apply this config only on slaves.
[mysqld]
super-read-only
创建mysql的账号密码配置
apiVersion: v1
kind: ConfigMap
metadata:
name: mconfig
labels:
app: mconfig
data:
passwd.cnf: |
[mysql]
user=root
password=123456
[mysqladmin]
user=root
password=123456
部署headless无头服务
kind: Service
clusterIP: None
apiVersion: v1
kind: Service
metadata:
name: mysql-headless
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
clusterIP: None
selector:
app: mysql
#服务名称 + 命名空间 + 集群域名+@coredns的IP地址
mysql-master-svc.mysql.svc.cluster.local @10.27.0.80
部署statefulset应用
具体请参考:国际渣男的文章:https://blog.csdn.net/qq_38900565/article/details/114832445
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql-ss
spec:
selector:
matchLabels:
app: mysql
serviceName: mysql-headless
replicas: 3
template:
metadata:
labels:
app: mysql
spec:
initContainers:
- name: init-mysql
image: mysql:5.7
imagePullPolicy: IfNotPresent
command:
- bash
- "-c"
- |
set ex
# 从hostname中获取索引,比如(mysql-1)会获取(1)
[[ `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
# 拷贝对应的文件到/mnt/conf.d/文件夹中
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: ist0ne/xtrabackup
imagePullPolicy: IfNotPresent
command:
- bash
- "-c"
- |
set -ex
# 整体意思:
# 1.如果是主mysql中的xtrabackup,就不需要克隆自己了,直接退出
# 2.如果是从mysql中的xtrabackup,先判断是否是第一次创建,因为第二次重启本地就有数据库,无需克隆。若是第一次创建(通过/var/lib/mysql/mysql文件是否存在判断),就需要克隆数据库到本地。
# 如果有数据不必克隆数据,直接退出()
[[ -d /var/lib/mysql/mysql ]] && exit 0
# 如果是master数据也不必克隆
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
[[ $ordinal -eq 0 ]] && exit 0
# 从序列号比自己小一的数据库克隆数据,比如mysql-2会从mysql-1处克隆数据
ncat --recv-only mysql-ss-$(($ordinal-1)).mysql-headless 3307 | xbstream -x -C /var/lib/mysql
# 比较数据,登录
xtrabackup --user=root --password=123456 --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
imagePullPolicy: IfNotPresent
args: ["--default-authentication-plugin=mysql_native_password"]
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
ports:
- name: mysql
containerPort: 3306
volumeMounts:
- name: mconfig
mountPath: /var/passwd.cnf
subPath: var/passwd.cnf
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
resources:
requests:
cpu: 50m
memory: 50Mi
livenessProbe:
exec:
command: ["mysqladmin", "--defaults-extra-file=/var/passwd.cnf", "ping"]
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
exec:
# Check we can execute queries over TCP (skip-networking is off).
command: ["mysql", "--defaults-extra-file=/var/passwd.cnf","-h", "127.0.0.1", "-e", "SELECT 1"]
initialDelaySeconds: 5
periodSeconds: 2
timeoutSeconds: 1
- name: xtrabackup
image: ist0ne/xtrabackup
imagePullPolicy: IfNotPresent
ports:
- name: xtrabackup
containerPort: 3307
command:
- bash
- "-c"
- |
set -ex
# 确定binlog 克隆数据位置(如果binlog存在的话).
cd /var/lib/mysql
# 如果存在该文件,则该xrabackup是从现有的从节点克隆出来的。
if [[ -s xtrabackup_slave_info ]]; then
mv xtrabackup_slave_info change_master_to.sql.in
rm -f xtrabackup_binlog_info
elif [[ -f xtrabackup_binlog_info ]]; then
[[ `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 -p123456 -e "SELECT 1"; do sleep 1; done
echo "Initializing replication from clone position"
mv change_master_to.sql.in change_master_to.sql.orig
mysql -h 127.0.0.1 -p123456 <<EOF
$(<change_master_to.sql.orig),
MASTER_HOST='mysql-ss-0.mysql-headless',
MASTER_USER='root',
MASTER_PASSWORD='123456',
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 --password=123456"
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
resources:
requests:
cpu: 10m
memory: 10Mi
volumes:
- name: mconfig
configMap:
name: mconfig
items:
- key: passwd.cnf
path: var/passwd.cnf
- name: conf
emptyDir: {}
- name: config-map
configMap:
name: mysql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
storageClassName: data
resources:
requests:
storage: 2Gi
查看状态
测试看看
kubectl get pods -n mysql -o wide
kubectl exec -it mysql-ss-0 -n mysql -c mysql /bin/bash
分别进行在主数据库中创建表,插入数据,在从表1中查询,在从表2中查询
mysql-master创建一个staffsde表
在mysql-slaver-1中也能查看得到
这样mysql就部署好了,我们还需要讲应用暴露进行测试
部署对外只读服务(Only-Read)
这里只读服务可以主从三个表都可以使用,两种方法
kind:NodePort
nodeport 方式部署,将mysql以节点端口方式向外暴露,在本地进行只读连接测试(使用集群任意节点IP:nodePort进行连接)
apiVersion: v1
kind: Service
metadata:
name: mysql-Read
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
targetPort: 3306
nodePort: 30036
type: NodePort
selector:
app: mysql
kind: LoadBalancer
LoadBalancer负载均衡方式将mysql,暴露
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: mysql
spec:
ports:
- port: 80
protocol: 30036
targetPort: 3306
selector:
run: nginx
type: LoadBalancer
在navicat中连接mysql进行测试,尝试插入数据,显示只读,部署完成
部署对外读写服务(Read-Write)
这里读写服务只允许主表能够使用,通过标签选择:statefulset.kubernetes.io/pod-name: mysql-ss-0
kind:NodePort
apiVersion: v1
kind: Service
metadata:
name: mysql-Read
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
targetPort: 3306
nodePort: 30036
type: NodePort
selector:
statefulset.kubernetes.io/pod-name: mysql-ss-0
kind: LoadBalancer
LoadBalancer负载均衡方式将mysql,暴露
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: mysql
spec:
ports:
- port: 80
protocol: 30036
targetPort: 3306
selector:
statefulset.kubernetes.io/pod-name: mysql-ss-0
type: LoadBalancer
此时可以通过读写连接测试(使用集群任意节点IP:nodePort进行连接)
至此,部署完成啦;
让我们现在通过集群外的主机看看nas挂载的数据变成什么样吧
创建的staffs表可以看到是在存储卷内的。至此,部署完成,哈哈