1. 从“删不掉”说起:为什么你的Kubernetes资源会卡住?

刚接触Kubernetes那会儿,我最头疼的不是部署应用,而是“删东西”。你肯定也遇到过:一个Pod明明已经kubectl delete了,但它就是倔强地显示着Terminating状态,几个小时都不消失;或者想清理一个测试用的Namespace,结果它一直卡在Terminating,里面的资源像被胶水粘住了一样,怎么也清不干净。更让人抓狂的是存储,一个声明删除的PVC(PersistentVolumeClaim)或者PV(PersistentVolume),状态变成了Released,然后就赖在那里不动了,占用着宝贵的存储空间。

这些“删不掉”的资源,我们通常称之为“异常资源”或“僵尸资源”。它们不仅占用着集群的计算、网络和存储资源,长期来看还可能引发资源耗尽、调度失败甚至集群稳定性问题。我经历过一次线上告警,查了半天才发现是一个早已下线的服务残留了几十个处于Terminating状态的Pod,把节点的文件描述符给耗尽了。

所以,高效的资源清理不是简单的“删除”,而是一门“外科手术”。它要求我们不仅能下删除命令,更要精准定位资源卡住的原因,然后对症下药,选择最合适的释放策略。盲目地使用--force强制删除,有时候能解决问题,但有时候可能会掩盖更深层次的问题,甚至导致数据丢失或状态不一致。

这篇文章,我就结合自己踩过的无数个坑,和你分享一套从诊断到清理的完整“手术方案”。我们会深入Pod、Namespace和存储资源(PV/PVC)这三种最常见也最棘手的场景,不仅告诉你“怎么删”,更会剖析“为什么删不掉”,以及如何安全、彻底地让它们从你的集群里消失。我们的目标很明确:让集群保持清爽、高效,把资源真正还给需要它的应用。

2. 精准诊断:找到资源“赖着不走”的根因

在动刀删除之前,我们必须先当好“医生”,进行精准诊断。Kubernetes的资源删除是一个由控制器(Controller)驱动的声明式过程,任何一个环节卡住,都会导致删除流程停滞。盲目操作就像蒙着眼睛拆炸弹,非常危险。

2.1 核心诊断命令与技巧

诊断的第一步,永远是查看资源的详细状态。kubectl describe 是你的最佳伙伴,它能提供远超 kubectl get 的丰富信息。

# 查看卡住的Pod的详细信息
kubectl describe pod <pod-name> -n <namespace>

# 查看卡住的Namespace的详细信息
kubectl describe namespace <stuck-namespace>

# 查看异常PV/PVC的详细信息
kubectl describe pv <pv-name>
kubectl describe pvc <pvc-name> -n <namespace>

describe 命令的输出中,你要特别关注以下几个部分:

  1. Events(事件):这是最重要的线索!它会按时间顺序记录该资源生命周期中的所有关键事件。比如,你可能会看到 Failed to kill podError cleaning volume 或者 Waiting for pods to terminate 这样的错误信息,直接指向了问题的根源。
  2. Status(状态):不仅仅是 Terminating,还要看具体的 Conditions(条件)。例如,一个Pod的 Ready 条件是否为 False,并附带了什么消息。
  3. Finalizers(终结器):这是很多“删不掉”问题的罪魁祸首。Finalizer 是一种保护机制,确保在删除资源前,某些清理操作(如从外部系统解绑存储)能够完成。如果负责执行清理操作的控制器挂了或者出了问题,Finalizer 就会一直阻塞删除流程。在 describe 输出的 Metadata 部分可以找到 finalizers 字段。
  4. Owner References(所有者引用):资源属于谁?一个Pod可能由Deployment管理,一个PVC可能被某个Pod使用。直接删除子资源有时会失败,因为它的父资源(Owner)会不断地尝试重建它。你需要先找到并处理它的上级资源。

2.2 常见卡住场景分析

根据我的经验,资源删除卡住无外乎下面几种情况,你可以像查字典一样对照:

资源类型常见卡住状态可能原因初步诊断方向
PodTerminating1. 容器终止流程阻塞preStop 钩子执行时间过长或卡死。
2. 存储卷卸载失败:关联的存储卷(尤其网络存储)无法从节点卸载。
3. 节点失联:Pod所在节点宕机或与控制平面网络中断。
describe pod 查看 Events,检查 preStop 配置,确认节点状态 (kubectl get node)。
NamespaceTerminating1. 残留资源有 Finalizers:Namespace内某些资源(如自定义资源CRD)的finalizer未移除。
2. API 服务扩展问题:某些APIService(如metrics-server)未正常响应删除请求。
kubectl get all -n <namespace> 查看所有资源,特别关注非标准资源。使用 kubectl api-resources 查看有哪些资源类型。
PVReleased / Failed1. 回收策略(Reclaim Policy)冲突:策略为 Retain 时,需要手动清理。
2. 存储后端问题:底层存储系统(如CEPH、NFS)故障,导致卷无法删除。
3. 仍被PVC引用:虽然PVC已删,但绑定关系未完全解除。
describe pv 查看 StatusClaim Ref。检查存储类的回收策略 (kubectl get storageclass)。
PVCTerminating1. 被Pod挂载使用中:仍有Pod在引用此PVC。
2. PV处于异常状态:绑定的PV本身是 FailedReleased 状态。
describe pvc 查看 Used By 字段。`kubectl get pods -o json

提示:当节点失联时,其上所有Pod都会卡在 Terminating。此时控制平面会标记这些Pod为“已删除”,但需要等待节点恢复或管理员强制移除后,这些Pod对象才会真正消失。你可以通过 kubectl get pods -o wide 看到Pod被困在哪个 NODE 上。

诊断完成后,我们心里就有底了。接下来,我们就针对每种资源,进行“手术治疗”。

3. 高效清理实战:Pod的终结艺术

Pod是Kubernetes世界里的“短跑选手”,设计初衷就是随时可以销毁和重建。但正是这种特性,让它的删除过程必须足够优雅和彻底,否则就会留下“烂摊子”。

3.1 常规删除与优雅终止

正常情况下,删除一个Pod就像送别一位完成任务的战士:

kubectl delete pod my-app-pod -n my-namespace

这条命令会触发Kubernetes标准的优雅终止流程

  1. Pod状态变为 Terminating
  2. 如果Pod定义了 preStop 钩子,kubelet会先执行它。这是一个给你做清理工作的机会,比如通知下游服务、保存状态等。
  3. kubelet向Pod内的每个容器发送 SIGTERM 信号。
  4. 等待一个“宽限期”(默认30秒),让容器处理完 SIGTERM 信号并自行退出。
  5. 如果容器在宽限期后仍未退出,kubelet会发送 SIGKILL 信号强制杀死进程。
  6. 清理Pod占用的资源(网络、存储卷等),最后从API Server中删除Pod对象。

这个流程在绝大多数情况下都工作良好。问题就出在第2步和第4步。如果你的 preStop 脚本里有死循环,或者容器进程忽略了 SIGTERM 信号,Pod就会卡住。

3.2 强制删除:何时用,怎么用?

当优雅终止失效时,我们就需要动用“强制删除”这把手术刀。它的核心是绕过正常的终止流程。

kubectl delete pod my-app-pod -n my-namespace --grace-period=0 --force
  • --grace-period=0:将优雅终止的等待时间设为0秒,立即进入强制阶段。
  • --force:强制从API Server中删除对象,即使kubelet报告删除失败。

但是,请务必谨慎使用! 我强烈建议你先尝试 --grace-period=0 而不加 --force。因为前者还是会尝试发送 SIGTERM 并执行 preStop(只是不给等待时间),而后者是直接从API Server里抹掉这个Pod的记录,可能导致节点上的容器进程变成“孤儿进程”,需要你手动登录节点去清理。

更常见的场景是,Pod卡住是因为它的“老板”(Owner Reference)还在。 比如,你删了一个Deployment管理的Pod,但Deployment控制器会立刻创建一个新的来维持副本数。这时候,你需要删除的是Deployment或者将其副本数缩容到0。

# 先缩容,再删除Pod,或者直接删除Deployment
kubectl scale deployment my-deploy -n my-namespace --replicas=0
# 或者
kubectl delete deployment my-deploy -n my-namespace

3.3 处理“幽灵Pod”:Finalizer的移除

我遇到过最棘手的一种情况是,Pod的 metadata.finalizers 字段里被添加了一些自定义内容,而执行这些finalizer的控制器已经不存在了。这会导致Pod永远卡在 Terminating,连 --force 都删不掉。

这时,我们需要直接编辑API对象,移除finalizer。这是一个危险操作,务必确认该Pod已无任何作用且无法通过常规手段删除。

# 1. 将Pod的配置导出到本地
kubectl get pod <pod-name> -n <namespace> -o yaml > stuck-pod.yaml

# 2. 编辑 stuck-pod.yaml,找到 `metadata.finalizers` 字段,将其值设置为空列表 `[]`
# 例如,将:
# finalizers:
# - custom-controller.io/cleanup
# 改为:
# finalizers: []

# 3. 使用 replace 命令,用本地文件替换API Server中的对象
kubectl replace -f stuck-pod.yaml --force

执行后,阻塞的finalizer被移除,Pod通常会被立即清理掉。这个方法同样适用于其他因finalizer卡住的资源(如Namespace、PV)。

4. Namespace清理:连根拔起的系统工程

删除Namespace是一个“连根拔起”的操作,Kubernetes会尝试删除其中的所有资源。正因为涉及面广,所以更容易出问题。

4.1 标准删除流程与等待

删除Namespace的命令很简单:

kubectl delete namespace my-test-ns

之后,你可以通过 kubectl get namespace my-test-ns -o yaml 观察它的状态。你会看到它的 status.phase 变为 Terminating,并且Kubernetes会为它添加一个 kubernetes finalizer。控制平面会开始协调删除该命名空间下的所有资源。

这个过程可能很慢,尤其是里面有大量Pod、ConfigMap或者自定义资源时。耐心等待是第一要务,不要一看到Terminating就急着强制操作。可以配合 watch 命令观察资源数量逐渐减少:

watch -n 2 'kubectl get all -n my-test-ns | wc -l'

4.2 破解Namespace删除僵局

如果Namespace卡住很久(比如超过10分钟),就该介入排查了。根本原因几乎总是某个或某些资源无法被删除,而它们又都有finalizer。

第一步:找出“钉子户”资源。 不要只看 kubectl get allall 只是一些常见资源。我们需要列出所有类型的资源。

# 获取命名空间下所有存在的资源类型
kubectl api-resources --verbs=list --namespaced -o name | xargs -n 1 kubectl get --show-kind --ignore-not-found -n my-test-ns

这条命令组合会列出该Namespace下每一种资源类型的具体对象。仔细查看输出,找到那些状态异常(如 Terminating)或者你不认识的资源。

第二步:诊断并清理问题资源。 假设我们发现了一个卡住的 MyCustomResource (CRD)。

  1. kubectl describe mycrd my-instance -n my-test-ns 查看它的finalizer和事件。
  2. 如果确定可以删除,尝试像处理Pod一样,先移除其finalizer
    kubectl patch mycrd my-instance -n my-test-ns -p '{"metadata":{"finalizers":[]}}' --type=merge
    
    对于Namespace本身,如果是因为APIService等问题卡住,也可以尝试移除其finalizer(风险较高,仅在其他资源都清空后使用):
    kubectl patch namespace my-test-ns -p '{"metadata":{"finalizers":[]}}' --type=merge
    

第三步:终极武器——直接操作etcd(仅限极端情况) 如果以上所有方法都失效,且这是一个无关紧要的、必须清除的Namespace,作为最后手段,可以通过访问Kubernetes的后端存储etcd来删除。这需要集群管理员权限,且操作不当会严重破坏集群。

# 假设使用 etcdctl v3
ETCDCTL_API=3 etcdctl del /registry/namespaces/my-test-ns --prefix

注意:直接操作etcd是核武器级别的操作,除非万不得已,并且你非常清楚自己在做什么,否则绝对不要使用。在操作前,务必对etcd进行备份。

5. 存储资源清理:释放被锁定的空间

PV和PVC的清理是另一个重灾区,因为它们关联着集群外部的、昂贵的存储系统。清理不当可能导致数据丢失或存储卷泄漏(在云上持续产生费用)。

5.1 理解PV/PVC的生命周期与状态

PV和PVC的绑定关系是理解清理的关键。一个PVC在 Pending -> Bound -> Released 的状态流转中,PV也随之变化。

  • Bound(已绑定):PVC成功匹配并绑定到一个PV。
  • Released(已释放):PVC被删除,但PV还未被集群回收。此时PV可能还保留着原用户的数据(根据回收策略)。
  • Failed(失败):PV在自动回收过程中发生错误。

回收策略(Reclaim Policy) 决定了PVC删除后PV的命运:

  • Retain(保留):默认策略。PV和數據都被保留,需要管理员手动清理。这是最安全,也最需要手动介入的策略。
  • Delete(删除):自动删除PV以及后端存储设施中的对应存储卷。方便,但有数据丢失风险。
  • Recycle(回收)(已弃用):删除卷上数据,使其可被新的PVC使用。

5.2 安全删除PVC与PV

标准流程应该是:先删Pod -> 再删PVC -> 最后根据回收策略处理PV。

  1. 删除使用PVC的Pod:确保没有任何Pod在挂载这个PVC。

    kubectl delete pod -l app=my-app -n my-namespace # 通过标签选择器删除
    
  2. 删除PVC

    kubectl delete pvc my-data-pvc -n my-namespace
    

    删除后,对应的PV状态会变为 Released

  3. 处理Released状态的PV

    • 如果PV的回收策略是 Delete:理论上PV会被自动删除。如果卡住,检查存储插件日志。
    • 如果PV的回收策略是 Retain:这就是需要手动清理的典型场景。PV会一直处于 Released 状态。

5.3 手动释放“Retain”策略的PV

对于 Retain 策略的PV,你需要手动完成两件事:

  1. 从Kubernetes中删除PV对象

    kubectl delete pv my-retained-pv
    

    如果因为 finalizers 卡住,同样可以用 patch 命令移除:

    kubectl patch pv my-retained-pv -p '{"metadata":{"finalizers":[]}}' --type=merge
    
  2. 在外部存储系统中清理实际存储卷:这才是真正释放空间和成本的关键。这一步完全取决于你的存储后端。

    • AWS EBS:去EC2控制台找到对应的卷(通过卷ID,通常在PV的 spec.awsElasticBlockStore.volumeID 中),然后删除。
    • Azure Disk:在Azure门户中找到对应的托管磁盘并删除。
    • Ceph RBD:使用 rbd 命令来删除镜像:rbd rm <pool-name>/<image-name>
    • NFS:登录NFS服务器,删除对应的目录。

一个非常实用的技巧是,在删除PV前,将其策略改为 Retain,以防误操作导致数据被自动删除:

kubectl patch pv my-pv -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'

5.4 处理“Failed”状态的PV

PV处于 Failed 状态,通常意味着存储插件在删除底层卷时遇到了永久性错误(如存储系统认证失败、卷不存在等)。此时,PV对象本身可能也删不掉。

  1. 首先尝试强制删除PV对象:
    kubectl delete pv my-failed-pv --force --grace-period=0
    
  2. 如果还不行,移除finalizer后删除(见上文)。
  3. 最重要的是,去检查存储后端的日志和状态。确保底层的问题(如权限、网络)被解决,否则即使删除了Kubernetes中的PV对象,实际存储卷可能还泄漏着。

存储资源的清理,本质上是在协调Kubernetes内部状态和外部存储系统的真实状态。保持两者一致,需要细心和严谨的操作记录。养成好习惯,在创建重要PV/PVC时做好标记,清理时按图索骥,就能最大程度避免混乱和资源泄漏。

更多推荐