1. 问题现象与背景分析
最近在维护Kubernetes集群时,我发现有几个Pod一直处于Terminating状态,无论怎么删除都无法彻底清除。这种情况在生产环境中并不少见,但很多运维人员会直接使用--force参数强制删除,这种做法其实存在很大风险。今天我就来详细分析这个问题的根源,并分享一套安全的解决方案。
首先,让我们看看典型的Terminating状态Pod长什么样:
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
这些Pod就像"僵尸"一样,既无法正常工作,又无法被彻底删除。要解决这个问题,我们需要先理解Kubernetes删除Pod的完整流程。
2. Kubernetes Pod删除机制解析
当执行kubectl delete pod命令时,Kubernetes会触发一个精心设计的删除流程:
- API Server标记删除:首先在etcd中将Pod标记为Terminating状态
- kubelet接收通知:节点上的kubelet检测到Pod状态变化
- 优雅终止流程:
- 发送SIGTERM信号给容器内进程
- 等待terminationGracePeriodSeconds(默认30秒)
- 发送SIGKILL强制终止
- 资源清理:
- 卸载存储卷(umount)
- 释放网络资源
- 清理cgroups等资源
- API对象删除:从API Server中移除Pod记录
这个过程中最容易出问题的就是第4步的资源清理阶段,特别是存储卷的卸载操作。
3. 问题根源深度剖析
经过多次问题排查和源码分析,我发现导致Pod卡在Terminating状态的主要原因有两类:
3.1 挂载泄漏(Mount Leak)
这是最常见的原因,表现为:
umount命令返回"device or resource busy"错误/proc/mounts中仍然存在挂载记录lsof显示有进程持有文件描述符
这种情况通常是由于:
- 容器运行时(Docker/containerd)的bug导致挂载点引用未释放
- 宿主机上有进程意外进入了挂载命名空间
- 存储插件(如NFS客户端)处理异常
3.2 Finalizers阻塞
Finalizers是Kubernetes的一种保护机制,常见于:
- PVC/PV的删除保护
- CSI存储驱动清理逻辑
- 自定义控制器的资源清理
当相关控制器异常时,这些Finalizers会导致对象无法删除。
4. 系统化排查流程
遇到Terminating Pod时,建议按照以下步骤排查:
4.1 检查Finalizers
bash复制kubectl get pod <POD_NAME> -o jsonpath='{.metadata.finalizers}'
如果有输出,说明是Finalizers导致的阻塞。此时应该:
- 检查相关控制器(如CSI驱动)是否正常
- 确认底层存储资源是否已清理
- 最后才考虑手动移除Finalizer
4.2 节点级深度排查
如果没有Finalizers,就需要登录Pod所在节点进行排查:
- 获取Pod UID:
bash复制POD=problem-pod
NS=default
UID=$(kubectl get pod $POD -n $NS -o jsonpath='{.metadata.uid}')
- 检查挂载点:
bash复制grep -F "$UID" /proc/mounts
findmnt | grep "$UID"
- 查找占用进程:
bash复制lsof +D /var/lib/kubelet/pods/$UID
5. 针对性解决方案
根据不同的排查结果,采取相应的解决方案:
5.1 Docker环境处理
对于使用Docker作为运行时的集群:
bash复制# 查找相关容器
docker ps --no-trunc | grep $POD
# 尝试优雅停止
docker stop <containerID>
# 终极手段(谨慎使用)
systemctl restart docker
注意:重启Docker会导致节点上所有容器重启,生产环境需谨慎评估影响。
5.2 Containerd环境处理
对于使用containerd的集群:
bash复制# 查找容器
crictl ps | grep $POD
# 停止并删除容器
crictl stopp <containerID>
crictl rmp <containerID>
# 终极手段
systemctl restart containerd
5.3 强制卸载挂载点
当确认没有进程占用后,可以尝试强制卸载:
bash复制umount -l /var/lib/kubelet/pods/$UID/volumes/...
-l参数表示lazy unmount,系统会在引用释放后自动完成卸载。
5.4 API对象清理
确保底层资源已清理后,可以删除API对象:
bash复制# 正常删除
kubectl delete pod $POD
# 强制删除(最后手段)
kubectl delete pod $POD --grace-period=0 --force
6. 预防措施与最佳实践
为了减少这类问题的发生,我总结了以下经验:
-
运行时版本管理:
- 保持Docker/containerd版本更新
- 关注已知的挂载泄漏bug修复
-
优雅终止配置:
- 合理设置terminationGracePeriodSeconds
- 应用正确处理SIGTERM信号
-
监控告警:
- 监控Terminating状态超过阈值的Pod
- 对Finalizers长时间阻塞的情况设置告警
-
存储系统健康检查:
- 定期验证CSI驱动健康状态
- 监控存储后端可用性
7. 实战案例分享
最近我们生产环境遇到一个典型案例:一个StatefulSet的Pod卡在Terminating状态超过24小时。通过上述方法排查,发现是NFS存储卷卸载失败。具体解决步骤:
- 确认没有Finalizers阻塞
- 登录节点发现NFS挂载点busy
- 使用
lsof找到是某个sidecar容器意外持有了文件描述符 - 手动停止相关容器进程
- 成功卸载NFS卷
- 正常删除Pod
整个过程耗时约30分钟,避免了强制删除可能导致的数据一致性问题。
8. 运维工具箱
以下是我整理的常用命令合集,建议保存备用:
bash复制# 1. 基础检查
kubectl get pod -o wide
kubectl describe pod <POD>
kubectl get pod <POD> -o jsonpath='{.metadata.finalizers}'
# 2. 节点排查
POD_UID=$(kubectl get pod <POD> -o jsonpath='{.metadata.uid}')
grep -F "$POD_UID" /proc/mounts
findmnt | grep "$POD_UID"
lsof +D /var/lib/kubelet/pods/$POD_UID
# 3. Docker清理
docker ps --no-trunc | grep <POD>
docker stop <CONTAINER>
systemctl restart docker
# 4. Containerd清理
crictl ps | grep <POD>
crictl stopp <CONTAINER>
crictl rmp <CONTAINER>
systemctl restart containerd
# 5. 强制卸载
umount -l <MOUNT_POINT>
# 6. API清理
kubectl delete pod <POD> --grace-period=0 --force
9. 经验总结与思考
在处理了数十起Terminating Pod案例后,我总结出几点重要经验:
-
切忌盲目强制删除:
--force应该是最后手段,而非首选方案。强制删除可能掩盖真正的问题,导致问题反复出现。 -
理解底层机制:只有深入理解Kubernetes的资源清理流程,才能快速定位问题根源。
-
系统性排查:按照从API层到节点层的顺序排查,可以避免遗漏关键线索。
-
预防胜于治疗:通过合理的监控和配置,可以大幅减少此类问题的发生概率。
最后提醒大家,在云原生环境中,稳定性往往取决于对这些"小问题"的处理能力。每次遇到Terminating Pod,都是深入了解Kubernetes内部机制的好机会。