Kubernetes 实战:使用 StatefulSet 部署多副本 MySQL 集群
website Kubernetes website and documentation repo: 项目地址: https://gitcode.com/gh_mirrors/webs/website
前言
在 Kubernetes 中部署有状态应用一直是个挑战,特别是像 MySQL 这样的数据库系统。本文将带你深入了解如何使用 StatefulSet 控制器在 Kubernetes 中部署一个多副本的 MySQL 集群,并确保其高可用性和数据持久性。
准备工作
在开始之前,请确保你已经具备以下条件:
- 一个运行中的 Kubernetes 集群
- 已配置默认的 StorageClass
- 熟悉 Kubernetes 核心概念(Pod、Service、PersistentVolume 等)
- 了解 StatefulSet 的基本工作原理
- 使用兼容 AMD64 架构的 CPU
部署架构概述
我们将部署的 MySQL 集群包含以下组件:
- 一个 ConfigMap:存储 MySQL 配置
- 两个 Service:
- 无头服务(Headless Service):用于 Pod 间直接通信
- 读服务(Read Service):负载均衡读请求
- 一个 StatefulSet:管理 MySQL Pod 的生命周期
详细部署步骤
1. 创建 ConfigMap
首先创建包含 MySQL 配置的 ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql
data:
primary.cnf: |
[mysqld]
log-bin
replica.cnf: |
[mysqld]
super-read-only
这个 ConfigMap 定义了两个配置文件:
primary.cnf
:主节点配置,启用二进制日志replica.cnf
:副本节点配置,设置为只读模式
2. 创建 Service
接下来创建两个 Service:
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
clusterIP: None
ports:
- name: mysql
port: 3306
selector:
app: mysql
---
apiVersion: v1
kind: Service
metadata:
name: mysql-read
spec:
ports:
- name: mysql
port: 3306
selector:
app: mysql
mysql
是无头服务,用于 Pod 间直接通信mysql-read
是常规服务,用于负载均衡读请求
3. 创建 StatefulSet
最后创建 StatefulSet:
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
# 生成 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
# 复制配置文件
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
# 跳过主节点的克隆
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
[[ $ordinal -eq 0 ]] && exit 0
# 从序号较低的 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
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
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
# 配置复制
mv xtrabackup_slave_info change_master_to.sql.in
sed -i 's/^CHANGE MASTER TO/CHANGE MASTER TO MASTER_HOST="mysql-0.mysql", MASTER_USER="root", MASTER_PASSWORD="", MASTER_PORT=3306,/' change_master_to.sql.in
echo "START SLAVE;" >> change_master_to.sql.in
mv change_master_to.sql.in change_master_to.sql.gen
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"
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
volumes:
- name: conf
emptyDir: {}
- name: config-map
configMap:
name: mysql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
关键实现细节
1. 初始化过程
StatefulSet 中的每个 Pod 都经历以下初始化过程:
-
init-mysql 容器:
- 从 Pod 名称中提取序号
- 生成唯一的 server-id(100 + 序号)
- 根据序号选择主节点或副本节点配置
-
clone-mysql 容器:
- 副本节点从序号较低的 Pod 克隆数据
- 使用 Percona XtraBackup 工具进行热备份
2. 数据复制
每个副本节点启动后:
- 检查是否有需要初始化的数据
- 从主节点或其他副本节点同步数据
- 配置复制参数并启动复制
3. 高可用设计
- 主节点(mysql-0)处理所有写请求
- 读请求可以通过 mysql-read 服务负载均衡到所有节点
- 使用稳定的 DNS 名称(mysql-0.mysql)确保 Pod 重启后仍能正确连接
测试集群
1. 写入测试
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
2. 读取测试
kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never -- \
mysql -h mysql-read -e "SELECT * FROM test.messages"
3. 观察负载均衡
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"
故障模拟与恢复
1. 模拟 Pod 故障
kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql /usr/bin/mysql.off
观察读请求是否自动转移到其他可用节点。
2. 扩容测试
kubectl scale statefulset mysql --replicas=5
观察新节点如何自动加入集群并同步数据。
3. 缩容测试
kubectl scale statefulset mysql --replicas=2
确保数据不会丢失,且剩余节点继续正常工作。
生产环境建议
本文示例为了演示简化了安全配置,实际生产环境中应考虑:
- 设置正确的 MySQL 密码
- 配置适当的资源限制
- 实现定期备份策略
- 监控数据库性能指标
- 考虑使用 MySQL 集群方案如 Group Replication
总结
通过 StatefulSet 部署 MySQL 集群,Kubernetes 提供了:
- 稳定的网络标识
- 有序的部署和扩展
- 持久化存储
- 自动化的故障恢复
这种模式不仅适用于 MySQL,也可以应用于其他需要持久化状态和有序部署的应用。
website Kubernetes website and documentation repo: 项目地址: https://gitcode.com/gh_mirrors/webs/website
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考