在Kubernetes中可以快速MySQL主从集群,但需要解决以下问题:
- 主从节点关系自动建立;
- 实现一主一从或一主多从,并且读写分离;
- 从节点server-id自动生成,并且不能重复;
- 自愈功能,主节点或从节点Pod重启后,主从复制状态自动恢复,且数据不丢失;
- 数据库数据持久化存储。
1. 简化MySQL主从配置步骤
在Kubernetes中部署MySQL主从集群,需要使用GTID代替传统复制技术classic,简化主从配置。
全局事物标识:Global Transaction Identifieds。
- GTID事物是全局唯一性的,且一个事务对应一个GTID。
- 一个GTID在一个服务器上只执行一次,避免重复执行导致数据混乱或者主从不一致。
- GTID用来代替classic的复制方法,不在使用binlog+pos开启复制。而是使用master_auto_postion=1的方式自动匹配GTID断点进行复制。
- 建议在MySQL 5.7或以上版本中使用。
GTID比传统复制classic的优势:
- 更简单的实现failover,不用以前那样在需要找log_file和log_pos。
- 更简单的搭建主从复制,比传统复制更加安全。
- Slave保障节点Pod重启后,主从复制状态自动恢复,且数据不丢失。
开启GTID:
开启GTID非常简单,在MySQL配置文件中增加: gtid_mode=on
2. MySQL主从节点关系自动建立
MySQL Docker镜像中有一个文件夹 /docker-entrypoint-initdb.d/,该文件夹中的.sql .sh文件都会被自动执行,但是需要注意该文件夹中的.sql .sh文件只在MySQL容器第一次初始化数据库时才会自动执行。
因此我们可以将MySQL Master和Slave初始化主从关系的SQL语句放在 /docker-entrypoint-initdb.d/文件夹下。
MySQL Master节点 – 初始化主从命令
— create repl user
CREATE USER \’xxxx\’@\’%\’ IDENTIFIED WITH mysql_native_password BY \’xxxx\’;— grant repl
GRANT REPLICATION SLAVE ON *.* TO \’xxxx\’@\’%\’;
MySQL Slave节点 – 初始化主从命令
CHANGE MASTER TO MASTER_HOST=\’xxxx\’, MASTER_USER=\’xxxx\’, MASTER_PASSWORD=\’xxxx\’, master_auto_position=1;
3. 从节点server-id自动生成
从节点需要生成唯一的server-id,可以通过InitContainer为Slave节点自动生成server-id
initContainers: # 用于生成slave server-id,确保多个slave server-id不重复
– name: init-mysql
image: mysql:latest
imagePullPolicy: IfNotPresent
command:
– bash
– \”-c\”
– |
set -ex# 从config-map中复制配置文件到emptyDir,因为config-map目录为只读
cp /etc/mysql-slave-conf/mysql-slave.cnf /etc/conf.d/mysql-slave.cnf# 根据Pod序号生成server-id, =~ 判断字符串包含关系,BASH_REMATCH变量存储匹配结果
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
pod_number=${BASH_REMATCH[1]}# 将生成的server-id写入mysql-slave.cnf
echo -e \”\\nserver-id=$((100 + pod_number))\” >> /etc/conf.d/mysql-slave.cnf
4. MySQL主从自愈
MySQL数据库数据会存储在/var/lib/mysql文件夹下,因此只需要使用PVC对该文件夹做持久化存储即可。
MySQL Master节点在/var/lib/mysql做持久化存储后,重启或重建Pod都没有任何影响。
MySQL Slave节点在/var/lib/mysql做持久化存储后,重启或重建Pod都会丢失和Master节点的复制关系,需要重置复制关系。
可以在MySQL Slave节点在重启或重建Pod时执行以下Shell命令即可实现MySQL Slave节点自愈:
# wait mysqld running
until mysql -uroot -p${MYSQL_ROOT_PASSWORD} -e \”SELECT 1\”; do sleep 2; done# start slave sql script
mysql -uroot -p${MYSQL_ROOT_PASSWORD} <<EOF
stop slave;
reset slave;
CHANGE MASTER TO MASTER_HOST=\’xxxx\’, MASTER_USER=\’repl\’, MASTER_PASSWORD=\’xxxx\’, master_auto_position=1;
start slave;
EOF
5. MySQL配置文件
可以用ConfigMap存储MySQL Master和Slave节点需要用到的配置文件、SQL语句、Shell脚本。
---# MySQL ConfapiVersion: v1kind: ConfigMapmetadata:name: mysql-confdata:mysql-master.cnf: |[mysqld]log-bin=mysql-binserver-id=1gtid_mode=onenforce_gtid_consistency=1skip-host-cacheskip-name-resolvemysql-slave.cnf: |[mysqld]log-bin=mysql-bin# server-id=100 通过initContainer自动生成gtid_mode=onenforce_gtid_consistency=1read_only=onskip-host-cacheskip-name-resolveinit-master.sql: |CREATE USER \'repl\'@\'%\' IDENTIFIED WITH mysql_native_password BY \'123456\';GRANT REPLICATION SLAVE ON *.* TO \'repl\'@\'%\';flush privileges;init-slave.sql: |CHANGE MASTER TO MASTER_HOST=\'mysql-master\', MASTER_USER=\'repl\', MASTER_PASSWORD=\'123456\', master_auto_position=1;start slave;reset-slave.sh: |#!/bin/sh# wait mysqld startupuntil mysql -uroot -p${MYSQL_ROOT_PASSWORD} -e \"SELECT 1\"; do sleep 1; done# reset slavemysql -uroot -p${MYSQL_ROOT_PASSWORD} <<EOFstop slave;reset slave;CHANGE MASTER TO MASTER_HOST=\'mysql-master\', MASTER_USER=\'repl\', MASTER_PASSWORD=\'123456\', master_auto_position=1;start slave;EOF
6. MySQL StatefulSet
为了简化MySQL主从配置,将MySQL Master和MySQL Slave节点分离为两个单独的StatefulSet
- MySQL Master节点始终将replicas设置为1
- MySQL Slave节点replicas可以设置为1或更多
mysql-master.yaml
[code]---# MySQL Master ServiceapiVersion: v1kind: Servicemetadata:name: mysql-masterspec:type: ClusterIP # 可以提供MySQL读写服务ports:- name: mysqlport: 3306protocol: TCPtargetPort: 3306selector:app: mysql-master---# MySQL Master StatefulSetapiVersion: apps/v1kind: StatefulSetmetadata:name: mysql-masterspec:replicas: 1 # 必须是1selector:matchLabels:app: mysql-masterserviceName: mysql-mastertemplate:metadata:labels:app: mysql-masterspec:containers:- name: mysqlimage: mysql:latestimagePullPolicy: IfNotPresentlivenessProbe:exec:command: [\"mysqladmin\", \"-uroot\", \"-p$(MYSQL_ROOT_PASSWORD)\", \"ping\"]initialDelaySeconds: 10periodSeconds: 10timeoutSeconds: 3readinessProbe:exec:command: [\"mysql\", \"-uroot\", \"-p$(MYSQL_ROOT_PASSWORD)\", \"-e\", \"SELECT 1\"]initialDelaySeconds: 10periodSeconds: 10timeoutSeconds: 3env:- name: MYSQL_ROOT_PASSWORDvalue: \"root\"ports:- containerPort: 3306name: mysqlprotocol: TCPvolumeMounts:- name: mysql-master-datamountPath: /var/lib/mysql # 持久化数据库- name: mysql-confmountPath: /etc/mysql/conf.d/ # mysql启动配置文件- name: mysql-initmountPath: /docker-entrypoint-initdb.d/ # master初始化sql语句volumes:- name: mysql-confconfigMap:name: mysql-confitems:- key: mysql-master.cnfpath: mysql-master.cnf- name: mysql-initconfigMap:name: mysql-confitems:- key: init-master.sqlpath: init.sqlvolumeClaimTemplates: # NFS提供持久化存储- metadata:name: mysql-master-dataspec:accessModes:- ReadWriteOnceresources:requests:storage: 10GistorageClassName: NFS
mysql-salve.yaml
[code]---# MySQL Slave ServiceapiVersion: v1kind: Servicemetadata:name: mysql-slavespec:type: ClusterIP # 提供MySQL只读服务ports:- name: mysqlport: 3306protocol: TCPtargetPort: 3306selector:app: mysql-slave---# MySQL StatefulSetapiVersion: apps/v1kind: StatefulSetmetadata:name: mysql-slavespec:replicas: 3 # 可以是1个或更多selector:matchLabels:app: mysql-slaveserviceName: mysql-slave # 必须指定servicetemplate:metadata:labels:app: mysql-slavespec:initContainers: # 用于生成slave server-id,确保多个slave server-id不重复- name: init-mysqlimage: mysql:latestimagePullPolicy: IfNotPresentcommand:- bash- \"-c\"- |set -ex# 从config-map中复制配置文件到emptyDir,因为config-map目录为只读cp /etc/mysql-slave-conf/mysql-slave.cnf /etc/conf.d/mysql-slave.cnf# 从Pod序号生成server-id, =~ 判断字符串包含关系,BASH_REMATCH变量存储匹配结果[[ `hostname` =~ -([0-9]+)$ ]] || exit 1pod_number=${BASH_REMATCH[1]}echo -e \"\\nserver-id=$((100 + pod_number))\" >> /etc/conf.d/mysql-slave.cnfvolumeMounts:- name: server-confmountPath: /etc/conf.d/- name: mysql-slave-confmountPath: /etc/mysql-slave-conf/containers:- name: mysqlimage: mysql:latestimagePullPolicy: IfNotPresentenv:- name: MYSQL_ROOT_PASSWORDvalue: \"root\"- name: REPL_USERNAMEvalue: \"repl\"- name: REPL_PASSWORDvalue: \"123456\"- name: mysql-slave-resetmountPath: /reset-slave.shsubPath: reset-slave.shlifecycle:postStart:exec:command: [\"/bin/sh\",\"-c\",\"if [ -f \'/var/lib/mysql/mysql-bin.index\' ]; then sh /reset-slave.sh; fi\"] # 通过判断mysql-bin.index文件是否存在,来确定Pod不是初次启动,从而执行reset-slave.sh脚本,否则不执行。livenessProbe:exec:command: [\"mysqladmin\", \"-uroot\", \"-p$(MYSQL_ROOT_PASSWORD)\", \"ping\"]initialDelaySeconds: 10periodSeconds: 10timeoutSeconds: 3readinessProbe:exec:command: [\"mysql\", \"-uroot\", \"-p$(MYSQL_ROOT_PASSWORD)\", \"-e\", \"SELECT 1\"]initialDelaySeconds: 10periodSeconds: 10timeoutSeconds: 3ports:- containerPort: 3306name: mysqlprotocol: TCPvolumeMounts:- name: mysql-slave-datamountPath: /var/lib/mysql- name: server-confmountPath: /etc/mysql/conf.d/- name: mysql-slave-initmountPath: /docker-entrypoint-initdb.d/- name: mysql-slave-resetmountPath: /reset-slave.shsubPath: reset-slave.sh # 以单个文件形式挂载volumes:- name: server-confemptyDir: {}- name: mysql-slave-confconfigMap:name: mysql-confitems:- key: mysql-slave.cnfpath: mysql-slave.cnf- name: mysql-slave-initconfigMap:name: mysql-confitems:- key: init-slave.sqlpath: init-slave.sql- name: mysql-slave-resetconfigMap:name: mysql-confdefaultMode: 0777 # 默认权限为0644,需要增加执行权限items:- key: reset-slave.shpath: reset-slave.shvolumeClaimTemplates:- metadata:name: mysql-slave-dataspec:accessModes:- ReadWriteOnceresources:requests:storage: 10GistorageClassName: NFS