Kubernetes StatefulSet 扩缩容踩坑记录:存储卷管理的血泪教训
背景
最近在阿里云 K8S 集群上对一个有状态服务(StatefulSet)进行扩容时遭遇了存储卷挂载失败的问题。经过一番排查,发现了 StatefulSet 扩缩容中两个容易被忽视但非常关键的问题。
问题现象
扩容操作:将 StatefulSet 从 2 个副本扩容到 8 个副本
失败症状:
新创建的 Pod 长时间处于
Init:0/2
状态部分 Pod 被调度到不存在的节点上
存储卷挂载超时:
timed out waiting for the condition
环境信息:
阿里云 ACK 集群
StatefulSet 配置了 1000Gi 持久存储
存储类:
idc-receiver-web-20220328
,策略:Retain + WaitForFirstConsumer
排查过程
1. 初步诊断
# 检查 Pod 状态
kubectl get pods -n cx-idc | grep receiver-receiver-web-statefulset
# 结果显示
receiver-receiver-web-statefulset-0 2/2 Running 0 19d
receiver-receiver-web-statefulset-1 2/2 Running 0 19d
receiver-receiver-web-statefulset-2 2/2 Terminating 0 33m # 卡住删除
receiver-receiver-web-statefulset-5 0/2 Init:0/2 0 32m # 初始化失败
2. 存储状态检查
# 检查 PVC 状态 - 正常
kubectl get pvc -n cx-idc | grep large-format-log
large-format-log-receiver-receiver-web-statefulset-0 Bound d-wz97b0sh6u1a3i00coh0 1000Gi
large-format-log-receiver-receiver-web-statefulset-1 Bound d-wz9a4xdd6p7b0nbyvwmd 1000Gi
...
# 检查 PV 状态 - 发现异常!
kubectl get pv | grep idc-receiver-web
d-wz9gme1m29dd3jkf0g2t 1000Gi RWO Retain Available # 未绑定状态
d-wz9gme1m29dd3jkf0g2x 1000Gi RWO Retain Available # 未绑定状态
3. 节点调度问题
# 检查 Pod 被调度到的节点
kubectl describe pod receiver-receiver-web-statefulset-2 -n cx-idc
# 发现调度到了不存在的节点
Node: cn-shenzhen.172.16.19.55
# 验证节点是否存在
kubectl get node cn-shenzhen.172.16.19.55
Error from server (NotFound): nodes "cn-shenzhen.172.16.19.55" not found
根本原因分析
经过深入排查和同事的经验总结,发现了两个核心问题:
问题 1:PV 复用导致的状态冲突
问题机制:
缩容时:Pod 删除 → PVC 保留(Retain 策略)→ PV 变为 Available 状态
扩容时:新 Pod 创建 → 复用存在的 Available PV → 数据冲突/状态异常
危害:
PV 中可能包含旧的应用数据和状态信息
新 Pod 挂载后出现数据不一致问题
导致初始化容器或应用启动失败
问题 2:新节点存储资源不足
问题背景:
扩容时新增的 ECS 节点默认只配置系统盘
StatefulSet 需要 1000Gi 大容量数据盘
CSI 驱动需要足够的存储配额才能自动创建云盘
影响:
存储类无法在新节点创建所需容量的云盘
Pod 调度成功但存储卷挂载失败
导致整个扩容流程阻塞
解决方案
立即修复
# 1. 强制删除卡住的 Pod
kubectl delete pod receiver-receiver-web-statefulset-2 -n cx-idc --force --grace-period=0
# 2. 清理 Available 状态的 PV
kubectl get pv | grep Available | grep idc-receiver-web
kubectl delete pv d-wz9gme1m29dd3jkf0g2t d-wz9gme1m29dd3jkf0g2x
# 3. 重新启动扩容流程
kubectl scale statefulset receiver-receiver-web-statefulset --replicas=8 -n cx-idc
预防措施
规范化缩容流程
# 1. 缩容 StatefulSet
kubectl scale statefulset my-app --replicas=2 -n namespace
# 2. 等待 Pod 完全删除
kubectl get pods -n namespace | grep my-app
# 3. 删除不需要的 PVC
kubectl delete pvc large-format-log-my-app-2 large-format-log-my-app-3 -n namespace
# 4. 清理对应的 PV(Retain 策略下需要手动删除)
kubectl get pv | grep Available
kubectl delete pv <pv-names>
扩容前置检查
# 1. 检查是否有历史遗留的 Available PV
kubectl get pv | grep Available | grep <storage-class>
# 2. 验证目标节点存储资源
kubectl describe node <node-name> | grep "Allocated resources" -A 10
# 3. 确认存储类在目标可用区的配额
# (需要通过阿里云控制台检查)
存储类配置优化
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: optimized-storage-class
provisioner: diskplugin.csi.alibabacloud.com
parameters:
type: cloud_essd
performanceLevel: PL1
reclaimPolicy: Delete # 改为 Delete 策略,自动清理
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
经验总结
核心原则
缩容后 PV 要及时清理:避免下次扩容时复用导致状态冲突
新节点存储资源要预先准备:大容量 PV 需要确保存储配额充足
监控存储类配额和可用区分布:避免调度到资源不足的区域
最佳实践
存储策略选择:
开发/测试环境:使用
Delete
策略,自动清理 PV生产环境:使用
Retain
策略,但建立完善的 PV 生命周期管理
扩缩容 SOP:
扩容前检查存储资源和历史 PV
缩容后及时清理不需要的 PVC/PV
建立 PV 使用情况的监控和告警
节点资源规划:
新增节点时预估存储需求
考虑单节点磁盘挂载数量限制(阿里云 ECS 通常为 16 个)
合理分布 Pod 避免存储热点
成本影响
这次踩坑也让我们意识到存储成本问题:
成本计算:
8 个副本 × 1000Gi = 8TB 存储
按阿里云高效云盘价格约 0.35 元/GB/月
月成本:8000GB × 0.35 元 = 2800 元/月
优化建议:
评估实际存储需求,避免过度分配
选择合适的云盘类型(高效云盘 vs ESSD)
建立存储使用率监控,及时清理无用 PV
结语
StatefulSet 的存储管理比想象中复杂,特别是在云环境中涉及自动化存储分配时。这次踩坑让团队深刻理解了以下几点:
状态管理的复杂性:有状态服务不仅仅是数据持久化,还涉及状态一致性
云资源的限制性:自动化不等于无限制,需要考虑配额和物理约束
运维流程的重要性:缺乏规范的扩缩容流程会带来隐性问题
希望这次总结能帮助其他同学避免类似的坑。记住:缩容清 PV,扩容备资源 🎯
参考命令速查:
# 检查 StatefulSet 状态
kubectl get statefulset -n namespace -w
# 检查 PV/PVC 状态
kubectl get pv,pvc -n namespace
# 清理 Available PV
kubectl get pv | grep Available | awk '{print $1}' | xargs kubectl delete pv
# 强制删除卡住的 Pod
kubectl delete pod <pod-name> -n namespace --force --grace-period=0