Pod 一直 Terminating,死活删不掉?三个凶手,每一个你都意想不到

发布时间:2026/7/8 1:39:35
Pod 一直 Terminating,死活删不掉?三个凶手,每一个你都意想不到 kubectl delete pod my-pod # 5 分钟过去了…… kubectl get pod my-pod # my-pod 0/1 Terminating 0 5m30s你盯着 Terminal光标一闪一闪。Pod 还是 Terminating。你加了--force --grace-period0还是删不掉。你又加了--force --grace-period0这次终于没了——但你不知道刚才那一刀下去到底留下了什么后遗症。Pod 从删到消失——你以为很快其实藏了六道门大多数人脑子里 Pod 删除的过程kubectl delete pod → Terminating → 消失真实的完整流程kubectl delete pod │ ▼ API Server 设 DeletionTimestamp DeletionGracePeriodSeconds │ ▼ kubelet 收到 DELETE 事件 │ ├── 1. 执行 preStop hook如果配了 ← 可能卡死 ├── 2. 等待 terminationGracePeriodSeconds ← 默认 30s ├── 3. 给容器发 SIGTERM ← 应用可能不响应 ├── 4. 等待 SIGKILL ← 又给了 2s ├── 5. 卸载 Volumeumount ← 可能永远卡住 ├── 6. 通知 API Server 删 Pod │ ▼ Finalizer 检查 → 所有 Finalizer 移除 → Pod 真正删除 │ ▼ GC 回收每一道门都可能卡死而且卡在不同的门排查方式完全不同。生产环境里最常卡的是三道门finalizer 挡路、preStop 死等、Volume 卸载失败。⚔️第一凶手Finalizer——Pod 删不掉的罪魁祸首Finalizer 是 Kubernetes 的一个保护机制在 Pod 被真正删除前必须先通过某种外部审批。看一个卡住的 Podkubectl get pod stuck-pod -o yaml | grep -A5 finalizersmetadata: deletionTimestamp: 2026-05-27T14:00:00Z # 删了很久了 finalizers: - kubernetes.io/pvc-protection # ← 就是它在挡路kubernetes.io/pvc-protection这个 finalizer 说这个 Pod 还引用着 PVC在 PVC 安全解除绑定前不准删 Pod。为什么 PVC 还没解除绑定因为 Volume 还没 umount。为什么没 umount因为 kubelet 卸载的时候发现文件系统还在被占用device is busy。死循环Pod 等 finalizer 移除 → finalizer 等 PVC 释放 → PVC 等 umount 完成 → umount 等 Pod 停止容器 → Pod 在等 finalizer 移除这就是经典的卡死 Terminating现场。怎么破手动拆断链条# 1. 先看是哪个 finalizer 在挡路 kubectl get pod stuck-pod -o jsonpath{.metadata.finalizers} # 2. 直接干掉它危险操作确保你知道后果 kubectl patch pod stuck-pod -p {metadata:{finalizers:null}} --typemerge⚠️ 但这样做的后果是什么PVC 没有被正确 detach→ 下一个使用同一 PVC 的 Pod 可能挂载失败 → 数据不一致。这不是一条随意执行的命令。你在绕过一道安全门。除了 PVC finalizer常见的挡路鬼还有foregroundDeletion— 级联删除模式自定义 CRD finalizerOperator 加的Service Account Token 的 finalizer第二凶手preStop Hook——你亲手写的死循环lifecycle: preStop: exec: command: - /bin/sh - -c - echo shutting down sleep 60 curl http://localhost:8080/notify问题在哪如果localhost:8080已经在 Nginx 退出后不可达了curl会一直卡着默认超时没有设置。preStop 启动 → 尝试执行 curl → 连不上 → 超时等待可能很久 → kubelet 在等 preStop 完成 → Pod 在 Terminating 状态只要 preStop 没返回kubelet 就不会给容器发 SIGTERM。Pod 就在 Terminating 状态无限等待。 最狠的场景preStop 里写了一个永远不会返回的命令Pod 永远删不掉——直到你--force。preStop 的正确写法lifecycle: preStop: exec: command: - /bin/sh - -c - | # 1. 设超时 timeout 10 curl http://localhost:8080/notify || true # 2. 等 in-flight 请求完成必须设上限 sleep 5 # 3. 明确退出 exit 0三条铁律所有网络调用必须有timeout— curl、wget、grpc_health_probe 统统加超时所有 sleep 不能超过terminationGracePeriodSeconds - 5s— 否则没等到 SIGTERM 就被 SIGKILL 了必须exit 0— 返回值不为 0 不代表重试kubelet 不会重试它只会等第三凶手Volume 卸载失败——device is busy这是最难排查的因为日志很少。kubelet 在 Pod Terminating 时会依次卸载所有 Volume。但在容器还没完全停止时Volume 可能还在被进程使用。nginx 容器在写访问日志 → 对 Volume 持有文件句柄 kubelet 尝试 umount → 返回 device is busy kubelet 重试 umount → 还是 busy 重试 → 还是 busy → 重试……在 kubelet 日志里看不到明确的错误——它会一直重试直到天荒地老。源码在pkg/kubelet/volumemanager/func (dsw *desiredStateOfWorldPopulator) checkVolumeUnmount() { // 重试逻辑不会停止没有最大重试次数 // 所以 Pod 就会永远 Terminating } 这个场景最常发生在NFS/CephFS 挂载— 网络文件系统umount 失败率远高于本地存储hostPath— 容器内进程 touch 了一个文件忘了关文件句柄一直活着subPath 挂载— 比普通 Mount 多一层文件系统绑定出错概率更高怎么确认是 Volume 的问题# 在节点上查看挂载点 mount | grep pod-uid # 如果挂载点还在 → Volume 没卸载 # 如果能找到 → 检查是否有进程在用这个挂载点 lsof | grep mount-point如果有进程还在用——kill 掉那个进程umount 就走得通了。怎么预防spec: terminationGracePeriodSeconds: 60 # 给足时间让应用优雅关闭文件如果你知道你的应用需要 10 秒来关闭文件和连接——terminationGracePeriodSeconds至少设 30 秒。默认也是 30 秒但给了就是给了。⚔️SIGTERM 被无视了——你的应用是个聋子kubelet 发给容器的第一个信号是 SIGTERM相当于kill -15。这意味着应用可以捕获这个信号做一些清理工作。但如果应用没有正确处理 SIGTERM 呢常见的防坑Node.js 没有处理 signal// ❌ 这样写SIGTERM 直接杀进程——不等任何 in-flight 请求 const express require(express); const app express(); app.listen(3000); // 没有 process.on(SIGTERM, ...) 暴力退出// ✅ 正确写法 process.on(SIGTERM, () { console.log(Received SIGTERM, closing server...); server.close(() { console.log(Server closed, exiting); process.exit(0); }); // 设一个超时以防万一 setTimeout(() process.exit(1), 5000); });Java 同理——JVM 不会自动处理 SIGTERM你得用Runtime.getRuntime().addShutdownHook()。Go 同理——signal.Notify()是必须的。如果进程忽略了 SIGTERMkubelet 会等terminationGracePeriodSeconds秒然后发 SIGKILL。SIGKILL 是不可捕获的——进程被直接杀掉没有清理机会。这解释了为什么有时候 Pod 删得很快进程收到 SIGTERM 正确退出有时候超慢进程忽略 SIGTERM硬生生等到 grace period 结束才被 SIGKILL 爆头。终极杀招--force --grace-period0——你知道你在干什么吗当一切卡住--force --grace-period0就是你的最后手段kubectl delete pod stuck-pod --force --grace-period0这是用DeleteOptions里的GracePeriodSeconds: 0直接从 etcd 删 Pod。API Server 不会等 kubelet 确认容器已停止——直接删记录。⚠️后果清单1️⃣容器可能还在运行— kubelet 最终会发现 Pod 已经不存在了然后清理容器。但这个过程有延迟。同一时间可能有僵尸容器在消耗节点资源。2️⃣Volume 没被正确卸载— 这是最危险的。如果 Pod 用的是 RWOReadWriteOnce模式的 PVC下一个绑定同一 PVC 的 Pod 到不同节点时Volume 因为还没 umount 而挂载失败。3️⃣StatefulSet 的有序删除被打破— StatefulSet Controller 认为 Pod 还在、绑定也还在。force delete会在它背后抽掉基石导致 PVC 和 Pod 的绑定关系破裂。4️⃣preStop hook 没执行— 日志没被优雅 flush、连接没被优雅关闭。下游看到的是连接突然断开——不是一个优雅的 FIN而是断崖式的 RST。--force --grace-period0不是解决问题是跳过问题。跳过之后出的新问题往往比卡住 Terminating 更麻烦。排查流程图Pod 卡在 Terminating 时按什么顺序查Pod 卡在 Terminating → 第 1 步kubectl describe pod 看 Events ├─ 有 unmount failed 或 device busy │ → 凶手Volume 卸载失败 │ → 解法上节点 lsof fuser 查谁在用挂载点 │ ├─ Events 为空 finalizers 非空 │ → 凶手finalizer 在挡路 │ → 解法kubectl get pod -o yaml | grep finalizers │ 确认后可手动 patch 移除评估后果 │ └─ Events 为空 finalizers 为空 时间还没到 30s → 凶手preStop hook 在执行中 或 进程没响应 SIGTERM → 解法等 terminationGracePeriodSeconds 超时 如果配得太长 → 改短后再删最后一击Pod 卡在 Terminating 不是Kubernetes 坏了恰恰是Kubernetes 在遵守你设定的规则。Finalizer 在等你确认 PVC 安全脱钩——是你写的规则。preStop hook 在执行你写的脚本——是你让它等的。terminationGracePeriodSeconds 在给你留优雅关闭的时间——是你设的。这些机制都在保护你——但它们保护得太认真了。认真到 Pod 真的删不掉。下次卡住别急着--force --grace-period0。打开kubectl describe pod看看 Events、查查 finalizers、上节点lsof看一眼挂载点。知道凶手在哪一刀毙命——比暴力删 Pod 优雅一万倍