在大型 Kubernetes 集群里,Pod 删除了,PVC 却通常会被保留下来。这种默认行为可以避免误删数据,但也容易让开发环境和临时任务积累大量无人使用的存储卷。Kubernetes v1.37 将 PersistentVolumeClaimUnusedSinceTime 特性提升到 Beta,并默认启用,让 PVC 自己告诉你:当前是否仍有非终止状态的 Pod 引用了它,以及它从什么时候开始闲置。
为什么 PVC 的闲置状态值得被原生记录
PVC 和 Pod 的生命周期并不绑定。用户删除 Deployment、Job 或临时调试 Pod 后,PVC 可能继续存在,背后的云盘也继续占用容量并产生费用。PVC protection controller 保留 PVC 的主要目的正是防止意外数据丢失,因此 Kubernetes 不会因为引用它的 Pod 消失,就自动删除 PVC。
过去,管理员想回答一个简单问题——某个 PVC 现在是否真的被使用——往往需要同时查询 Pod、PVC 和 PersistentVolume,再处理跨命名空间、历史 Pod 和终止状态等细节。集群规模一大,脚本和监控流水线很快就会变得复杂。
v1.37 的新能力把这个判断结果直接写入 PVC 的 status.conditions。只要启用了默认的 Beta 特性,每个 PVC 都会由 PVC protection controller 维护一个 Unused 条件。
Unused 条件到底表示什么
条件状态由当前 Pod 引用关系决定:
| 场景 | status |
reason |
|---|---|---|
| 没有任何非终止状态的 Pod 引用 PVC | True |
NoPodsUsingPVC |
| 至少有一个运行中或 Pending 的 Pod 引用 PVC | False |
PodUsingPVC |
这里有几个容易误判的边界:
- Succeeded 或 Failed 的 Pod 不计入使用中。 批处理任务结束后,不会永久阻止 PVC 进入
Unused=True。 - Pending Pod 也计入使用中。 即使 Pod 因节点选择器不可能匹配而一直无法调度,只要它表达了使用该 PVC 的意图,条件仍然是
Unused=False。 - 多个 Pod 共享一个 PVC 时,以最后一个非终止 Pod 为准。 只有所有仍在运行或 Pending 的引用者都消失后,PVC 才会转为闲置。
这意味着 Unused=True 代表当前没有非终止 Pod 引用 PVC,并不等价于可以无条件删除。PVC 可能包含重要数据、被外部系统读取,或者只是暂时等待下一次任务使用。
lastTransitionTime:从状态判断闲置时长
Unused 是标准 Kubernetes condition,因此包含 lastTransitionTime。当条件从 False 变为 True 时,该字段记录 PVC 进入闲置状态的时间。
这比只看 PVC 的创建时间更有用:一个 PVC 可能创建于数月前,但昨天才被最后一个 Pod 释放。清理策略应该关注它闲置了多久,而不是它存在了多久。
例如,可以把规则定义为:
- 闲置超过 7 天:进入候选列表,等待负责人确认;
- 闲置超过 30 天:提交工单或进入开发环境自动清理流程;
- 生产命名空间:只告警,不自动删除。
实际操作:创建、使用并观察 PVC
下面的示例假设集群版本为 v1.37 或更高,并且控制面组件正常运行。先创建一个 PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
保存为 pvc.yaml 后执行:
kubectl apply -f pvc.yaml
kubectl get pvc my-data -o jsonpath='{.status.conditions[?(@.type=="Unused")]}' | jq .
如果当前没有引用它的非终止状态 Pod,结果中应该能看到类似内容:
{
"lastTransitionTime": "2026-09-14T12:03:11Z",
"message": "No pods are currently referencing this PVC",
"reason": "NoPodsUsingPVC",
"status": "True",
"type": "Unused"
}
接着创建一个引用 PVC 的 Pod:
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: app
image: busybox
command: ["sleep", "3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: my-data
kubectl apply -f pod.yaml
kubectl get pvc my-data -o jsonpath='{.status.conditions[?(@.type=="Unused")].status}'
printf '\n'
此时输出应为:
False
删除 Pod 后,等待控制器更新状态,再检查条件:
kubectl delete pod my-app
kubectl wait --for=jsonpath='{.status.conditions[?(@.type=="Unused")].status}'=True pvc/my-data --timeout=60s
kubectl get pvc my-data -o jsonpath='{.status.conditions[?(@.type=="Unused")]}' | jq .
实际环境中,条件更新是异步的,因此删除 Pod 后应通过轮询或 kubectl wait 等待,而不是假设状态会立即变化。
集群范围筛选闲置超过 30 天的 PVC
下面的命令需要安装 jq。它会查询所有命名空间,筛选 Unused=True 且 lastTransitionTime 早于 30 天前的 PVC:
kubectl get pvc -A -o json | jq -r '
.items[]
| select(
.status.conditions[]?
| select(.type == "Unused" and .status == "True")
)
| select(
(.status.conditions[] | select(.type == "Unused") | .lastTransitionTime) as $t
| (now - ($t | fromdateiso8601)) > (30 * 86400)
)
| "\(.metadata.namespace)/\(.metadata.name) unused since \(.status.conditions[] | select(.type == "Unused") | .lastTransitionTime)"
'
输出可能类似:
team-a/cache-data unused since 2026-08-01T10:15:00Z
sandbox/test-volume unused since 2026-07-22T03:40:11Z
这个结果适合用作清理候选,而不是直接作为删除指令。自动化脚本还应加入命名空间白名单、PVC 标签、存储类和负责人审批等保护条件。
Beta 阶段的采用建议
从 Alpha 升级到 v1.37 Beta 后,特性门默认开启,并拥有完整的端到端测试覆盖。使用者通常不需要额外配置 feature gate,直接检查 PVC 的 status.conditions 即可。
不过,Unused=True 只是控制器根据 Pod 引用关系得出的运行时信号,存在几个边界:
- 它不判断 PVC 中的数据是否重要;
- 它不代表底层 PersistentVolume 已经被释放;
- 它不替代备份、保留策略和业务负责人确认;
- Pending Pod 会阻止 PVC 进入闲置状态,因此异常调度对象也需要纳入排查;
- 大规模集群中应避免频繁全量扫描 API,最好将结果导出到监控或定时任务中。
可以采用下面这份落地清单:
- 在非生产集群先验证
Unused条件的更新延迟和命名空间范围。 - 为 PVC 增加应用、环境和负责人标签,方便清理前确认归属。
- 用
lastTransitionTime建立 7 天、30 天等分级告警。 - 对生产 PVC 先通知和审批,不直接自动删除。
- 定期检查长期 Pending 的 Pod,避免错误引用让闲置 PVC 一直显示为使用中。
Kubernetes v1.37 把 PVC 使用情况从需要自行拼接的数据关系,变成了 PVC 状态中的标准条件。它不会替你决定哪些存储可以删除,但能显著降低发现孤立 PVC 的成本,让容量治理从定制脚本走向更一致的原生接口。