各位观众老爷,晚上好!我是你们的老朋友,今天咱们不聊风花雪月,就来聊聊MySQL在K8s这个大舞台上的那些事儿。今天的主题是:MySQL
在K8s
中的StatefulSet
:如何实现持久化存储与高可用部署。
简单来说,就是教大家如何在K8s里,用一种比较靠谱的方式,把MySQL搭起来,并且保证数据不丢,服务稳定。
一、 为什么是StatefulSet?
想象一下,如果你要把一群小猫(MySQL实例)放到一个猫舍(K8s集群)里。每只猫都需要有自己的名字,自己的房间(持久化存储),并且不能随便换房间,否则它会找不到自己的猫粮(数据)。
-
Deployment: 如果你用Deployment,K8s会随机创建和删除Pod,每次Pod重启,都是一个新的Pod。这就意味着,你的MySQL实例会丢失所有数据,这显然不行。Deployment适合管理无状态应用,比如Nginx,它不需要记住任何东西。
-
StatefulSet: StatefulSet就是为这种有状态应用而生的。它保证了:
- 稳定的网络标识: 每个Pod都有一个固定的域名,比如
mysql-0.mysql.default.svc.cluster.local
。即使Pod重启,域名也不会变。 - 稳定的持久化存储: 每个Pod都绑定一个PersistentVolumeClaim (PVC),PVC会关联到实际的存储(比如云硬盘)。即使Pod重启,存储也会被重新挂载到新的Pod上。
- 有序的部署和删除: Pod会按照
0, 1, 2...
的顺序创建,并且按照相反的顺序删除。这样可以保证数据一致性。
- 稳定的网络标识: 每个Pod都有一个固定的域名,比如
二、 准备工作
在开始之前,你需要确保你的K8s集群已经搭建好,并且安装了kubectl。还需要一个可以提供持久化存储的StorageClass。大部分云厂商都会提供默认的StorageClass,如果没有,你需要自己创建一个。
你可以通过下面的命令查看现有的StorageClass:
kubectl get storageclass
如果你的集群没有默认的StorageClass,或者你需要使用特定的StorageClass,可以创建一个:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: my-storage-class
provisioner: kubernetes.io/aws-ebs # 根据你的云平台修改
parameters:
type: gp2 # 根据你的云平台修改
reclaimPolicy: Delete # Delete/Retain, 删除PVC时是否删除存储卷
三、 YAML文件:StatefulSet定义
接下来,就是重头戏了:编写StatefulSet的YAML文件。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
serviceName: mysql # 创建一个headless service
replicas: 3
template:
metadata:
labels:
app: mysql
spec:
terminationGracePeriodSeconds: 10
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "your_root_password"
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: [ "ReadWriteOnce" ] # 读写权限
resources:
requests:
storage: 10Gi # 存储大小
storageClassName: my-storage-class # 使用刚才创建的StorageClass
代码解读:
apiVersion: apps/v1
: 指定API版本,StatefulSet属于apps/v1版本。kind: StatefulSet
: 指定资源类型为StatefulSet。metadata.name: mysql
: StatefulSet的名称。spec.selector
: 用于匹配Pod的标签。spec.serviceName: mysql
: 指定一个Headless Service的名称。Headless Service不会分配Cluster IP,而是直接返回Pod的IP地址。这对于StatefulSet来说非常重要,因为我们需要通过Pod的域名来访问它们。spec.replicas: 3
: 指定创建3个MySQL实例。spec.template
: Pod的模板,定义了Pod的各种属性。spec.terminationGracePeriodSeconds: 10
: Pod终止前的缓冲时间,单位是秒。spec.containers
: 容器的定义。name: mysql
: 容器的名称。image: mysql:8.0
: 使用的MySQL镜像。ports
: 暴露的端口。env
: 环境变量,这里设置了MySQL的root密码。volumeMounts
: 挂载存储卷。name: mysql-data
: 存储卷的名称,与volumeClaimTemplates
中定义的名称一致。mountPath: /var/lib/mysql
: 将存储卷挂载到容器内的/var/lib/mysql
目录,这是MySQL默认的数据存储目录。
volumeClaimTemplates
: 定义PVC的模板。K8s会为每个Pod创建一个PVC,并使用这个模板来创建。metadata.name: mysql-data
: PVC的名称。spec.accessModes: [ "ReadWriteOnce" ]
: 指定访问模式为ReadWriteOnce,表示只能被一个Pod读写。spec.resources
: 存储资源请求。requests.storage: 10Gi
: 请求10Gi的存储空间。
spec.storageClassName: my-storage-class
: 指定使用的StorageClass。
四、 YAML文件:Headless Service定义
StatefulSet需要一个Headless Service才能正常工作。Headless Service不会分配Cluster IP,而是直接返回Pod的IP地址。
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
clusterIP: None # 表明这是一个Headless Service
selector:
app: mysql
ports:
- port: 3306
name: mysql
代码解读:
apiVersion: v1
: 指定API版本。kind: Service
: 指定资源类型为Service。metadata.name: mysql
: Service的名称,与StatefulSet中的spec.serviceName
一致。spec.clusterIP: None
: 将clusterIP
设置为None
,表明这是一个Headless Service。spec.selector
: 用于匹配Pod的标签。ports
: 暴露的端口。
五、 应用YAML文件
保存上面的两个YAML文件,分别命名为mysql-statefulset.yaml
和mysql-service.yaml
。然后,使用kubectl命令来应用它们:
kubectl apply -f mysql-service.yaml
kubectl apply -f mysql-statefulset.yaml
稍等片刻,K8s就会创建StatefulSet和Headless Service。你可以使用下面的命令来查看Pod的状态:
kubectl get pods
你应该看到类似下面的输出:
NAME READY STATUS RESTARTS AGE
mysql-0 1/1 Running 0 1m
mysql-1 1/1 Running 0 50s
mysql-2 1/1 Running 0 40s
注意,Pod的名称是有序的,分别是mysql-0
, mysql-1
, mysql-2
。
你也可以查看PVC的状态:
kubectl get pvc
你应该看到类似下面的输出:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mysql-data-mysql-0 Bound pvc-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 10Gi RWO my-storage-class 1m
mysql-data-mysql-1 Bound pvc-yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy 10Gi RWO my-storage-class 50s
mysql-data-mysql-2 Bound pvc-zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz 10Gi RWO my-storage-class 40s
每个Pod都有一个对应的PVC,并且PVC已经绑定到了实际的存储卷。
六、 连接MySQL
现在,你可以通过Pod的域名来连接MySQL了。比如,你可以使用mysql-0.mysql.default.svc.cluster.local
来连接第一个MySQL实例。
首先,进入集群内部的Pod:
kubectl exec -it mysql-0 -- bash
然后在Pod内部,使用mysql客户端连接MySQL:
mysql -h mysql-0.mysql.default.svc.cluster.local -u root -p
输入你在YAML文件中设置的root密码,就可以成功连接到MySQL了。
七、 高可用部署
上面的例子只是一个简单的部署,并没有实现真正的高可用。要实现高可用,你需要使用MySQL的主从复制功能。
- 修改镜像: 使用支持主从复制的MySQL镜像,或者自己构建一个。
- 初始化脚本: 编写一个初始化脚本,用于配置主从复制。
- 修改StatefulSet: 修改StatefulSet的YAML文件,添加初始化脚本。
这是一个更复杂的例子,包含了主从复制的配置:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
serviceName: mysql
replicas: 3
template:
metadata:
labels:
app: mysql
spec:
terminationGracePeriodSeconds: 10
initContainers:
- name: init-mysql
image: mysql:8.0
command:
- bash
- "-c"
- |
set -ex
# 生成一个随机的server-id,避免冲突
SERVER_ID=$((RANDOM % 10000 + 10000))
echo "server-id = $SERVER_ID" > /mnt/conf.d/server-id.cnf
# 如果是第一个Pod,则设置为主节点
if [ "$(hostname)" = "mysql-0" ]; then
echo "skip-slave-start" > /mnt/conf.d/master.cnf
else
echo "log-bin" > /mnt/conf.d/slave.cnf
echo "log-slave-updates" >> /mnt/conf.d/slave.cnf
fi
volumeMounts:
- name: mysql-data
mountPath: /mnt/conf.d
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "your_root_password"
- name: MYSQL_REPLICATION_USER
value: "repl"
- name: MYSQL_REPLICATION_PASSWORD
value: "your_replication_password"
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
- name: config-volume
mountPath: /etc/mysql/conf.d
volumes:
- name: config-volume
emptyDir: {}
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi
storageClassName: my-storage-class
代码解读:
initContainers
: 初始化容器,会在主容器启动之前运行。name: init-mysql
: 初始化容器的名称。image: mysql:8.0
: 使用的MySQL镜像。command
: 执行的命令。SERVER_ID
: 生成一个随机的server-id
,避免冲突。if [ "$(hostname)" = "mysql-0" ]
: 判断是否是第一个Pod(主节点)。skip-slave-start
: 如果为主节点,则跳过从节点的启动。log-bin
: 如果为从节点,则启用二进制日志。log-slave-updates
: 如果为从节点,则记录从节点更新的日志。
volumeMounts
: 挂载存储卷。/mnt/conf.d
: 将存储卷挂载到初始化容器内的/mnt/conf.d
目录,用于存放MySQL的配置文件。
volumes
: 定义共享卷,用于在初始化容器和主容器之间共享数据。name: config-volume
: 共享卷的名称。emptyDir: {}
: 创建一个空的目录,用于存放MySQL的配置文件。
volumeMounts
: 主容器的存储卷挂载。/etc/mysql/conf.d
: 将共享卷挂载到主容器内的/etc/mysql/conf.d
目录,用于读取MySQL的配置文件。
MYSQL_REPLICATION_USER
,MYSQL_REPLICATION_PASSWORD
: 用于主从复制的用户和密码,需要在MySQL服务器上创建。
接下来,你需要手动配置主从复制关系。
-
进入主节点(mysql-0)的Pod:
kubectl exec -it mysql-0 -- bash
-
登录MySQL:
mysql -u root -p
-
创建复制用户:
CREATE USER 'repl'@'%' IDENTIFIED BY 'your_replication_password'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%'; FLUSH PRIVILEGES; SHOW MASTER STATUS;
记录下
File
和Position
的值,稍后会用到。 -
进入从节点(mysql-1 或 mysql-2)的Pod:
kubectl exec -it mysql-1 -- bash
-
登录MySQL:
mysql -u root -p
-
配置从节点:
CHANGE MASTER TO MASTER_HOST='mysql-0.mysql.default.svc.cluster.local', MASTER_USER='repl', MASTER_PASSWORD='your_replication_password', MASTER_LOG_FILE='记录下来的File值', MASTER_LOG_POS=记录下来的Position值; START SLAVE; SHOW SLAVE STATUSG
查看
Slave_IO_Running
和Slave_SQL_Running
是否为Yes
。
重复步骤4-6,配置其他的从节点。
八、 总结
今天我们一起学习了如何在K8s中使用StatefulSet部署MySQL,并且实现了持久化存储和高可用部署。
- StatefulSet是管理有状态应用的最佳选择。
- Headless Service是StatefulSet的必备组件。
- 主从复制是实现MySQL高可用的常用方法。
当然,这只是一个简单的例子,实际部署中还需要考虑更多的因素,比如:
- 数据备份与恢复
- 监控与告警
- 性能优化
希望今天的讲座对大家有所帮助。下次再见!