Kubernetes实战:使用StatefulSet部署高可用MySQL集群
website Kubernetes website and documentation repo: 项目地址: https://gitcode.com/gh_mirrors/webs/website
概述
在Kubernetes中部署有状态应用一直是个挑战,特别是像MySQL这样的数据库系统。本文将详细介绍如何使用Kubernetes的StatefulSet控制器来部署一个高可用的MySQL集群,包含一个主节点和多个副本节点,采用基于行的异步复制机制。
请注意,本文展示的配置不适合生产环境,MySQL设置保留了不安全的默认值,目的是聚焦于在Kubernetes中运行有状态应用的通用模式。
前置条件
在开始之前,请确保您具备以下条件:
- 已经安装并配置好Kubernetes集群
- 集群已配置默认的StorageClass
- 熟悉Kubernetes核心概念,包括PersistentVolumes、StatefulSets、Pods、Services和ConfigMaps
- 了解MySQL基础知识会有帮助,但不是必须的
- 使用默认命名空间或没有冲突对象的其他命名空间
- 使用AMD64兼容的CPU
部署架构
我们将部署的MySQL集群包含以下组件:
- ConfigMap:存储MySQL配置
- 两个Service:
- 一个无头(Headless)Service用于Pod间直接通信
- 一个普通Service用于读请求负载均衡
- StatefulSet:管理MySQL Pod的生命周期
详细部署步骤
1. 创建ConfigMap
首先创建ConfigMap来存储MySQL配置:
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql
data:
primary.cnf: |
[mysqld]
log-bin
replica.cnf: |
[mysqld]
super-read-only
这个ConfigMap为主节点和副本节点提供了不同的配置:
- 主节点启用二进制日志(binlog)
- 副本节点设置为只读模式
2. 创建Service
接下来创建两个Service:
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
clusterIP: None
selector:
app: mysql
---
apiVersion: v1
kind: Service
metadata:
name: mysql-read
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
selector:
app: mysql
mysql
是无头Service,为每个Pod提供稳定的DNS名称mysql-read
是普通Service,为读请求提供负载均衡
3. 创建StatefulSet
最后创建StatefulSet来管理MySQL Pod:
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
# 基于Pod序号生成server-id
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
echo [mysqld] > /mnt/conf.d/server-id.cnf
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/primary.cnf /mnt/conf.d/
else
cp /mnt/config-map/replica.cnf /mnt/conf.d/
fi
volumeMounts:
- name: conf
mountPath: /mnt/conf.d
- name: config-map
mountPath: /mnt/config-map
- name: clone-mysql
image: gcr.io/google-samples/xtrabackup:1.0
command:
- bash
- "-c"
- |
set -ex
# 如果数据已经存在,跳过克隆
[[ -d /var/lib/mysql/mysql ]] && exit 0
# 跳过主实例(序号0)的克隆
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
[[ $ordinal -eq 0 ]] && exit 0
# 从序号减1的Pod克隆数据
ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
# 准备备份
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:
command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
initialDelaySeconds: 5
periodSeconds: 2
timeoutSeconds: 1
- name: xtrabackup
image: gcr.io/google-samples/xtrabackup:1.0
ports:
- name: xtrabackup
containerPort: 3307
command:
- bash
- "-c"
- |
set -ex
cd /var/lib/mysql
# 确定主服务器是否已经有数据
if [[ -f xtrabackup_slave_info ]]; then
# 如果已经配置为副本,则应用备份
xtrabackup --prepare --target-dir=/var/lib/mysql
# 启动复制
mv xtrabackup_slave_info change_master_to.sql.in
rm -f xtrabackup_binlog_info
elif [[ -f xtrabackup_binlog_info ]]; then
# 如果这是主服务器,只记住binlog位置
[[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
rm -f xtrabackup_binlog_info xtrabackup_slave_info
echo "SET GLOBAL gtid_purged='$2';" > change_master_to.sql.in
echo "CHANGE MASTER TO MASTER_LOG_FILE='$1', MASTER_LOG_POS=$2;" >> change_master_to.sql.in
fi
# 检查是否需要初始化MySQL
if [[ ! -d /var/lib/mysql/mysql ]]; then
mysql_install_db --user=mysql --datadir=/var/lib/mysql
fi
# 启动临时服务器
mysqld --datadir=/var/lib/mysql --socket=/var/run/mysqld/mysqld.sock \
--port=3306 --log-error=/var/log/mysql/error.log \
--pid-file=/var/run/mysqld/mysqld.pid \
--daemonize
# 等待MySQL启动
for i in {1..30}; do
mysql -S /var/run/mysqld/mysqld.sock -e "SELECT 1" && break
sleep 1
done
# 设置复制
if [[ -f change_master_to.sql.in ]]; then
mysql -S /var/run/mysqld/mysqld.sock -e "$(cat change_master_to.sql.in)"
fi
# 停止临时服务器
mysqladmin -S /var/run/mysqld/mysqld.sock shutdown
# 开始监听克隆请求
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"
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: 10Gi
这个StatefulSet定义包含几个关键部分:
-
initContainers:
init-mysql
:基于Pod序号配置MySQL server-idclone-mysql
:从之前的Pod克隆数据(使用Percona XtraBackup)
-
Containers:
mysql
:运行MySQL服务器的主容器xtrabackup
:处理复制和克隆的sidecar容器
-
volumeClaimTemplates:为每个Pod创建独立的PersistentVolume
工作原理详解
Pod初始化流程
-
init-mysql容器:
- 从Pod名称中提取序号(如mysql-0的0)
- 生成server-id(100 + 序号)
- 根据序号决定使用主节点还是副本节点配置
-
clone-mysql容器:
- 主节点(mysql-0)跳过克隆
- 副本节点从序号减1的Pod克隆数据(如mysql-1从mysql-0克隆)
- 使用XtraBackup准备数据
-
mysql容器:
- 启动MySQL服务器
- 使用配置好的server-id和主/副本配置
-
xtrabackup容器:
- 如果是副本节点,配置复制关系
- 监听来自新Pod的克隆请求
稳定的网络标识
StatefulSet为每个Pod提供稳定的网络标识:
- Pod名称:mysql-0, mysql-1, mysql-2
- DNS名称:mysql-0.mysql, mysql-1.mysql, mysql-2.mysql
这种稳定性对于数据库复制至关重要,因为副本需要始终知道如何连接到主节点。
测试MySQL集群
写入测试
连接到主节点(mysql-0.mysql)执行写操作:
kubectl run mysql-client --image=mysql:5.7 -i --rm --restart=Never -- \
mysql -h mysql-0.mysql <<EOF
CREATE DATABASE test;
CREATE TABLE test.messages (message VARCHAR(250));
INSERT INTO test.messages VALUES ('hello');
EOF
读取测试
通过mysql-read Service执行读操作,请求会被负载均衡到所有可用的副本:
kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never -- \
mysql -h mysql-read -e "SELECT * FROM test.messages"
观察负载均衡
运行以下命令观察读请求如何分布到不同副本:
kubectl run mysql-client-loop --image=mysql:5.7 -i -t --rm --restart=Never -- \
bash -ic "while sleep 1; do mysql -h mysql-read -e 'SELECT @@server_id,NOW()'; done"
故障恢复测试
模拟Pod故障
-
破坏readiness探针:
kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql /usr/bin/mysql.off
观察该Pod变为NotReady,但读服务仍然可用
-
恢复Pod:
kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql.off /usr/bin/mysql
删除Pod
kubectl delete pod mysql-2
StatefulSet会自动重建Pod并保持原有身份和存储
节点维护
- 排空节点:
kubectl drain <node-name> --force --delete-emptydir-data --ignore-daemonsets
- 观察Pod在其他节点重建
- 取消节点排空:
kubectl uncordon <node-name>
扩缩容操作
扩容
kubectl scale statefulset mysql --replicas=5
新创建的副本会自动从现有Pod克隆数据并加入复制拓扑
缩容
kubectl scale statefulset mysql --replicas=3
注意:缩容不会自动删除PVC,需要手动清理不需要的PVC
清理资源
# 删除StatefulSet和Pod
kubectl delete statefulset mysql
# 删除Service
kubectl delete service mysql mysql-read
# 删除ConfigMap
kubectl delete configmap mysql
# 删除PVC(可选)
kubectl delete pvc -l app=mysql
总结
通过本文,我们学习了如何在Kubernetes中使用StatefulSet部署高可用的MySQL集群。关键点包括:
- StatefulSet为有状态应用提供稳定的网络标识和存储
- 精心设计的初始化流程确保数据一致性和正确复制
- 读写分离架构提高查询性能
- 自动故障恢复保障服务可用性
- 灵活的扩缩容能力适应业务需求变化
这种模式不仅适用于MySQL,也可以应用于其他需要持久化存储和复制功能的有状态应用。
website Kubernetes website and documentation repo: 项目地址: https://gitcode.com/gh_mirrors/webs/website
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考