Kubernetes 疑难杂症:Pod 始终处于 Terminating 状态的真实原因与安全修复方案

摘要 :在 Kubernetes 集群运维中,Pod 卡在 Terminating 状态是一个常见的问题。通常情况下,这极有可能是因为容器运行时(如历史版本中的 Docker 或 containerd)在宿主机或其他命名空间中发生了挂载泄漏(mount leak)。这会导致 kubelet 在清理资源时触发 "device or resource busy" 错误,进而使得该 Pod 的 API 对象永远滞留。本文将带你从底层逻辑拆解该问题,并提供安全、彻底的修复步骤。


现象描述

当删除一个包含多个 Pod 的资源对象(如 ReplicationController 或 Deployment)时,大部分 Pod 会被删除,但总有几个顽固的 Pod 停留在 Terminating 状态:

bash 复制代码
NAME        READY   STATUS        RESTARTS   AGE
pod-186o2   1/1     Terminating   0          2h
pod-4b6qc   1/1     Terminating   0          2h
pod-8xl86   1/1     Terminating   0          1h

此时,无论是反复执行 kubectl delete 还是等待,都无济于事。要彻底解决这个问题,我们需要先理解其背后的工作机制。

为什么 Pod 会卡在 "Terminating"?

当 Kubernetes 删除一个 Pod 时,kubelet 会执行一个严谨的关闭流程:发送 SIGTERM 信号 -> 等待 terminationGracePeriodSeconds -> 停止容器 -> 卸载(unmount)卷和网络命名空间 -> 最后从节点和 API Server 中移除该 Pod。

如果卸载(unmounting)过程失败 (通常是因为挂载点处于 "busy" 状态),清理流程就会中断,导致 API 对象陷入 Terminating 的边缘状态。

这种 "device or resource busy" 的错误在 Kubernetes 和 Docker/Moby 的 issue 跟踪器中由来已久。核心原因通常分为两类:

  1. 挂载泄漏 / 运行时占用(Host-level) :容器运行时泄露了挂载点,或者有进程在这些挂载点内持有了文件描述符。常见症状包括 kubelet 无法移除 Pod 的 volume 路径;umount 报告 "not mounted" 但 /proc/mounts 中依然存在记录。
  2. Finalizers 阻塞(API-level):例如 PVC/CSI finalizers 为了确保资源被正确清理,会有意拦截删除操作。如果负责清理的控制器发生故障,对象就会卡住。(警告:在未完全评估后果前,切勿暴力移除 finalizers)。

快速排查决策树

遇到该问题时,按照以下逻辑进行排查,切勿盲目使用 --force

第一步:检查是否被 Finalizer 阻塞

bash 复制代码
kubectl get pod <POD_NAME> -o jsonpath='{.metadata.finalizers}'
  • 如果输出包含 finalizers:问题出在存储/CSI 的清理流程上。请修复底层的存储依赖,而不是强行删除 Pod。仅在底层资源已手动确认处理完毕后,作为最后手段移除 finalizer。
  • 如果没有 finalizers:高度怀疑是挂载泄漏或运行时持有。进入宿主机级别的深度排查。

节点级深度排查与修复(针对 Docker & containerd)

通过 kubectl describe pod <POD_NAME> 找到 Pod 所在的节点,并通过 SSH 登录到该节点。

1. 定位 Pod 的 UID 和挂载路径

首先获取目标 Pod 的 UID:

bash 复制代码
POD=pod-186o2
NS=default
UID=$(kubectl get pod $POD -n $NS -o jsonpath='{.metadata.uid}')
echo $UID

在节点上检查 kubelet 下的挂载情况:

bash 复制代码
# 查找属于该 UID 的挂载记录
grep -F "$UID" /proc/mounts || true
findmnt | grep "$UID" || true

如果看到类似 /var/lib/kubelet/pods/<UID>/volumes/... 的路径拒绝被卸载,这就是罪魁祸首。

2. 识别占用挂载点的进程

bash 复制代码
# 查找哪个进程打开了挂载点下的文件或目录
lsof +D /var/lib/kubelet/pods/$UID 2>/dev/null | head

如果某个容器运行时或 sidecar 进程仍持有打开的文件,这里会暴露其行踪。

3. 容器运行时清理

根据你集群使用的运行时,执行相应的清理操作。

对于 Docker(传统集群):

bash 复制代码
docker ps --no-trunc | grep $POD
# 如果找到容器,尝试优雅停止:
docker stop <containerID>

# 逃生舱操作(高危:会重启节点上所有容器):
# systemctl restart docker

注:docker rm 时的 "device or resource busy" 是个历史悠久的已知问题(涉及 aufs/overlay2)。重启 Docker daemon 通常能释放泄漏的引用,但在生产节点需极其谨慎。

对于 containerd(现代 Kubernetes 默认):

bash 复制代码
crictl ps | grep $POD
crictl stopp <containerID>
crictl rmp <containerID>

# 逃生舱操作(高危):
# systemctl restart containerd

4. 强制卸载残留路径 (Lazy Umount)

如果运行时已不再持有该路径,但挂载依然存在:

bash 复制代码
umount -l /var/lib/kubelet/pods/$UID/volumes/<driver>/<vol> || true

(注意:仅当确认使用该路径的进程已彻底退出后,才能使用 -l 参数。Lazy umount 会立即断开连接,并在 busy 引用消失后清理系统资源。)


安全删除 Pod(仅在解除占用后)

一旦 kubelet 能够完成节点的清理工作,普通的删除命令即可生效:

bash 复制代码
kubectl delete pod $POD -n $NS

如果普通的 delete 依然无效,且你已经 100% 确认节点上的真实工作负载和挂载均已清理干净,方可使用强制删除来清理 API 对象:

bash 复制代码
kubectl delete pod $POD -n $NS --grace-period=0 --force

根本预防与最佳实践

为了减少此类事件的发生,建议采取以下加固措施:

  1. 保持运行时更新:及时升级 containerd/Docker,包含的 bug 修复能大幅减少挂载泄漏的边缘场景。
  2. 合理设置优雅退出时间 :准确配置 terminationGracePeriodSeconds,确保应用在 kubelet 强行介入前有充足时间完成退出。
  3. 监控 OOM 事件 :节点的高负载或内核 OOM 行为容易遗留异常的挂载状态,甚至产生不可杀死的 D-state 进程。
  4. 存储组件健康度:确保 CSI 驱动和 PVC finalizers 能够正常完成工作,并对卡住的 finalizers 建立告警机制。

运维速查手册

bash 复制代码
# === 1. Docker 环境清理 ===
docker ps --no-trunc | grep <pod-name-or-uid> || true
docker stop <containerID> || true
# sudo systemctl restart docker # 终极手段

# === 2. containerd 环境清理 ===
crictl ps | grep <pod-name-or-uid> || true
crictl stopp <containerID> && crictl rmp <containerID> || true
# sudo systemctl restart containerd # 终极手段

# === 3. Kubernetes API 清理 ===
kubectl delete pod <POD> -n <NS>                # 正常删除
kubectl delete pod <POD> -n <NS> --grace-period=0 --force  # 清理完底层后的强制删除

结语 :下次当你看到 Pod 永远处于 Terminating 时,不要直接敲击 --force 的手。

相关推荐
zyplayer-doc3 小时前
VuePress类静态文档站和动态知识库怎么选:两种技术路线的适用场景
javascript·人工智能·后端·安全·智能手机
KKKlucifer4 小时前
拨开接口黑盒迷雾:运营商第三方合作接口安全审计与准入管控落地实践
网络·人工智能·安全
@insist1235 小时前
系统集成项目管理工程师-安全架构与云原生架构
云原生·架构·软考·安全架构·系统集成项目管理工程师·软考中项·软件水平考试
小绫网络安全5 小时前
【每日一讲】学习网络安全的一些注意事项
网络·安全·web安全
SamChan908 小时前
PDF翻译API的JWT签名鉴权与速率限制设计:安全实战指南
网络·安全·pdf
阿里云云原生9 小时前
一张告警卡片到一键 RCA:塔斯汀万店连锁的智能运维闭环实践
云原生
坐吃山猪10 小时前
Docker07-MySQL
mysql·docker·容器
Python私教10 小时前
签名 URL 刷新导致审批失效?别急着删掉所有 query
python·安全
Kismet_nvi10 小时前
《Kubernetes Service 进阶、kube-proxy 与 Ingress 实战精要》
云原生·容器·kubernetes
weixin_6182323010 小时前
OpenAI 因安全暂停 Astra:前沿 Agent 第一次撞上“关键级”红线
安全