在K8S中部署MySQL主从
1 概述
- 主库的配置文件和从库的配置文件,也就是需要根据主从的不同使用不同的配置文件。
- 从库开始的时候,主库可能已经有数据了,需要备份这些数据到从库作为初始数据。
- 从库启动前,需要连接主库并指定同步文件的名称和从哪个位置开始同步数据,然后启动从库。
2 配置
2.1 yaml配置
# mysql-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:name: mysql-demo03labels:app: mysql 2、主从库的配置文件
主库主要是开启binlog,使得从库可以同步binlog文件来保证数据与主库一致。从库则需要设置只读,不支持写入数据。
# mysql-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:name: mysqlnamespace: mysql-demo03labels:app: mysql
data:master.cnf: |[mysqld]log-bin slave.cnf: |[mysqld]super-read-only 3、设置MySQL数据库的访问密码
使用Secret类型时,数据需要用Base64编码,可以在Linux命令行中执行命令进行Base64编码:echo -n "123456" | base64
# mysql-secret.yaml
apiVersion: v1
kind: Secret
metadata:name: mysql-secretnamespace: mysql-demo03labels:app: mysql
type: Opaque
data:password: MTIzNDU2 4、开放访问端口
使用NodePort类型的Service,支持从外部访问MySQL,注意在Windows系统需要使用WSL的IP,Linux则可以使用宿主机的IP。
# mysql-service.yaml
apiVersion: v1
kind: Service
metadata:name: mysqlnamespace: mysql-demo03labels:app: mysql
spec:type: NodePortports:- name: mysqlport: 3306nodePort: 31306selector:app: mysql
---
apiVersion: v1
kind: Service
metadata:name: mysql-readnamespace: mysql-demo03labels:app: mysql
spec:ports:- name: mysqlport: 3306selector:app: mysql 5、定义文件目录类型的PV
作为例子,使用宿主机上的目录作为PV最简单。注意Windows系统中需要用WSL里面的的目录,Linux上可以直接使用宿主机上的目录。也可以配置NFS之类的来提供PV。注意storageClassName,需要在下面是statefulset中映射上,否则PVC无法正确绑定PV。volumeClaimTemplates中补充storageClassName的配置,用于匹配PV。
# mysql-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:name: demo03-mysql-pv01labels:app: mysql
spec:storageClassName: demo03-mysql-pvaccessModes:- ReadWriteOncecapacity:storage: 1GihostPath:path: /mnt/host/d/mysql_demo03/volume-pv01
---
apiVersion: v1
kind: PersistentVolume
metadata:name: demo03-mysql-pv02labels:app: mysql
spec:storageClassName: demo03-mysql-pvaccessModes:- ReadWriteOncecapacity:storage: 1GihostPath:path: /mnt/host/d/mysql_demo03/volume-pv02
---
apiVersion: v1
kind: PersistentVolume
metadata:name: demo03-mysql-pv03labels:app: mysql
spec:storageClassName: demo03-mysql-pvaccessModes:- ReadWriteOncecapacity:storage: 1GihostPath:path: /mnt/host/d/mysql_demo03/volume-pv03 6、编排容器
使用statefulset编排容器,使用两个initContainers,一个通过提取容器的编号来区分主从,从而获取不同的配置;第二个组装启动从库的同步SQL。还提供了一个常规容器xtrabackup来备份数据,备份数据用来初始化从库数据和提供同步初始信息。
# mysql-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:name: mysqlnamespace: mysql-demo03labels:app: mysql
spec:selector:matchLabels:app: mysqlserviceName: mysqlreplicas: 3template:metadata:labels:app: mysqlspec:initContainers:- name: init-mysqlimage: ubuntu:20.04env:- name: MYSQL_ROOT_PASSWORDvalueFrom:secretKeyRef:name: mysql-secretkey: passwordcommand:- bash- "-c"- |set -ex# statefulset为pod生成hostname,里面包含序号,提取序号来作为server-id,并区分主从(第0个为主、其它为从)[[ $(hostname) =~ -([0-9]+)$ ]] || exit 1ordinal=${BASH_REMATCH[1]}echo [mysqld] > /mnt/conf.d/server-id.cnf# 由于server-id不能为 0,因此给ID加个数来避开它echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf# 如果pod序号为0则为主,其它为从,区分主从获取不同的配置if [[ ${ordinal} -eq 0 ]]; thencp /mnt/config-map/master.cnf /mnt/conf.delsecp /mnt/config-map/slave.cnf /mnt/conf.dfivolumeMounts:- name: confmountPath: /mnt/conf.d- name: config-mapmountPath: /mnt/config-map- name: clone-mysqlimage: ist0ne/xtrabackup:1.0env:- name: MYSQL_ROOT_PASSWORDvalueFrom:secretKeyRef:name: mysql-secretkey: passwordcommand:- bash- "-c"- |set -ex# 一个新的MySQL需要拷贝已有的数据,如果数据已经存在,就不需要再次拷贝了[[ -d /var/lib/mysql/mysql ]] && exit 0# 从hostname中提取pod序号[[ $(hostname) =~ -([0-9]+)$ ]] || exit 1ordinal=${BASH_REMATCH[1]}# 主MySQL不用拷贝数据[[ $ordinal == 0 ]] && exit 0# 从前一个序号的MySQL中拷贝数据ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysqlxtrabackup --prepare --target-dir=/var/lib/mysqlvolumeMounts:- name: datamountPath: /var/lib/mysqlsubPath: mysql- name: confmountPath: /etc/mysql/conf.dcontainers:- name: mysqlimage: mysql:5.7env:- name: MYSQL_ROOT_PASSWORDvalueFrom:secretKeyRef:name: mysql-secretkey: passwordports:- name: mysqlcontainerPort: 3306volumeMounts:- name: datamountPath: /var/lib/mysqlsubPath: mysql- name: confmountPath: /etc/mysql/conf.dresources:requests:cpu: 500mmemory: 1GilivenessProbe:exec:command: ["mysqladmin", "ping", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]initialDelaySeconds: 30periodSeconds: 10timeoutSeconds: 5readinessProbe:exec:command: ["mysqladmin", "ping", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]initialDelaySeconds: 5periodSeconds: 2timeoutSeconds: 1- name: xtrabackupimage: ist0ne/xtrabackup:1.0ports:- name: xtrabackupcontainerPort: 3307env:- name: MYSQL_ROOT_PASSWORDvalueFrom:secretKeyRef:name: mysql-secretkey: passwordcommand:- bash- "-c"- |set -excd /var/lib/mysql# 启动从库前,需要执行"CHANGE MASTER TO" SQL,里面需要提供MASTER_LOG_FILE(binlog文件名)和MASTER_LOG_POS(同步数据位置)# 如果通过xtrabackup备份了数据文件,则可以从备份文件里获取到这两个信息if [[ -f xtrabackup_slave_info ]]; then# xtrabackup_slave_info文件来自于从库,文件里已经包含"CHANGE MASTER TO" SQL,改名字后直接使用即可mv xtrabackup_slave_info change_master_to.sql.inrm -f xtrabackup_binlog_infoelif [[ -f xtrabackup_binlog_info ]]; then# xtrabackup_binlog_info文件来自于主库,需要从中提取MASTER_LOG_FILE和MASTER_LOG_POS并组装"CHANGE MASTER TO" SQL[[ $(cat xtrabackup_binlog_info) =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1rm xtrabackup_binlog_infoecho "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.infiif [[ -f change_master_to.sql.in ]]; then# 等待MySQL容器ready之后才能执行上面组装的SQLecho "Waiting for mysqld to be ready(accepting connections)"until mysql -uroot -p${MYSQL_ROOT_PASSWORD} -e "SELECT 1"; do sleep 1; doneecho "Initializing replication from clone position"mv change_master_to.sql.in change_master_to.sql.orig# 执行"CHANGE MASTER TO" SQL,并启动Slave库同步mysql -uroot -p${MYSQL_ROOT_PASSWORD} << EOF$(< change_master_to.sql.orig),MASTER_HOST='mysql-0.mysql.weixnie',MASTER_USER='root',MASTER_PASSWORD='${MYSQL_ROOT_PASSWORD}',MASTER_CONNECT_RETRY=10;START SLAVE;EOFfi# ncat监听3307端口,有新数据则备份exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \"xtrabackup --backup --slave-info --stream=xbstream --user=root --password=${MYSQL_ROOT_PASSWORD}"volumeMounts:- name: datamountPath: /var/lib/mysqlsubPath: mysql- name: confmountPath: /etc/mysql/conf.dvolumes:- name: confemptyDir: {}- name: config-mapconfigMap:name: mysqlvolumeClaimTemplates:- metadata:name: dataspec:accessModes: ["ReadWriteOnce"]resources:requests:storage: 1GistorageClassName: demo03-mysql-pvvolumeMode: Filesystem 通过kubectl apply -f xx.yaml,按顺序执行上面yaml文件即可。
2.2 一些问题
1、容器init-mysql需要使用正确的镜像。
这个容器中用到了bash脚本,需要使用提供bash命令的镜像,比如上例换成了ubuntu:20.04镜像,提供完整的Linux命令,方便测试时可以多一些命令可以使用,缺点是镜像比较大。正式使用时可以选用小一点的镜像。
2、Service需要在Statefulset之前执行
Statefulset中从库需要连接主库,由于Pod本身的IP是不确定的,所以需要Service来提供稳定的连接服务。如果Service后执行,需要重启Statefulset。
3、不能使用127.0.0.1 IP
Statefulset里面有bash脚本中访问MySQL库的时候,如果用了-h 127.0.0.1,带着这个参数实际是访问不了的,在Pod中并不认这个IP,应该使用Service名称访问。
4、配置好PV
没有PV的时候,Pod也启动不起来。需要先配置好PV,最简单的配置PV方式就是使用宿主机的目录,如果是Windows系统,需要注意要把宿主机的目录转为WSL里的目录,上例使用的就是Windows的目录。
2.3 检验
1、使用kubectl get pod -n mysql-demo03查看Pod的状态,观察STATUS字段需要为Running,READY字段里的两个数字要相等,这样Pod才正常运行。
2、使用kubectl exec -it mysql-0 -n mysql-demo03 -- /bin/bash 进入Pod,用mysql -uroot -p命令登录MySQL,在里面能够正常执行SQL命令。主库可查可写,从库只能查不能写。
3、修改statefulset里的replicas字段值,增减从库数量,通过kubectl rollout status sts/mysql 命令查看从库的增减过程。
4、如果遇到问题,则使用kubectl logs pod_name -c container_name -n mysql-demo03来查看具体的日志,根据日志解决问题。
3 小结
使用K8S来部署主从,有点在yaml里写shell脚本的嫌疑,如果不使用Go语言来自定义Operator,就只能先用这种方式了。
