Kubernetes PV 回收策略Retain 之后的手动清理流程一、Retain 不是不管了是你来管Kubernetes PersistentVolume 的回收策略有三个选项Delete删除 PV 和底层存储、Recycle已废弃执行 rm -rf 后重新可用、Retain保留 PV 和底层存储等待人工处理。生产环境中有状态应用数据库、消息队列几乎一律使用 Retain原因很直接——数据不能被自动删除。但 Retain 策略带来的问题也不直接PV 在 PVC 删除后进入 Released 状态底层存储数据还在PV 无法被新 PVC 绑定因为 PV 的 claimRef 还指向已删除的 PVC。这意味着这块存储资源被冻结了——不可用、也没人管。运维团队的典型做法是手动清理 PV 的 claimRef让 PV 回到 Available 状态再由新 PVC 绑定。但手动清理四个字背后是一套流程确认底层存储数据是否需要保留、是否需要迁移、是否需要擦除、清理 claimRef 后是否修改 PV 的访问模式或容量。每一步都需要判断和操作没有标准流程就容易出错——要么误删还在使用的数据要么忘记清理导致存储资源长期浪费。一个中型 Kubernetes 集群500 个 PV使用 Retain 策略的 PV 占 70%。PVC 因应用重建、Namespace 删除等原因被释放后Released 状态的 PV 逐月累积。三个月后Released PV 占总 PV 的 15%75 个底层存储占用约 2TB但这些 PV 既不可用也不可回收处于管理真空状态。二、Retain 策略下 PV 的生命周期与冻结点PV 在 Retain 策略下的完整生命周期有一个关键冻结点。graph TD A[PV 创建: Available] -- B[PVC 绑定: Bound] B -- C[应用使用存储数据] C -- D{PVC 被删除} D -- E[PV 进入 Released 状态] E -- F[底层存储数据保留] E -- G[claimRef 指向已删除 PVC] G -- H[PV 不可被新 PVC 绑定] H -- I{手动决策点} I -- J[数据需要保留: 迁移到新 PVC] I -- K[数据需要擦除: 清理底层存储] I -- L[数据不再需要: 删除 PV] J -- M[清除 claimRef] M -- N[PV 回到 Available] N -- O[新 PVC 绑定使用] K -- P[清除 claimRef] P -- Q[擦除底层存储数据] Q -- N L -- R[kubectl delete pv] R -- S[底层存储资源释放] style H fill:#f99,stroke:#333 style I fill:#f9f,stroke:#333 style N fill:#9f9,stroke:#333冻结点在步骤 HPV 进入 Released 状态后claimRef 还指向已删除的 PVCKubernetes 的 PV 控制器不会自动清除这个引用。PV 控制器的逻辑是Retain 策略下PV 保留给管理员处理控制器不做任何自动操作。这意味着 Released PV 的 claimRef 必须手动清除否则 PV 永远停在 Released 状态。手动决策点 I 是整个流程的核心。管理员面对 Released PV 时必须做三个判断底层存储数据是否还有用是否迁移或擦除、PV 是否还需要保留是否直接删除 PV 释放底层存储、PV 清理后是否需要修改规格容量、访问模式是否调整。这三个判断没有自动化工具能替代——数据价值是业务层面的判断不是运维能决定的。三、Released PV 的手动清理流程与自动化脚本手动清理 Released PV 的标准流程如下配合半自动化脚本减少操作风险。# pv_retain_cleanup.sh — Released PV 清理脚本 #!/bin/bash NAMESPACE${1:?用法: $0 namespace [pv-name]} PV_NAME${2:-} echo PV Retain 清理流程 echo Namespace: ${NAMESPACE} # 1. 列出所有 Released 状态的 PV echo [1] Released PV 列表: released_pvs$(kubectl get pv --no-headers | awk $5Released {print $1}) if [ -z $released_pvs ]; then echo 没有 Released 状态的 PV exit 0 fi echo $released_pvs echo # 2. 对每个 Released PV 展示详细信息 for pv in $released_pvs; do if [ -n $PV_NAME ] [ $pv ! $PV_NAME ]; then continue fi echo --- PV: ${pv} --- # 底层存储信息 storage_type$(kubectl get pv $pv -o jsonpath{.spec.storageClassName}) capacity$(kubectl get pv $pv -o jsonpath{.spec.capacity.storage}) access_modes$(kubectl get pv $pv -o jsonpath{.spec.accessModes[*]}) claim_ref$(kubectl get pv $pv -o jsonpath{.spec.claimRef.name}) claim_ns$(kubectl get pv $pv -o jsonpath{.spec.claimRef.namespace}) echo StorageClass: ${storage_type} echo 容量: ${capacity} echo 访问模式: ${access_modes} echo claimRef: ${claim_ns}/${claim_ref} (PVC 已删除) echo 底层存储后端: $(kubectl get pv $pv -o jsonpath{.spec.nfs.server}{.spec.nfs.path}{.spec.hostPath.path}{.spec.csi.driver}) # 检查 PVC 是否真的已不存在 pvc_exists$(kubectl get pvc $claim_ref -n $claim_ns --ignore-not-found 2/dev/null) if [ -n $pvc_exists ]; then echo ⚠ PVC ${claim_ns}/${claim_ref} 仍然存在不应清理此 PV continue fi echo echo 清理选项: echo 1) 清除 claimRef → PV Available保留数据 echo 2) 清除 claimRef 擦除数据 → PV Available数据清空 echo 3) 删除 PV → 底层存储释放 echo read -p 选择操作 [1/2/3/skip]: choice case $choice in 1) # 清除 claimRefPV 回到 Available kubectl patch pv $pv -p {spec:{claimRef:null}} echo ✓ PV ${pv} claimRef 已清除状态变为 Available ;; 2) # 清除 claimRef 擦除数据 kubectl patch pv $pv -p {spec:{claimRef:null}} echo ✓ PV ${pv} claimRef 已清除 # 根据存储类型执行数据擦除 # NFS 类型在 NFS 服务器上 rm -rf nfs_path$(kubectl get pv $pv -o jsonpath{.spec.nfs.path}) if [ -n $nfs_path ]; then echo 擦除 NFS 数据: ${nfs_path} ssh nfs-server rm -rf ${nfs_path}/* echo ✓ NFS 数据已擦除 fi # 本地存储类型在对应节点上 rm -rf local_path$(kubectl get pv $pv -o jsonpath{.spec.hostPath.path}) if [ -n $local_path ]; then node_name$(kubectl get pv $pv -o jsonpath{.spec.nodeAffinity.required.nodeSelectorTerms[0].matchExpressions[0].values[0]}) echo 擦除本地数据: 节点 ${node_name} 路径 ${local_path} # 实际操作需通过节点 SSH 或 DaemonSet 执行 echo 提示: 需在节点 ${node_name} 上执行 rm -rf ${local_path}/* fi ;; 3) # 删除 PV底层存储由 CSI 驱动处理 kubectl delete pv $pv echo ✓ PV ${pv} 已删除底层存储由 Retain 策略保留 echo 提示: 底层存储资源需手动在存储后端释放 ;; skip) echo 跳过 PV ${pv} ;; *) echo 无效选择 ;; esac echo done echo 清理流程完成 批量巡检脚本定期发现 Released PV 并发出告警# pv_released_audit.sh — Released PV 定期巡检 #!/bin/bash # 统计 Released PV 数量和存储占用 released_count$(kubectl get pv --no-headers | awk $5Released | wc -l) total_capacity$(kubectl get pv --no-headers | awk $5Released | \ kubectl get pv -o json | python3 -c import sys, json pvs json.load(sys.stdin)[items] total 0 for pv in pvs: if pv[status][phase] Released: cap pv[spec][capacity][storage] if cap.endswith(Gi): total int(cap[:-2]) elif cap.endswith(Ti): total int(cap[:-2]) * 1024 print(f{total}Gi) ) echo Released PV 数量: ${released_count} echo Released PV 存储占用: ${total_capacity} # 告警阈值 if [ $released_count -gt 10 ]; then echo ⚠ 告警: Released PV 数量超过 10建议执行清理流程 fi # 列出 Released 超过 7 天的 PV长期冻结 kubectl get pv -o json | python3 -c import sys, json, datetime pvs json.load(sys.stdin)[items] now datetime.datetime.now(datetime.timezone.utc) for pv in pvs: if pv[status][phase] Released: # Released 时间从 lastTransitionTime 或 deletionTimestamp 推算 name pv[metadata][name] cap pv[spec][capacity][storage] print(f{name} ({cap})) PV 清理的 Kubernetes 原生操作——清除 claimRef 让 PV 回到 Available# pv-claimref-cleanup.yaml — 批量清除 Released PV 的 claimRef # 注意仅清除 claimRef不涉及底层存储数据操作 # 适用场景数据需要保留PV 需要被新 PVC 重新绑定 # 单个 PV 清理示例 apiVersion: v1 kind: PersistentVolume metadata: name:>
Kubernetes PV 回收策略:Retain 之后的手动清理流程
发布时间:2026/7/9 9:42:20
Kubernetes PV 回收策略Retain 之后的手动清理流程一、Retain 不是不管了是你来管Kubernetes PersistentVolume 的回收策略有三个选项Delete删除 PV 和底层存储、Recycle已废弃执行 rm -rf 后重新可用、Retain保留 PV 和底层存储等待人工处理。生产环境中有状态应用数据库、消息队列几乎一律使用 Retain原因很直接——数据不能被自动删除。但 Retain 策略带来的问题也不直接PV 在 PVC 删除后进入 Released 状态底层存储数据还在PV 无法被新 PVC 绑定因为 PV 的 claimRef 还指向已删除的 PVC。这意味着这块存储资源被冻结了——不可用、也没人管。运维团队的典型做法是手动清理 PV 的 claimRef让 PV 回到 Available 状态再由新 PVC 绑定。但手动清理四个字背后是一套流程确认底层存储数据是否需要保留、是否需要迁移、是否需要擦除、清理 claimRef 后是否修改 PV 的访问模式或容量。每一步都需要判断和操作没有标准流程就容易出错——要么误删还在使用的数据要么忘记清理导致存储资源长期浪费。一个中型 Kubernetes 集群500 个 PV使用 Retain 策略的 PV 占 70%。PVC 因应用重建、Namespace 删除等原因被释放后Released 状态的 PV 逐月累积。三个月后Released PV 占总 PV 的 15%75 个底层存储占用约 2TB但这些 PV 既不可用也不可回收处于管理真空状态。二、Retain 策略下 PV 的生命周期与冻结点PV 在 Retain 策略下的完整生命周期有一个关键冻结点。graph TD A[PV 创建: Available] -- B[PVC 绑定: Bound] B -- C[应用使用存储数据] C -- D{PVC 被删除} D -- E[PV 进入 Released 状态] E -- F[底层存储数据保留] E -- G[claimRef 指向已删除 PVC] G -- H[PV 不可被新 PVC 绑定] H -- I{手动决策点} I -- J[数据需要保留: 迁移到新 PVC] I -- K[数据需要擦除: 清理底层存储] I -- L[数据不再需要: 删除 PV] J -- M[清除 claimRef] M -- N[PV 回到 Available] N -- O[新 PVC 绑定使用] K -- P[清除 claimRef] P -- Q[擦除底层存储数据] Q -- N L -- R[kubectl delete pv] R -- S[底层存储资源释放] style H fill:#f99,stroke:#333 style I fill:#f9f,stroke:#333 style N fill:#9f9,stroke:#333冻结点在步骤 HPV 进入 Released 状态后claimRef 还指向已删除的 PVCKubernetes 的 PV 控制器不会自动清除这个引用。PV 控制器的逻辑是Retain 策略下PV 保留给管理员处理控制器不做任何自动操作。这意味着 Released PV 的 claimRef 必须手动清除否则 PV 永远停在 Released 状态。手动决策点 I 是整个流程的核心。管理员面对 Released PV 时必须做三个判断底层存储数据是否还有用是否迁移或擦除、PV 是否还需要保留是否直接删除 PV 释放底层存储、PV 清理后是否需要修改规格容量、访问模式是否调整。这三个判断没有自动化工具能替代——数据价值是业务层面的判断不是运维能决定的。三、Released PV 的手动清理流程与自动化脚本手动清理 Released PV 的标准流程如下配合半自动化脚本减少操作风险。# pv_retain_cleanup.sh — Released PV 清理脚本 #!/bin/bash NAMESPACE${1:?用法: $0 namespace [pv-name]} PV_NAME${2:-} echo PV Retain 清理流程 echo Namespace: ${NAMESPACE} # 1. 列出所有 Released 状态的 PV echo [1] Released PV 列表: released_pvs$(kubectl get pv --no-headers | awk $5Released {print $1}) if [ -z $released_pvs ]; then echo 没有 Released 状态的 PV exit 0 fi echo $released_pvs echo # 2. 对每个 Released PV 展示详细信息 for pv in $released_pvs; do if [ -n $PV_NAME ] [ $pv ! $PV_NAME ]; then continue fi echo --- PV: ${pv} --- # 底层存储信息 storage_type$(kubectl get pv $pv -o jsonpath{.spec.storageClassName}) capacity$(kubectl get pv $pv -o jsonpath{.spec.capacity.storage}) access_modes$(kubectl get pv $pv -o jsonpath{.spec.accessModes[*]}) claim_ref$(kubectl get pv $pv -o jsonpath{.spec.claimRef.name}) claim_ns$(kubectl get pv $pv -o jsonpath{.spec.claimRef.namespace}) echo StorageClass: ${storage_type} echo 容量: ${capacity} echo 访问模式: ${access_modes} echo claimRef: ${claim_ns}/${claim_ref} (PVC 已删除) echo 底层存储后端: $(kubectl get pv $pv -o jsonpath{.spec.nfs.server}{.spec.nfs.path}{.spec.hostPath.path}{.spec.csi.driver}) # 检查 PVC 是否真的已不存在 pvc_exists$(kubectl get pvc $claim_ref -n $claim_ns --ignore-not-found 2/dev/null) if [ -n $pvc_exists ]; then echo ⚠ PVC ${claim_ns}/${claim_ref} 仍然存在不应清理此 PV continue fi echo echo 清理选项: echo 1) 清除 claimRef → PV Available保留数据 echo 2) 清除 claimRef 擦除数据 → PV Available数据清空 echo 3) 删除 PV → 底层存储释放 echo read -p 选择操作 [1/2/3/skip]: choice case $choice in 1) # 清除 claimRefPV 回到 Available kubectl patch pv $pv -p {spec:{claimRef:null}} echo ✓ PV ${pv} claimRef 已清除状态变为 Available ;; 2) # 清除 claimRef 擦除数据 kubectl patch pv $pv -p {spec:{claimRef:null}} echo ✓ PV ${pv} claimRef 已清除 # 根据存储类型执行数据擦除 # NFS 类型在 NFS 服务器上 rm -rf nfs_path$(kubectl get pv $pv -o jsonpath{.spec.nfs.path}) if [ -n $nfs_path ]; then echo 擦除 NFS 数据: ${nfs_path} ssh nfs-server rm -rf ${nfs_path}/* echo ✓ NFS 数据已擦除 fi # 本地存储类型在对应节点上 rm -rf local_path$(kubectl get pv $pv -o jsonpath{.spec.hostPath.path}) if [ -n $local_path ]; then node_name$(kubectl get pv $pv -o jsonpath{.spec.nodeAffinity.required.nodeSelectorTerms[0].matchExpressions[0].values[0]}) echo 擦除本地数据: 节点 ${node_name} 路径 ${local_path} # 实际操作需通过节点 SSH 或 DaemonSet 执行 echo 提示: 需在节点 ${node_name} 上执行 rm -rf ${local_path}/* fi ;; 3) # 删除 PV底层存储由 CSI 驱动处理 kubectl delete pv $pv echo ✓ PV ${pv} 已删除底层存储由 Retain 策略保留 echo 提示: 底层存储资源需手动在存储后端释放 ;; skip) echo 跳过 PV ${pv} ;; *) echo 无效选择 ;; esac echo done echo 清理流程完成 批量巡检脚本定期发现 Released PV 并发出告警# pv_released_audit.sh — Released PV 定期巡检 #!/bin/bash # 统计 Released PV 数量和存储占用 released_count$(kubectl get pv --no-headers | awk $5Released | wc -l) total_capacity$(kubectl get pv --no-headers | awk $5Released | \ kubectl get pv -o json | python3 -c import sys, json pvs json.load(sys.stdin)[items] total 0 for pv in pvs: if pv[status][phase] Released: cap pv[spec][capacity][storage] if cap.endswith(Gi): total int(cap[:-2]) elif cap.endswith(Ti): total int(cap[:-2]) * 1024 print(f{total}Gi) ) echo Released PV 数量: ${released_count} echo Released PV 存储占用: ${total_capacity} # 告警阈值 if [ $released_count -gt 10 ]; then echo ⚠ 告警: Released PV 数量超过 10建议执行清理流程 fi # 列出 Released 超过 7 天的 PV长期冻结 kubectl get pv -o json | python3 -c import sys, json, datetime pvs json.load(sys.stdin)[items] now datetime.datetime.now(datetime.timezone.utc) for pv in pvs: if pv[status][phase] Released: # Released 时间从 lastTransitionTime 或 deletionTimestamp 推算 name pv[metadata][name] cap pv[spec][capacity][storage] print(f{name} ({cap})) PV 清理的 Kubernetes 原生操作——清除 claimRef 让 PV 回到 Available# pv-claimref-cleanup.yaml — 批量清除 Released PV 的 claimRef # 注意仅清除 claimRef不涉及底层存储数据操作 # 适用场景数据需要保留PV 需要被新 PVC 重新绑定 # 单个 PV 清理示例 apiVersion: v1 kind: PersistentVolume metadata: name:>