Kubernetes 僵尸容器清理完整教程
·
问题描述
当 Kubernetes 集群中出现以下症状时,可能存在僵尸容器问题:
症状识别
-
Pod 事件中出现大量
FailedKillPod错误error killing pod: failed to "KillContainer" with KillContainerError: "rpc error: code = DeadlineExceeded desc = context deadline exceeded" -
Pod metrics 异常
- CPU 显示异常高的值(如 14635 cores)
- 内存或 CPU 显示为 0
kubectl top pods返回不正常数据
-
节点资源占用异常
- 内存使用率持续在 75-80% 以上
- 存在多个月前的旧进程仍在运行
-
容器状态异常
crictl ps | grep <pod-name> # 显示多个相同 Pod 的容器,创建时间相差很久(如几个月)
诊断步骤
1. 检查 Pod 状态和事件
# 查看 Pod 状态
kubectl get pods -n <namespace> -o wide
# 查看最近的事件
kubectl get events -n <namespace> --sort-by='.lastTimestamp' | tail -50
# 查看特定 Pod 的详细信息
kubectl describe pod <pod-name> -n <namespace>
2. 检查节点上的容器
在每个节点上执行:
# 列出所有容器
crictl ps | grep <pod-name>
# 检查是否有旧容器(注意 CREATED 列)
# 正常情况:所有容器的创建时间应该相近
# 异常情况:存在 "X months ago" 的旧容器
3. 检查进程状态
# 查看相关进程
ps aux | grep <application-name> | grep -v grep
# 检查是否有僵尸进程(状态为 Z)
ps aux | grep defunct
4. 检查系统资源
# 检查内存使用
free -h
# 检查节点资源
kubectl top nodes
# 检查 Pod 资源(可能失败)
kubectl top pods -n <namespace>
解决方案
方案概述
僵尸容器的清理需要在每个受影响的节点上执行以下步骤:
- 识别并杀死旧容器的进程
- 停止 containerd
- 清理容器元数据
- 重启 containerd
完整清理脚本
将以下脚本保存为 cleanup-zombie-containers.sh:
#!/bin/bash
# Kubernetes 僵尸容器清理脚本
# 使用方法:sudo ./cleanup-zombie-containers.sh <old-container-id>
set -e
if [ "$EUID" -ne 0 ]; then
echo "请使用 root 权限运行此脚本"
exit 1
fi
if [ -z "$1" ]; then
echo "使用方法: $0 <container-id>"
echo "示例: $0 c6cfbe677343b"
exit 1
fi
OLD_CONTAINER_ID="$1"
echo "=========================================="
echo "开始清理僵尸容器: $OLD_CONTAINER_ID"
echo "=========================================="
# 1. 查找容器的进程 ID
echo ""
echo "=== 步骤 1: 查找容器进程 ==="
PID=$(crictl inspect "$OLD_CONTAINER_ID" 2>/dev/null | grep '"pid"' | head -1 | grep -oP ':\s*\K\d+')
if [ -n "$PID" ] && [ "$PID" != "1" ]; then
echo "找到容器进程 PID: $PID"
# 杀死主进程和子进程
echo "杀死进程及其子进程..."
pkill -9 -P "$PID" 2>/dev/null || true
kill -9 "$PID" 2>/dev/null || true
sleep 2
else
echo "未找到有效的进程 ID 或 PID 为 1(跳过)"
fi
# 2. 查找并杀死旧的应用进程(根据日期)
echo ""
echo "=== 步骤 2: 清理旧的应用进程 ==="
echo "查找 4 个月前(May)启动的进程..."
OLD_PIDS=$(ps aux | grep -E "May14|May" | grep -v grep | awk '{print $2}')
if [ -n "$OLD_PIDS" ]; then
echo "找到旧进程: $OLD_PIDS"
echo "$OLD_PIDS" | xargs -r kill -9 2>/dev/null || true
sleep 2
else
echo "未找到旧进程"
fi
# 3. 查找 Pod Sandbox ID
echo ""
echo "=== 步骤 3: 查找 Pod Sandbox ==="
SANDBOX_ID=$(crictl inspect "$OLD_CONTAINER_ID" 2>/dev/null | grep '"sandboxID"' | grep -oP ':\s*"\K[^"]+')
echo "Pod Sandbox ID: $SANDBOX_ID"
# 4. 杀死 containerd-shim 进程
if [ -n "$SANDBOX_ID" ]; then
echo ""
echo "=== 步骤 4: 清理 containerd-shim ==="
SHIM_PIDS=$(ps aux | grep containerd-shim | grep "$SANDBOX_ID" | grep -v grep | awk '{print $2}')
if [ -n "$SHIM_PIDS" ]; then
echo "找到 containerd-shim 进程: $SHIM_PIDS"
echo "$SHIM_PIDS" | xargs -r kill -9 2>/dev/null || true
sleep 2
fi
fi
# 5. 停止 containerd
echo ""
echo "=== 步骤 5: 停止 containerd ==="
systemctl stop containerd
sleep 5
# 6. 清理状态文件
echo ""
echo "=== 步骤 6: 清理容器元数据 ==="
if [ -n "$SANDBOX_ID" ]; then
echo "清理 Sandbox 相关文件..."
rm -rf /run/containerd/runc/k8s.io/"$SANDBOX_ID"* 2>/dev/null || true
rm -rf /run/containerd/io.containerd.grpc.v1.cri/sandboxes/"$SANDBOX_ID"* 2>/dev/null || true
fi
echo "清理容器相关文件..."
rm -rf /run/containerd/io.containerd.runtime.v2.task/k8s.io/"$OLD_CONTAINER_ID"* 2>/dev/null || true
rm -rf /run/containerd/io.containerd.grpc.v1.cri/containers/"$OLD_CONTAINER_ID"* 2>/dev/null || true
# 7. 重启 containerd
echo ""
echo "=== 步骤 7: 重启 containerd ==="
systemctl start containerd
echo "等待 30 秒让 containerd 完全启动..."
sleep 30
# 8. 验证结果
echo ""
echo "=== 步骤 8: 验证清理结果 ==="
echo "当前容器列表:"
crictl ps 2>/dev/null | grep -v "CONTAINER" | head -5
REMAINING=$(crictl ps 2>/dev/null | grep "$OLD_CONTAINER_ID" || true)
if [ -z "$REMAINING" ]; then
echo ""
echo "✓ 僵尸容器已成功清理!"
else
echo ""
echo "✗ 警告:容器仍然存在,可能需要重启节点"
fi
echo ""
echo "=========================================="
echo "清理完成!"
echo "=========================================="
使用步骤
步骤 1: 识别僵尸容器
在每个节点上执行:
# 查找旧容器(注意 "months ago")
crictl ps | grep <app-name>
# 记录旧容器的 ID(通常是前几个字符就够了)
# 示例输出:
# c6cfbe677343b emqx:5.8.6 4 months ago Running emqx ...
步骤 2: 执行清理
# 赋予脚本执行权限
chmod +x cleanup-zombie-containers.sh
# 执行清理(替换为实际的容器 ID)
sudo ./cleanup-zombie-containers.sh c6cfbe677343b
步骤 3: 验证清理结果
# 检查容器列表(不应该有 "months ago" 的容器)
crictl ps | grep <app-name>
# 检查进程
ps aux | grep <app-name> | grep -v grep
# 检查是否还有旧进程
ps aux | grep May | grep <app-name>
步骤 4: 在所有受影响节点重复
对集群中所有有僵尸容器的节点重复上述步骤。
步骤 5: 验证 Kubernetes 状态
回到 master 节点执行:
# 检查 Pod 状态(应该都在 Running)
kubectl get pods -n <namespace> -o wide
# 等待 3-5 分钟让 Pod 完全启动
sleep 180
# 检查 Pod 是否全部就绪
kubectl get pods -n <namespace>
# 检查是否还有错误事件
kubectl get events -n <namespace> --sort-by='.lastTimestamp' | grep -i failed | tail -10
# 检查资源使用(应该恢复正常)
kubectl top pods -n <namespace>
快速清理命令(紧急情况)
如果脚本无法使用,可以手动执行以下命令:
# 1. 找到旧容器 ID
OLD_CONTAINER_ID="c6cfbe677343b" # 替换为实际的容器 ID
# 2. 杀死容器进程
PID=$(crictl inspect $OLD_CONTAINER_ID 2>/dev/null | grep '"pid"' | head -1 | grep -oP ':\s*\K\d+')
[ -n "$PID" ] && kill -9 $PID
# 3. 杀死旧应用进程
ps aux | grep <app-name> | grep May | awk '{print $2}' | xargs -r kill -9
# 4. 找到 Sandbox ID 并杀死 shim
SANDBOX_ID=$(crictl inspect $OLD_CONTAINER_ID 2>/dev/null | grep '"sandboxID"' | grep -oP ':\s*"\K[^"]+')
ps aux | grep containerd-shim | grep "$SANDBOX_ID" | awk '{print $2}' | xargs -r kill -9
# 5. 停止 containerd
systemctl stop containerd
sleep 5
# 6. 清理元数据
rm -rf /run/containerd/runc/k8s.io/${SANDBOX_ID}*
rm -rf /run/containerd/io.containerd.runtime.v2.task/k8s.io/${OLD_CONTAINER_ID}*
rm -rf /run/containerd/io.containerd.grpc.v1.cri/sandboxes/${SANDBOX_ID}*
rm -rf /run/containerd/io.containerd.grpc.v1.cri/containers/${OLD_CONTAINER_ID}*
# 7. 重启 containerd
systemctl start containerd
sleep 30
# 8. 验证
crictl ps | grep <app-name>
终极方案:重启节点
如果上述方法都无法清理僵尸容器,可以重启节点:
# 在 master 节点执行
# 1. 标记节点为不可调度
kubectl cordon <node-name>
# 2. 驱逐节点上的 Pod
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data --force --grace-period=0
# 3. 在目标节点上重启
reboot
# 4. 重启后,恢复节点调度
kubectl uncordon <node-name>
# 5. 验证节点状态
kubectl get nodes
kubectl get pods -n <namespace> -o wide
预防措施
1. 调整 Kubelet 超时配置
编辑 /var/lib/kubelet/config.yaml:
runtimeRequestTimeout: 5m # 从默认 2m 增加到 5m
重启 kubelet:
systemctl restart kubelet
2. 配置应用优雅关闭
在部署配置中添加:
spec:
template:
spec:
terminationGracePeriodSeconds: 120 # 给应用足够时间关闭
containers:
- name: <container-name>
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- |
# 应用特定的关闭命令
<app-stop-command>
sleep 10
3. 合理配置资源限制
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "4"
memory: "8Gi"
避免设置过高的资源限制导致节点内存压力过大。
4. 避免频繁 apply/delete
# ✗ 不推荐
kubectl delete -f app.yaml
kubectl apply -f app.yaml
# ✓ 推荐使用滚动更新
kubectl rollout restart deployment/<deployment-name>
# ✓ 或使用 patch
kubectl patch deployment <deployment-name> -p '{"spec":{"template":{"metadata":{"annotations":{"restarted-at":"'$(date +%s)'"}}}}}'
5. 监控和告警
设置监控告警:
- 节点内存使用率 > 80%
- Pod 重启次数异常
- 出现
FailedKillPod事件
常见问题 (FAQ)
Q1: 为什么会出现僵尸容器?
原因:
- 频繁执行
kubectl apply/delete导致容器无法正常终止 - 应用关闭时间过长,超过 terminationGracePeriod
- 系统资源不足(内存、磁盘)
- Containerd/Docker 运行时故障
Q2: 清理僵尸容器会影响正在运行的服务吗?
影响:
- ✅ 清理旧容器不影响新容器
- ⚠️ 重启 containerd 会导致节点上所有容器短暂重启(30-60秒)
- ✅ Kubernetes 会自动恢复 Pod
- ⚠️ 建议在低峰期操作
Q3: 如何确认容器是"僵尸容器"?
判断标准:
- 创建时间很久(几个月前)
- 对应的 Pod 状态异常或已不存在
- 无法通过
crictl stop/rm删除 - 进程处于僵尸状态(Z 或 defunct)
Q4: 清理后 Pod 一直重启怎么办?
排查步骤:
- 检查应用日志:
kubectl logs <pod-name> -n <namespace> - 检查节点资源:
kubectl top nodes - 检查存储卷:PVC 是否正常挂载
- 检查网络:Pod IP 是否分配正常
Q5: metrics-server 不可用怎么办?
解决方法:
# 重启 metrics-server
kubectl rollout restart deployment metrics-server -n kube-system
# 等待恢复
kubectl get pods -n kube-system | grep metrics-server
总结
关键步骤回顾
- 诊断:识别僵尸容器和异常进程
- 清理:杀死进程 → 停止 containerd → 清理元数据 → 重启 containerd
- 验证:确认容器和进程已清理,Pod 恢复正常
- 预防:调整配置,避免再次发生
操作检查清单
- 备份重要数据和配置
- 识别所有受影响的节点
- 记录当前 Pod 状态
- 逐节点执行清理(不要同时清理所有节点)
- 验证每个节点清理成功
- 检查 Pod 恢复状态
- 验证应用功能正常
- 配置预防措施
注意事项
⚠️ 重要警告:
- 清理操作需要 root 权限
- 建议在低峰期执行
- 一次只清理一个节点
- 确保有完整备份
- 操作前通知相关团队
附录
A. 相关命令速查
# 容器管理
crictl ps # 列出容器
crictl inspect <container-id> # 查看容器详情
crictl stop <container-id> # 停止容器
crictl rm <container-id> # 删除容器
# Pod 管理
kubectl get pods -n <ns> -o wide # 查看 Pod
kubectl describe pod <pod> -n <ns> # Pod 详情
kubectl logs <pod> -n <ns> # 查看日志
kubectl delete pod <pod> -n <ns> --force --grace-period=0 # 强制删除
# 节点管理
kubectl cordon <node> # 标记不可调度
kubectl drain <node> # 驱逐 Pod
kubectl uncordon <node> # 恢复调度
# 系统服务
systemctl status containerd # 查看状态
systemctl restart containerd # 重启服务
journalctl -u containerd -n 100 # 查看日志
B. 日志位置
# Containerd 日志
/var/log/containerd.log
journalctl -u containerd
# Kubelet 日志
/var/log/kubelet.log
journalctl -u kubelet
# Pod 日志
/var/log/pods/<namespace>_<pod-name>_<uid>/<container-name>/
更多推荐



所有评论(0)