在Kubernetes(K8s)中部署无状态应用(如Web服务器)通常使用Deployment,但对于MySQL、Kafka或ZooKeeper这类有状态应用,它们需要稳定的网络标识和持久化存储。这时,我们就需要使用StatefulSet。
StatefulSet提供了以下关键特性:
1. 稳定的网络标识(DNS): 每个Pod都有一个基于序号的稳定主机名。
2. 有序的部署和扩展: Pod按照序号(0, 1, 2…)顺序创建和销毁。
3. 稳定的持久存储: 通过VolumeClaimTemplate,为每个Pod分配独立的PersistentVolume。
本文将以部署MySQL为例,详细讲解StatefulSet的配置和需要避开的陷阱。
步骤一:创建Headless Service
StatefulSet必须配合Headless Service(无头服务)使用。Headless Service不分配Cluster IP,而是返回所有关联Pod的IP地址,从而保证了每个StatefulSet Pod拥有一个稳定的、可解析的DNS名称(格式为:
apiVersion: v1
kind: Service
metadata:
name: mysql-headless-service
labels:
app: mysql
spec:
ports:
- port: 3306
name: mysql
clusterIP: None # 关键:设置为None即为Headless Service
selector:
app: mysql
步骤二:定义StatefulSet
下面是一个部署MySQL的StatefulSet示例,重点关注volumeClaimTemplates的使用。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: "mysql-headless-service"
replicas: 3 # 部署三个实例,例如用于主从复制
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: your_strong_password
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
# 确保容器入口点(Entrypoint)能处理初始化和集群配置
# 关键配置:持久卷声明模板
volumeClaimTemplates:
- metadata:
name: mysql-data # 必须匹配上面 volumeMounts 中的 name
spec:
accessModes:
- ReadWriteOnce # 通常用于单个Pod独占存储
storageClassName: standard-sc # 替换为您的存储类名称
resources:
requests:
storage: 10Gi # 请求10GB存储空间
常见陷阱及解决方案
使用StatefulSet部署有状态应用时,如果不理解其工作原理,可能会遇到以下几个“坑”:
陷阱一:删除StatefulSet不会删除PV/PVC
问题: 当你使用 kubectl delete statefulset mysql 命令删除StatefulSet后,其关联的PersistentVolumeClaim (PVC) 和 PersistentVolume (PV) 默认不会被删除。它们仍然保留在集群中,如果重新部署同名的StatefulSet,它会尝试重新绑定到旧的存储上。
风险: 存储泄露或重新部署时加载到旧数据。
解决方案: 删除StatefulSet后,必须手动删除相关的PVC和PV。
# 检查并删除PVC
kubectl get pvc | grep mysql-data-mysql
kubectl delete pvc mysql-data-mysql-0 mysql-data-mysql-1 mysql-data-mysql-2
# 检查PV(如果StorageClass的reclaimPolicy是Retain,则PV也需要手动删除)
陷阱二:存储回收策略(Reclaim Policy)
问题: 如果你使用的StorageClass的reclaimPolicy设置为Retain,即使你删除了PVC,底层的PV和数据仍会保留。如果设置为Delete,则删除PVC时,PV也会被自动删除。
解决方案: 在生产环境中,对于关键数据库,建议使用Retain策略以防止意外删除导致数据丢失,但这意味着管理负担增加。在测试环境,可以使用Delete。
陷阱三:依赖序号的初始化逻辑
对于MySQL集群(如主从复制或Galera集群),实例0通常是Primary/Master,而实例1、2…是Replica/Slave。
问题: 容器启动时,它不知道自己是StatefulSet中的哪一个序号,因此无法自动配置自己的角色。
解决方案: 在容器的Entrypoint脚本中,通过读取Pod名称来获取序号,并据此执行不同的初始化逻辑。
例如,在启动脚本中可以检查:
if hostname | grep "-0$"; then
# I am the master (mysql-0)
echo "Initializing Master Role"
# 执行主节点特有的初始化和配置
else
# I am a replica (mysql-1, mysql-2, ...)
echo "Initializing Replica Role"
# 配置连接到Master节点(使用mysql-0.mysql-headless-service 作为Master地址)
fi
陷阱四:启动顺序和健康检查
StatefulSet严格按照序号顺序启动(0 -> 1 -> 2)。如果在启动下一个Pod前,上一个Pod没有达到“就绪”状态,部署会停滞。
解决方案: 务必为MySQL容器配置准确的 readinessProbe 和 livenessProbe,确保只有当MySQL服务完全启动、数据库初始化完成且能够接受连接时,Pod才被标记为Ready。如果初始化时间较长,需要设置较高的 initialDelaySeconds。
汤不热吧