删除 PVC 后 PV 数据保护与复用避坑

一句话点透原理

PVC 被删除后,PV 的最终命运由 persistentVolumeReclaimPolicy(回收策略)决定。它有三种值:Delete(删除,包括底层存储)、Retain(保留,PV 标记为 Released,数据继续保留)和已废弃的 Recycle(清空数据)。

通过 StorageClass 动态供给出来的 PV,默认回收策略通常是 Delete,而不是 Retain。因此在动态供给的集群中,删除 PVC 可能等价于删除底层磁盘;重要数据必须显式使用 Retain,并在删除前完成备份或快照。

三个常见陷阱

陷阱 1:动态供给默认 Delete,删 PVC 可能删盘

没有显式指定回收策略时,StorageClass 往往使用 Delete。PVC 被删除后,Provisioner 收到 PV 释放事件,可能调用云厂商 API 销毁底层云盘。

删除前先检查 StorageClass 和具体 PV 的策略:

kubectl get storageclass
kubectl get sc <sc-name> -o jsonpath='{.reclaimPolicy}'

# 以 PV 自身的策略为准
kubectl get pv <pv-name> -o jsonpath='{.spec.persistentVolumeReclaimPolicy}'

重要数据建议使用回收策略为 Retain 的 StorageClass;如果没有合适的 StorageClass,可以手动创建 PV,并设置 persistentVolumeReclaimPolicy: Retain。对于云盘,也可以在 StorageClass 中固定 reclaimPolicy: Retain,从供给端保护新创建的盘。

早期手动创建 PV 时,人们常在 YAML 中写 Retain,容易因此形成“默认就是 Retain”的误解。静态 PV 与动态供给 PV 的默认行为并不应混为一谈。

陷阱 2:修改 StorageClass 不会回溯已有 PV

StorageClass 是供给模板,PV 是已经创建的实例。修改 StorageClass 的策略只影响后续新建的 PV,已经存在的 PV 仍以自身的 spec.persistentVolumeReclaimPolicy 为准。

需要保护已有数据盘时,必须修改具体 PV:

kubectl patch pv <pv-name> \
  -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'

这与修改 Deployment 模板不会改写已经创建的旧 ReplicaSet 类似。新集群可以从源头准备一个专用的 retain-sc 给有状态服务使用,存量 PV 则需要逐个检查和修改。

陷阱 3:PV 卡在 Released 后不能直接复用

在 Retain 策略下,PVC 删除后 PV 会变成 Released,数据仍在,但 spec.claimRef 中通常还保留着旧 PVC 的引用。新 PVC 因为 claimRef 不匹配,不能直接绑定这块 PV。

如果确认旧数据已经不需要或已经备份,可以清理 claimRef 后复用:

# 清除旧 PVC 引用,让 PV 可以重新绑定
kubectl patch pv <pv-name> --type json \
  -p '[{"op":"remove","path":"/spec/claimRef"}]'

# 复用前确保策略仍然是 Retain
kubectl patch pv <pv-name> \
  -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'

清除 claimRef 后,这块盘可能被新的 PVC 绑定。新 PVC 可能读取旧数据,也可能在后续操作中覆盖旧数据;生产环境不要把仍有价值的 Released 盘直接拿来凑合,优先创建新 PV 或从快照克隆数据。

生产环境注意事项

  1. 数据库和有状态服务(MySQL、Redis、Elasticsearch、MQ 等)优先使用 Retain,删除前执行快照或备份。
  2. 每次删除 PVC 前先用 kubectl get pv -o wide 找到关联 PV 并确认回收策略;看到 Delete 就先停手检查备份。
  3. 云厂商 CSI 的 Delete 通常会调用云 API 真正销毁云盘,多数情况下不会进入可恢复的回收站。
  4. StatefulSet 的 volumeClaimTemplates 创建的 PVC 同样继承 StorageClass 的策略;删除 StatefulSet 时要考虑 PVC 是否会被级联删除。
  5. 把 VolumeSnapshot 或云厂商快照作为最后一道安全网,删除前确认快照状态为 Ready。

StatefulSet 的额外风险

StatefulSet 的 volumeClaimTemplates 会为每个 Pod 创建独立 PVC,例如 data-<statefulset>-0、data-<statefulset>-1。这些 PVC 继承所用 StorageClass 的回收策略。

需要保留数据时,可以使用:

kubectl delete sts <name> --cascade=orphan

或者从源头使用 Retain 的 StorageClass。删除有状态服务前,必须明确 Pod、PVC、PV 与底层磁盘分别会发生什么。

安全的删 PVC / PV SOP

任何删除 PVC 或 PV 的操作,都按以下顺序执行:

  1. 用 kubectl get pv -o wide 找到目标 PV,记录 reclaimPolicy、claimRef 和底层存储标识。
  2. 如果策略是 Delete,先创建 VolumeSnapshot 或云厂商快照。
  3. 检查快照状态为 Ready,并确认恢复路径可用。
  4. 如果数据还要保留,先将具体 PV 改为 Retain,再删除 PVC。
  5. 删除后重新检查 PV 状态:Retain 通常变为 Released 且底层盘仍在;Delete 通常会清理 PV 和底层盘。
  6. 将检查步骤写进运维手册,并在 CI/CD 对 delete pvc 做策略校验;发现目标 PV 为 Delete 时直接阻断。

Retain 之后如何彻底清理

改成 Retain 后,删除 PVC 只会释放 PV,底层盘不会自动删除。需要彻底清理时:

  1. 先在云厂商控制台或 CLI 中确认并删除底层云盘,例如 aws ec2 delete-volume 或对应云厂商的删除接口。
  2. 再删除 Kubernetes 中的 PV 对象:
kubectl delete pv <pv-name>

先删除 PV 对象可能让云盘失去 Kubernetes 引用;Retain 的价值就是在清理前提供人工确认机会。

静态 PV 与动态供给的差异

静态 PV 通过 kubectl apply -f pv.yaml 创建,回收策略通常由 YAML 显式指定,常见配置是 Retain。动态供给 PV 则由 StorageClass 创建,常见默认策略是 Delete。

判断数据是否会被删除时,不要只看 PVC 名称,应同时确认:

  • PVC 使用的 storageClassName
  • 具体 PV 的 persistentVolumeReclaimPolicy
  • Provisioner 或 CSI 驱动的删除行为
  • 底层云盘、NFS 目录或分布式存储卷的实际生命周期

扩容与删盘是两类不同风险

启用 allowVolumeExpansion: true 的 StorageClass 支持通过修改 PVC 请求容量扩容:

kubectl patch pvc <pvc-name> -n <namespace> \
  -p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'

扩容通常只增加底层容量,不会删除原有数据,但仍需确认 StorageClass 和 CSI 驱动支持在线扩容。扩容失败时要分别检查后端卷容量与文件系统容量。

扩容与删盘的风险方向相反:扩容通常是增加容量,删除 PVC 则可能触发底层卷销毁,不能因为扩容安全就降低对删除操作的审查级别。

用 VolumeSnapshot 做安全网

云原生环境可以在删除 PVC 前创建快照:

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: mysql-data-snap
spec:
  volumeSnapshotClassName: csi-disk-snapclass
  source:
    persistentVolumeClaimName: mysql-data

删除 PVC 前确认快照已经 Ready。快照还可以用于克隆出新 PVC 做数据校验,避免直接在线操作生产盘。

误删云盘后的恢复

如果误删操作触发了云盘删除,先检查云厂商控制台是否存在自动快照或手动快照:

  • 有快照:从快照创建新盘,重新创建 PV 和 PVC,并让应用指向新的 PVC。
  • 没有快照:只能依靠业务层冷备份恢复,恢复时间可能以小时计。

恢复前需要确认 PV、PVC 与底层卷的绑定关系,避免只删除 Kubernetes 对象而遗留云盘,或新 PVC 绑定到包含旧业务数据的错误卷。

Released PV 监控与备份

大量 Released PV 表示“PVC 已删除但 PV 或底层存储仍待人工处理”。它们会持续占用存储配额,也增加误复用和数据串扰风险。可以使用以下指标统计并告警:

count(kube_persistentvolume_status_phase{phase="Released"})

存储备份可以采用“三二一”策略:三份副本、两种介质、一份异地。在 Kubernetes 中可以组合 VolumeSnapshot、业务层逻辑备份(如 mysqldump)和跨地域或跨账号的备份副本。

高频问题

PVC 一直 Pending 怎么办?

先检查 StorageClass 是否存在可用的 Provisioner,再检查 accessMode 是否匹配,最后检查 PVC selector 是否能匹配目标 PV。静态供给下,PV 的 capacity 和 accessModes 必须满足 PVC 请求。

已绑定的 PVC 能否直接更换 StorageClass?

不能。PVC 创建后其 storageClassName 基本固定,绑定的 PV 也不会因此更换。需要新建 PVC,通过快照或数据复制迁移数据,再切换应用引用。

本地 PV 或 hostPath 删除后数据还在吗?

本地静态 PV 通常是 Retain,删除 PVC 后节点磁盘上的数据可能仍在。但节点更换或重装会导致数据丢失,多节点调度时数据也不会跟随 Pod;生产有状态服务应优先使用云盘或分布式存储。

怎么确认某块 PV 被谁绑定?

kubectl get pv <pv-name> -o jsonpath='{.spec.claimRef}'
kubectl get pvc -n <namespace>

前者可以看到 PVC 名称和命名空间,后者可以反向查看 PVC 的 VolumeName。

删除 PVC 卡在 Terminating 怎么办?

PVC 删除可能受 metadata.finalizers 保护。先查看:

kubectl get pvc <pvc-name> -n <namespace> \
  -o jsonpath='{.metadata.finalizers}'

某些 CSI 驱动会使用 Finalizer 等待异步清理完成。应先等待控制器完成清理;只有在确认外部资源已安全处理、控制器确实不存在或异常时,才考虑手动移除 Finalizer。强制清理可能留下云盘或其他外部资源泄漏。

排查 Checklist

  • [ ] kubectl get sc 确认目标 StorageClass 的回收策略
  • [ ] kubectl get pv <pv-name> 确认具体 PV 的 persistentVolumeReclaimPolicy
  • [ ] 删除 PVC 前确认快照或备份可用,Delete 策略一律先备份
  • [ ] 记住修改 StorageClass 不会回溯修改存量 PV
  • [ ] 复用 Released PV 前确认旧数据可弃或已完成备份
  • [ ] StatefulSet 删除时检查 volumeClaimTemplates 产生的 PVC
  • [ ] 云盘 Delete 可能直接调用云 API 销毁底层卷

删除前最后确认

PVC 删除不可逆的开关是 reclaimPolicy:Delete 可能等于删盘,Retain 才会留下人工恢复机会。没有可验证的快照或备份时,不要删除重要 PVC。