StatefulSet 稳定网络标识与持久存储
StatefulSet 的核心价值在于它能保持有状态应用的网络标识和存储的稳定性。但这背后是怎么实现的?
稳定网络标识的实现
命名规则
bash
# StatefulSet 创建的 Pod 名称格式
# <statefulset-name>-<ordinal>
# 例如:mysql StatefulSet
mysql-0 # 主节点
mysql-1 # 从节点
mysql-2 # 从节点
# Pod 序号从 0 开始,依次递增Pod Identity
每个 StatefulSet Pod 都有一个唯一的身份标识:
bash
# 查看 Pod 的 identity annotations
kubectl get pod mysql-0 -o jsonpath='{.metadata.annotations}' | jq .
# 输出:
# {
# "statefulset.kubernetes.io/pod-name": "mysql-0"
# }身份保持机制
┌─────────────────────────────────────────────────────────────────────┐
│ Pod 重建时的身份保持 │
│ │
│ Pod mysql-0 正在运行 │
│ ↓ │
│ Pod 被删除(或节点故障) │
│ ↓ │
│ StatefulSet 控制器创建新的 mysql-0 │
│ ↓ │
│ 新 Pod 绑定到原来的 PVC(data-mysql-0) │
│ ↓ │
│ 新 Pod 获得相同的名称和网络标识 │
│ ↓ │
│ 应用感知到的「服务器 ID」不变 │
│ │
└─────────────────────────────────────────────────────────────────────┘网络标识保持示例
yaml
# MySQL 主从配置示例
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql
replicas: 3
template:
spec:
initContainers:
- name: init-mysql
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
command:
- bash
- -c
- |
# 根据 Pod 名称设置 server-id
SERVER_ID=${POD_NAME##mysql-}
echo "server-id=$((SERVER_ID + 1))" >> /mnt/conf.cnfHeadless Service 的作用
为什么需要 Headless Service?
bash
# 普通 Service
kubectl get service mysql -o yaml
# spec.clusterIP: None # Headless
# 普通 Service:
# mysql.default.svc.cluster.local → 负载均衡到某个 Pod
# Headless Service:
# mysql-0.mysql.default.svc.cluster.local → 直接解析到 Pod IP
# mysql-1.mysql.default.svc.cluster.local → 直接解析到 Pod IPDNS 记录
bash
# 在 Pod 内测试 DNS 解析
kubectl exec -it mysql-0 -- nslookup mysql-0.mysql
# 输出:
# Server: 10.96.0.10
# Address: 10.96.0.10#53
# Name: mysql-0.mysql.default.svc.cluster.local
# Address: 10.244.1.15
kubectl exec -it mysql-0 -- nslookup mysql-1.mysql
# Name: mysql-1.mysql.default.svc.cluster.local
# Address: 10.244.2.18服务发现机制
┌─────────────────────────────────────────────────────────────────────┐
│ StatefulSet 服务发现 │
│ │
│ Headless Service + StatefulSet │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ CoreDNS │ │
│ │ │ │
│ │ mysql-0.mysql.default.svc.cluster.local → 10.244.1.15 │ │
│ │ mysql-1.mysql.default.svc.cluster.local → 10.244.2.18 │ │
│ │ mysql-2.mysql.default.svc.cluster.local → 10.244.3.20 │ │
│ │ │ │
│ │ mysql.default.svc.cluster.local → A 记录列表 │ │
│ │ → 10.244.1.15, 10.244.2.18, 10.244.3.20 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘PVC/PV 的生命周期
PVC 命名规则
bash
# VolumeClaimTemplate.metadata.name + "-" + StatefulSet.name + "-" + ordinal
# 例如:
# VolumeClaimTemplate: data
# StatefulSet: mysql
# ordinal: 0
# 结果:data-mysql-0PVC 生命周期
bash
# 查看所有 PVC
kubectl get pvc
# 输出:
# NAME STATUS VOLUME
# data-mysql-0 Bound pvc-xxxx
# data-mysql-1 Bound pvc-yyyy
# data-mysql-2 Bound pvc-zzzz
# 查看 PVC 详情
kubectl describe pvc data-mysql-0PVC 保留策略
bash
# StatefulSet 删除时,PVC 默认保留
kubectl delete statefulset mysql
# 查看 PVC(仍然存在)
kubectl get pvc | grep mysql
# 手动删除 PVC
kubectl delete pvc data-mysql-0
# 删除所有 PVC
kubectl delete pvc -l app=mysqlPV 的绑定
┌─────────────────────────────────────────────────────────────────────┐
│ PVC 绑定过程 │
│ │
│ 1. StatefulSet 创建 Pod mysql-0 │
│ ↓ │
│ 2. VolumeClaimTemplate 生成 PVC 申请:data-mysql-0 │
│ ↓ │
│ 3. StorageClass 响应,创建 PV │
│ ↓ │
│ 4. PV 绑定到 PVC │
│ ↓ │
│ 5. Pod 挂载 PVC │
│ │
└─────────────────────────────────────────────────────────────────────┘存储类与动态供给
StorageClass 配置
yaml
# 创建 StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-storage
provisioner: kubernetes.io/gce-pd # 根据环境选择
parameters:
type: pd-ssd
replication-type: regional-pd
reclaimPolicy: Retain # Retain / Delete
volumeBindingMode: WaitForFirstConsumer延迟绑定
yaml
# volumeBindingMode: WaitForFirstConsumer
# Pod 调度到节点后才绑定 PV
volumeClaimTemplates:
- metadata:
name: data
spec:
storageClassName: fast-storage
volumeBindingMode: WaitForFirstConsumer
resources:
requests:
storage: 50Gi立即绑定
yaml
# volumeBindingMode: Immediate
# StatefulSet 创建时立即绑定 PV
volumeClaimTemplates:
- metadata:
name: data
spec:
storageClassName: fast-storage
volumeBindingMode: Immediate
resources:
requests:
storage: 50Gi数据持久化实践
分离数据和配置
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
template:
spec:
containers:
- name: mysql
volumeMounts:
# 数据目录
- name: mysql-data
mountPath: /var/lib/mysql
# 配置文件
- name: mysql-config
mountPath: /etc/mysql/conf.d
# 日志目录
- name: mysql-logs
mountPath: /var/log/mysql
volumes:
- name: mysql-config
configMap:
name: mysql-config
- name: mysql-logs
emptyDir: {}
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: ssd-storage
resources:
requests:
storage: 100Gi本地存储 vs 网络存储
本地存储(高性能):
yaml
# 使用 Local PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv
spec:
capacity:
storage: 100Gi
storageClassName: local-storage
persistentVolumeReclaimPolicy: Retain
local:
path: /mnt/disks/ssd1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node-1网络存储(高可用):
yaml
# 使用云存储
volumeClaimTemplates:
- metadata:
name: data
spec:
storageClassName: cloud-storage
resources:
requests:
storage: 100Gi数据迁移
备份与恢复流程
┌─────────────────────────────────────────────────────────────────────┐
│ 数据备份与恢复流程 │
│ │
│ 备份流程: │
│ 1. Pod 缩容到 0(可选,取决于备份策略) │
│ ↓ │
│ 2. 快照 PVC │
│ ↓ │
│ 3. 从快照创建新 PVC │
│ ↓ │
│ 4. 恢复 Pod 副本数 │
│ │
│ 恢复流程: │
│ 1. 创建临时 StatefulSet │
│ ↓ │
│ 2. 挂载快照 PVC │
│ ↓ │
│ 3. 恢复数据 │
│ ↓ │
│ 4. 切换流量 │
│ ↓ │
│ 5. 清理临时资源 │
│ │
└─────────────────────────────────────────────────────────────────────┘备份脚本示例
yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: mysql-backup
spec:
schedule: "0 2 * * *" # 每天凌晨 2 点
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: mysql:8.0
command:
- bash
- -c
- |
# 备份命令
mysqldump -h mysql-0.mysql \
-u root -p$MYSQL_ROOT_PASSWORD \
--all-databases \
| gzip > /backup/backup-$(date +%Y%m%d).sql.gz
volumeMounts:
- name: backup
mountPath: /backup
volumes:
- name: backup
persistentVolumeClaim:
claimName: mysql-backup
restartPolicy: OnFailure常见问题排查
PVC 绑定失败
bash
# 检查 StorageClass
kubectl get storageclass
# 检查 PVC 事件
kubectl describe pvc data-mysql-0
# 查看 PVC 状态
kubectl get pvc data-mysql-0 -o yaml
# 常见原因:
# - StorageClass 不存在
# - 存储配额不足
# - 节点标签不匹配数据不一致
bash
# 主从复制问题
kubectl exec mysql-1 -- mysql -u root -p -e "SHOW SLAVE STATUS\G"
# 检查复制延迟
kubectl exec mysql-1 -- mysql -u root -p -e \
"SHOW SLAVE STATUS\G" | grep Seconds_Behind_Master存储空间不足
bash
# 查看 PVC 大小
kubectl get pvc -o jsonpath='{range.items[*]}{.metadata.name}{"\t"}{.status.capacity.storage}{"\n"}{end}'
# 查看实际使用
kubectl exec mysql-0 -- df -h /var/lib/mysql面试追问
- StatefulSet 的稳定网络标识是怎么实现的?Pod 名称和 DNS 记录有什么关系?
- Headless Service 在 StatefulSet 中扮演什么角色?它和普通 Service 有什么区别?
- 删除 StatefulSet 后,PVC 会自动删除吗?数据会丢失吗?
- 如何备份 StatefulSet 中的数据?有哪些备份策略?
- StatefulSet 的扩缩容顺序是什么?为什么需要有序扩缩容?
"StatefulSet 的稳定网络标识和持久存储是有状态应用的基础。理解 PVC/PV 的生命周期、Headless Service 的作用,以及如何正确备份和恢复数据,是在 K8s 上运行有状态应用的关键。"
