K8s 节点镜像垃圾回收 (GC) 失败故障知识库
故障概述
故障现象
在 Kubernetes 集群日常巡检或监控告警中,发现节点频繁出现以下警告事件:
- Event Type:
Warning - Reason:
ImageGCFailed - Message:
unable to remove repository reference "xxx" (must force) - container xxx is using its referenced image xxx - 伴随现象: 节点状态在
NodeHasNoDiskPressure和NodeHasDiskPressure之间反复切换
同时可能观察到:
- 节点磁盘使用率持续高于 85%
- 新 Pod 调度到该节点后无法拉取镜像
- 极端情况下触发 Pod 驱逐(Eviction)
故障等级
P2 - 重要告警
虽然此类故障通常不会直接导致业务中断(因为已有容器仍在正常运行),但存在以下风险:
- 磁盘空间持续紧张,可能影响节点稳定性
- 新业务部署或扩缩容时镜像拉取失败
- 长期不处理可能演变为 P1 级故障(如触发大规模 Pod 驱逐)
影响范围
- 直接影响: 单节点磁盘空间无法有效释放
- 潜在影响:
- 该节点上新 Pod 的创建和调度
- 可能触发 kubelet 的主动驱逐机制
- 集群整体资源利用率下降
报错原理深度分析
一、Kubernetes 镜像 GC 机制详解
1.1 为什么需要镜像 GC?
在 Kubernetes 集群中,节点会不断拉取新的容器镜像。随着时间推移:
- 旧版本应用镜像不再使用
- 测试镜像被废弃
- 镜像标签更新产生历史版本
如果不进行清理,这些"悬空"镜像会持续占用磁盘空间,最终导致节点磁盘耗尽。为此,kubelet 内置了镜像垃圾回收(Garbage Collection)机制。
1.2 GC 触发条件
kubelet 通过两个阈值控制镜像 GC 行为:
| 参数名 | 默认值 | 含义 |
|---|---|---|
imageGCHighThresholdPercent | 85% | 当磁盘使用率超过此阈值时,开始执行 GC |
imageGCLowThresholdPercent | 80% | GC 的目标是将磁盘使用率降至该阈值以下 |
工作流程说明:
正常状态:磁盘使用率 < 85%
↓
某时刻磁盘使用率达到 86%(超过高阈值)
↓
kubelet 检测到条件满足,触发镜像 GC 流程
↓
GC 开始扫描并识别"可删除"的镜像
↓
删除未被任何容器引用的镜像
↓
持续检查磁盘使用率
↓
当磁盘使用率降至 80% 以下(低阈值),GC 暂停
↓
等待下一次触发1.3 什么样的镜像会被 GC 删除?
kubelet 判断镜像是否可以删除的逻辑是:
核心原则: 只有未被任何容器引用的镜像才会被 GC 清理
具体判断标准:
- ✅ 可删除: 没有任何容器(包括运行中和已停止的)引用该镜像
- ❌ 不可删除: 至少有一个容器正在使用该镜像(即使 Pod 已经删除,但容器未清理)
这里的"引用"是指:容器的根文件系统基于该镜像创建,Docker 层面存在依赖关系。
二、GC 失败的常见原因剖析
根据实际运维经验,镜像 GC 失败主要有以下几种场景:
2.1 场景一:镜像被运行中的容器引用(本次故障类型)
典型错误信息:
ImageGCFailed: unable to remove repository reference
"dkr-registry:5000/chrony:v3.5" (must force) -
container 7e9f6007bbee is using its referenced image 15d4fd598132详细原理解读:
这种情况通常发生在以下场景:
- Pod 已删除但容器残留
- 用户执行
kubectl delete pod后,Pod 对象被移除 - 但由于某种原因(如节点网络分区、kubelet 异常等),对应的 Docker 容器未被清理
- 该容器仍然持有原镜像的引用锁
- 用户执行
- 长周期运行的系统容器
- 某些系统组件(如 chrony、node-exporter 等)以 DaemonSet 形式运行
- 升级后旧版本容器未及时清理
- 新旧容器共存,旧容器仍占用旧镜像
- 容器异常僵死
- 容器进程异常但未完全退出
- 处于
Dead或Zombie状态 - Docker 无法正常回收其资源
为什么不能强制删除?
从技术角度,Docker 确实提供了 docker rmi -f 强制删除选项。但在 Kubernetes 环境中,强烈不建议这样做,原因是:
- 强制删除可能导致运行中容器文件系统损坏
- 容器可能进入不可预测的状态
- 最坏情况下需要重启整个节点才能恢复
因此,正确的做法是先清理容器,再让 GC 自动回收镜像。
2.2 场景二:多个镜像标签指向同一镜像层
现象描述:
$ docker images
REPOSITORY TAG IMAGE ID SIZE
dkr-registry:5000/chrony v3.5 15d4fd598132 50MB
dkr-registry:5000/chrony latest 15d4fd598132 50MB可以看到,v3.5 和 latest 两个标签实际上指向同一个镜像 ID(15d4fd598132)。
问题分析:
- Docker 的镜像存储是分层的,多个标签可以引用同一组镜像层
- 删除其中一个标签(如
v3.5)并不会释放磁盘空间 - 只有当所有标签都被删除,且没有容器引用时,镜像层才会被真正清理
排查方法:
# 查看镜像的所有标签
docker inspect 15d4fd598132 | jq '.[0].RepoTags'2.3 场景三:镜像层被多个镜像共享
现代容器镜像采用分层存储机制,基础镜像层(如 ubuntu:20.04)可能被数十个应用镜像共享。
问题表现:
- 删除了某个应用镜像
- 但磁盘空间几乎没有变化
原因:
- 该镜像的基础层仍被其他镜像引用
- Docker 只会删除独占的层,共享层会保留
这种情况严格来说不算"GC 失败",而是符合预期的行为。但如果运维人员不了解这一机制,可能会误以为 GC 没有工作。
三、磁盘压力连锁反应机制
理解磁盘压力如何一步步影响集群稳定性,对于制定应对策略至关重要。
3.1 连锁反应流程图
┌─────────────────────────────────────────────────────────────┐
│ 阶段 1: 磁盘使用率逐渐上升 │
│ - 新镜像不断拉取 │
│ - 旧镜像未被清理 │
│ - 容器日志累积 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 阶段 2: 触发 GC 高阈值 (>85%) │
│ - kubelet 检测到磁盘使用率超标 │
│ - 启动镜像 GC 流程 │
│ - 尝试删除未引用的镜像 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 阶段 3: GC 失败(本故障的核心) │
│ - 发现目标镜像被容器引用 │
│ - 无法安全删除,返回错误 │
│ - 磁盘空间未能释放 │
│ - 错误事件被记录并上报告警 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 阶段 4: 磁盘压力状态激活 │
│ - 磁盘使用率持续高于阈值 │
│ - kubelet 将节点状态标记为 DiskPressure │
│ - 节点状态同步到 API Server │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 阶段 5: 调度器感知并规避 │
│ - 调度器看到节点 DiskPressure=True │
│ - 不再将新 Pod 调度到该节点 │
│ - 集群有效容量下降 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 阶段 6 (最坏情况): 触发 Pod 驱逐 │
│ - 磁盘使用率继续上升至更高级别阈值 │
│ - kubelet 开始主动驱逐 Pod(通常是 QoS 较低的) │
│ - 业务受到影响 │
└─────────────────────────────────────────────────────────────┘3.2 关键观察点
从上述流程可以看出,阶段 3(GC 失败)是干预的最佳时机:
- 此时业务尚未受影响
- 有充足的时间进行诊断和处理
- 操作风险相对较低
一旦进入阶段 5 或 6,就需要紧急响应,且可能需要牺牲部分业务来恢复节点健康。
完整排查步骤(实战版)
下面是一套经过生产环境验证的标准化排查流程。建议按顺序执行,每一步都确认清楚后再进入下一步。
Step 1: 确认 Docker 数据目录位置
⚠️ 重要提醒:很多运维同学习惯性地直接执行 df -h /var/lib/docker,这是错误的做法!
Docker 的数据目录可以通过配置文件自定义,不同环境的配置可能不同。盲目假设会导致误判。
1.1 查看 Docker 配置文件
Docker 的主配置文件位于 /etc/docker/daemon.json,其中 data-root(旧版本叫 graph)参数指定了数据目录位置。
# 查看配置文件内容
cat /etc/docker/daemon.json典型配置示例:
{
"registry-mirrors": ["https://docker.mirror.example.com"],
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
},
"data-root": "/data/docker"
}注意最后一行 "data-root": "/data/docker",这说明 Docker 的数据实际存储在 /data/docker,而不是默认的 /var/lib/docker。
1.2 如果配置文件不存在或为空
有些环境可能没有 daemon.json 文件,此时 Docker 使用默认配置,数据目录为 /var/lib/docker。
可以用以下命令确认:
# 查看 Docker 服务启动参数
systemctl show docker | grep ExecStart
# 或者使用 docker info 查看
docker info | grep "Docker Root Dir"输出示例:
Docker Root Dir: /data/docker1.3 确认真实的数据目录路径
综合以上信息,我们才能确定要检查的磁盘分区。假设我们得到的是 /data/docker,那么后续所有操作都要基于这个路径。
Step 2: 评估磁盘使用情况
确认真实的数据目录后,现在可以检查磁盘使用状况了。
2.1 查看磁盘分区和使用率
# 假设数据目录是 /data/docker
df -h /data/docker输出示例:
Filesystem Size Used Avail Use% Mounted on
/dev/sdb1 500G 450G 50G 90% /data关键指标解读:
Use%达到 90%,已经超过 GC 高阈值(85%),这解释了为什么会触发 GCAvail只剩 50G,确实比较紧张
2.2 分析 Docker 各组件空间占用
# 查看 Docker 各类型资源的总体占用
docker system df输出示例:
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 156 45 120GB 80GB (66%)
Containers 89 45 2.5GB 1.2GB (48%)
Local Volumes 23 12 15GB 8GB (53%)
Build Cache - - 5.2GB 5.2GB分析要点:
- Images 行显示有 120GB 镜像,其中 80GB 理论上可回收(但可能因为本次故障无法实际回收)
- Containers 行显示有 44 个非活跃容器(89-45),占用 1.2GB,这部分是可以安全清理的
- Local Volumes 也有 8GB 可回收
2.3 深入分析各目录占用
# 查看 Docker 数据目录下各子目录的大小
du -sh /data/docker/*
# 或者更详细的排序
du -ah /data/docker | sort -rh | head -20常见目录说明:
containers/: 容器相关数据image/: 镜像元数据和层volumes/: 数据卷overlay2/(或其他存储驱动目录): 实际的文件系统层
Step 3: 定位问题容器
从 Step 1 收集到的告警事件中,我们已经知道了关键信息:
- 问题镜像:
dkr-registry:5000/chrony:v3.5 - 占用容器 ID:
7e9f6007bbee
现在需要深入了解这个容器的情况。
3.1 查看容器基本信息
CONTAINER_ID="7e9f6007bbee"
# 查看容器详细信息
docker inspect $CONTAINER_ID重点关注以下字段:
{
"Config": {
"Image": "dkr-registry:5000/chrony:v3.5",
"Labels": {
"io.kubernetes.container.name": "chrony",
"io.kubernetes.pod.name": "chrony-daemonset-abc123",
"io.kubernetes.pod.namespace": "kube-system"
}
},
"State": {
"Status": "running",
"Running": true,
"StartedAt": "2026-02-15T08:30:00Z"
},
"Created": "2026-02-15T08:29:55Z"
}关键信息提取:
- 容器所属 Pod:
chrony-daemonset-abc123 - 命名空间:
kube-system - 容器名称:
chrony - 运行状态:
running(正在运行) - 启动时间:2 月 15 日,已运行约 25 天
3.2 查找对应的 Kubernetes Pod
# 根据 Pod 名称查找
kubectl get pod chrony-daemonset-abc123 -n kube-system -o wide
# 或者通过标签搜索
kubectl get pods -n kube-system -l app=chrony -o wide可能的情况:
情况 A:Pod 仍然存在
- 说明这是当前正在运行的合法 Pod
- 不能简单删除容器,需要考虑其他方式
情况 B:Pod 已被删除
- 说明这是一个"孤儿容器"
- 可以安全删除
3.3 评估容器的重要性
chrony 是时间同步服务,通常是集群基础设施的一部分。在决定如何处理之前,需要确认:
# 查看容器日志
docker logs --tail 100 $CONTAINER_ID
# 查看容器资源使用
docker stats $CONTAINER_ID --no-stream
# 检查是否有其他 chrony 实例在运行
kubectl get pods -n kube-system -l app=chrony如果发现有多个 chrony Pod 在其他节点正常运行,且该容器的 Pod 已经被新版本的 Pod 替代,那么可以安全清理。
Step 4: 检查镜像引用关系
为了全面理解问题,还需要分析问题镜像的引用情况。
# 查看所有包含 chrony 的镜像
docker images | grep chrony
# 查看特定镜像的详细信息
docker inspect dkr-registry:5000/chrony:v3.5 | jq '.[0].RepoTags'如果输出显示多个标签:
[
"dkr-registry:5000/chrony:v3.5",
"dkr-registry:5000/chrony:latest"
]说明这两个标签指向同一个镜像,只删除一个标签不会释放空间。
解决方案(生产环境验证版)
根据实际情况和风险评估,提供以下几种解决方案。请严格按照推荐顺序考虑。
方案一:清理磁盘空间(首选方案)⭐
这是最安全、最推荐的方案。核心思路是:不直接处理问题镜像,而是清理其他可释放的空间,让磁盘使用率降到 GC 阈值以下。
适用场景
- 问题容器是重要的系统组件,不能随意删除
- 业务高峰期,需要最小化风险
- 有其他可清理的空间(如旧日志、悬空卷等)
操作步骤
1. 清理已停止的容器
这些容器已经不再运行,但它们的历史数据仍占用空间。
# 查看所有已停止的容器
docker ps -a | grep Exit
# 统计数量
docker ps -a | grep Exit | wc -l
# 删除所有已停止的容器
docker container prune -f
# 验证
docker ps -a | wc -l预期效果:根据 Step 2 的分析,这一步可能释放 1-2GB 空间。
2. 清理悬空镜像(dangling images)
悬空镜像是指没有被任何容器引用、也没有标签指向的镜像层。
# 查看悬空镜像
docker images -f dangling=true
# 删除悬空镜像
docker image prune -f注意:这不会删除有标签的镜像,只清理无主的镜像层。
3. 清理未使用的数据卷(谨慎操作)
数据卷可能包含持久化数据,清理前务必确认!
# 查看未使用的卷(没有被任何容器挂载)
docker volume ls -f dangling=true
# 逐个检查后再删除
for vol in $(docker volume ls -f dangling=true -q); do
echo "=== Volume: $vol ==="
docker volume inspect $vol | jq '.[0].Mountpoint'
# 确认无误后再删除
# docker volume rm $vol
done
# 确认安全后批量删除
docker volume prune -f⚠️ 警告:某些应用可能将重要数据存放在卷中,即使容器已删除。务必先检查卷的内容!
4. 清理容器日志
容器日志文件可能非常大,特别是没有配置日志轮转的环境。
# 查找大的日志文件
find /data/docker/containers -name "*.log" -size +100M
# 查看具体是哪个容器的日志
ls -lh /data/docker/containers/<container_id>/<container_id>-json.log
# 清空日志(不要删除文件,而是清空内容)
truncate -s 0 /data/docker/containers/<container_id>/<container_id>-json.log更好的做法:配置 Docker 日志轮转,防止未来再次出现此问题。
编辑 /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}然后重启 Docker:
systemctl restart docker5. 清理构建缓存(如果有)
如果节点上执行过 docker build,可能会有构建缓存。
# 查看构建缓存
docker builder df
# 清理构建缓存
docker builder prune -f6. 验证磁盘空间释放
# 重新检查磁盘使用率
df -h /data/docker
# 检查是否仍高于阈值
# 理想情况:降至 80% 以下如果磁盘使用率成功降至 80% 以下,kubelet 会自动停止 GC,告警也会消失。
方案二:磁盘扩容(根本解决方案)⭐⭐
如果清理后空间仍然紧张,或者预计未来业务增长会持续消耗空间,建议进行磁盘扩容。这是从根本上解决问题的方案。
适用场景
- 清理后可用空间仍然不足
- 业务持续增长,磁盘需求不断增加
- 当前磁盘规划不合理(如初始分配过小)
扩容方式选择
方式 A:在线扩容(推荐,业务无感知)
如果底层存储支持(如云盘、LVM),可以在线扩容。
云环境示例(以百度云为例):
- 在云平台控制台扩大云盘容量
- 登录节点扩展文件系统
# 查看磁盘变化
lsblk
# 假设 /dev/vdb 从 500G 扩展到 1000G
# 扩展分区(如果需要)
growpart /dev/vdb 1
# 扩展文件系统(根据文件系统类型选择命令)
# ext4
resize2fs /dev/vdb1
# xfs
xfs_growfs /data验证:
df -h /data/docker方式 B:添加新磁盘并迁移
如果无法在线扩容,可以添加新磁盘并迁移 Docker 数据目录。
步骤:
- 添加新磁盘并挂载
# 假设新磁盘为 /dev/vdc
mkfs.xfs /dev/vdc
mount /dev/vdc /data2- 停止 Docker 服务
systemctl stop docker- 迁移数据
rsync -avz /data/docker/ /data2/docker/- 修改 Docker 配置
编辑 /etc/docker/daemon.json:
{
"data-root": "/data2/docker"
}- 启动 Docker 并验证
systemctl start docker
docker info | grep "Docker Root Dir"
docker ps # 确认所有容器正常启动- (可选)清理旧数据
确认一切正常后,可以卸载并清理旧磁盘。
方案三:调整 GC 阈值(临时缓解方案)
如果短期内无法扩容,且可清理空间有限,可以调整 GC 阈值,让 kubelet 更积极地回收空间。
适用场景
- 磁盘空间确实紧张,但暂时无法扩容
- 作为临时措施,为扩容争取时间
- 节点上镜像更新频繁,需要更积极的 GC
操作步骤
1. 查看当前 kubelet 配置
kubelet 的配置方式因安装方式而异:
方式 A:通过配置文件
# 查找 kubelet 配置文件
find /etc/kubernetes -name "kubelet-config.yaml"
# 或
find /etc/kubernetes -name "config.yaml"方式 B:通过 systemd 配置
# 查看 kubelet 启动参数
systemctl cat kubelet方式 C:通过 KubeletConfiguration CR(K8s 1.11+)
kubectl get kubeletconfiguration -o yaml2. 修改 GC 阈值
在配置文件中找到或添加以下参数:
imageGCHighThresholdPercent: 80
imageGCLowThresholdPercent: 70或者如果是命令行参数形式:
--image-gc-high-threshold=80 --image-gc-low-threshold=70参数说明:
imageGCHighThresholdPercent: 80:磁盘使用率达到 80% 就开始 GC(原来是 85%)imageGCLowThresholdPercent: 70:GC 目标是降到 70% 以下(原来是 80%)
3. 重启 kubelet
# 重新加载配置
systemctl daemon-reload
# 重启 kubelet
systemctl restart kubelet
# 验证状态
systemctl status kubelet4. 观察效果
# 查看节点事件
kubectl describe node <node-name> | grep -A 5 "Events"
# 监控磁盘使用率变化
watch -n 5 'df -h /data/docker'⚠️ 注意事项
- GC 频率增加可能影响性能
- 更频繁的 GC 会消耗更多 CPU 和 I/O
- 在镜像拉取频繁的环境中尤为明显
- 这不是长期解决方案
- 只是延缓问题爆发的时间
- 根本解决还是需要扩容或清理
- 建议在低峰期操作
- 避免在业务高峰期调整
- 给 kubelet 一些时间适应新配置
预防措施与最佳实践
一、建立定期清理机制
1. 部署自动化清理脚本
创建脚本 /usr/local/bin/k8s-node-cleanup.sh:
#!/bin/bash
#
# K8s 节点定期清理脚本
# 功能:清理已停止容器、悬空镜像、旧日志
#
set -e
LOG_FILE="/var/log/k8s-cleanup.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
log() {
echo "[$TIMESTAMP] $1" | tee -a $LOG_FILE
}
log "=== 开始节点清理 ==="
# 1. 清理已停止的容器
STOPPED_COUNT=$(docker ps -a | grep Exit | wc -l)
if [ $STOPPED_COUNT -gt 0 ]; then
log "发现 $STOPPED_COUNT 个已停止的容器,开始清理..."
docker container prune -f
log "已停止容器清理完成"
else
log "无需清理已停止容器"
fi
# 2. 清理悬空镜像
DANGLING_COUNT=$(docker images -f dangling=true -q | wc -l)
if [ $DANGLING_COUNT -gt 0 ]; then
log "发现 $DANGLING_COUNT 个悬空镜像,开始清理..."
docker image prune -f
log "悬空镜像清理完成"
else
log "无需清理悬空镜像"
fi
# 3. 清理构建缓存
BUILD_CACHE=$(docker builder du | tail -1)
log "构建缓存占用:$BUILD_CACHE"
docker builder prune -f
log "构建缓存清理完成"
# 4. 检查磁盘使用率
DISK_USAGE=$(df /data/docker | tail -1 | awk '{print $5}')
log "当前磁盘使用率:$DISK_USAGE"
# 5. 记录清理结果
log "=== 清理完成 ==="
log ""
# 如果磁盘使用率仍然很高,发送告警
THRESHOLD=80
USAGE_NUM=${DISK_USAGE%\%}
if [ $USAGE_NUM -gt $THRESHOLD ]; then
log "⚠️ 警告:磁盘使用率 ${DISK_USAGE} 仍高于阈值 ${THRESHOLD}%"
# 这里可以集成告警通知,如发送如流消息、邮件等
fi赋予执行权限:
chmod +x /usr/local/bin/k8s-node-cleanup.sh2. 配置定时任务
编辑 crontab:
crontab -e添加以下内容(每周日凌晨 2 点执行):
0 2 * * 0 /usr/local/bin/k8s-node-cleanup.sh3. 监控清理效果
可以在 Grafana 中创建仪表板,跟踪以下指标:
- 节点磁盘使用率趋势
- 镜像数量变化
- 容器数量变化
- 清理脚本执行日志
二、优化镜像管理策略
1. 使用具体的版本号标签
❌ 不推荐:
docker pull dkr-registry:5000/myapp:latest✅ 推荐:
docker pull dkr-registry:5000/myapp:v1.2.3原因:
latest标签会不断指向新版本,旧版本的镜像成为"悬空"状态- 具体版本号便于追溯和回滚
- 更容易实施镜像保留策略
2. 实施镜像保留策略
对于 CI/CD 流水线构建的镜像,建议:
- 只保留最近 N 个版本(如最近 5 个)
- 定期清理超过一定时间的旧版本
- 使用镜像仓库的生命周期管理功能
示例(Harbor 仓库):
- 配置保留策略:保留最近 10 个 Tag
- 启用自动清理:每天凌晨清理过期镜像
3. 多环境镜像隔离
- 开发环境镜像使用独立仓库或命名空间
- 生产环境镜像严格管控,禁止随意推送
- 定期审计各环境镜像使用情况
三、完善监控告警体系
1. Prometheus 告警规则
在 Prometheus 中添加以下告警规则:
groups:
- name: k8s-node-storage
rules:
# 磁盘使用率告警
- alert: NodeDiskUsageHigh
expr: (node_filesystem_size_bytes{mountpoint="/data"} - node_filesystem_avail_bytes{mountpoint="/data"}) / node_filesystem_size_bytes{mountpoint="/data"} * 100 > 80
for: 5m
labels:
severity: warning
annotations:
summary: "节点 {{ $labels.instance }} 磁盘使用率过高"
description: "磁盘使用率 {{ $value }}%,已超过 80% 阈值"
- alert: NodeDiskUsageCritical
expr: (node_filesystem_size_bytes{mountpoint="/data"} - node_filesystem_avail_bytes{mountpoint="/data"}) / node_filesystem_size_bytes{mountpoint="/data"} * 100 > 90
for: 2m
labels:
severity: critical
annotations:
summary: "节点 {{ $labels.instance }} 磁盘使用率严重过高"
description: "磁盘使用率 {{ $value }}%,已超过 90% 临界值"
# 镜像 GC 失败告警
- alert: KubeletImageGCFailed
expr: increase(kubelet_image_gc_failed_total[5m]) > 5
for: 5m
labels:
severity: warning
annotations:
summary: "节点 {{ $labels.node }} 镜像 GC 频繁失败"
description: "过去 5 分钟内发生 {{ $value }} 次 GC 失败"
# 节点磁盘压力状态告警
- alert: NodeHasDiskPressure
expr: kube_node_status_condition{condition="DiskPressure",status="true"} == 1
for: 5m
labels:
severity: warning
annotations:
summary: "节点 {{ $labels.node }} 处于磁盘压力状态"
description: "节点可能开始驱逐 Pod"2. 告警通知配置
根据告警级别配置不同的通知渠道:
| 告警级别 | 通知方式 | 通知对象 | 响应时间要求 |
|---|---|---|---|
| Warning | 如流 + 邮件 | 值班人员 | 1 小时内 |
| Critical | 电话 + 如流 + 邮件 | 值班 + 负责人 | 15 分钟内 |
四、容量规划与预测
1. 建立容量基线
定期(如每月)统计以下数据:
| 指标 | 当前值 | 警戒线 | 行动线 |
|---|---|---|---|
| 磁盘使用率 | - | 70% | 80% |
| 镜像总数 | - | 100 个 | 150 个 |
| 容器总数 | - | 50 个 | 80 个 |
| 停止容器数 | - | 10 个 | 30 个 |
| 数据卷数量 | - | 20 个 | 40 个 |
2. 趋势分析与预测
基于历史数据,预测未来容量需求:
# 示例:计算过去 30 天磁盘增长趋势
# 假设有监控系统可以提供历史数据
# 平均每日增长 = (当前使用量 - 30 天前使用量) / 30
# 预计耗尽天数 = (总容量 - 当前使用量) / 平均每日增长如果预计 3 个月内会达到警戒线,应提前规划扩容。
3. 制定扩容计划
根据业务发展规划:
- 新业务上线带来的镜像增长
- 现有业务的自然增长
- 季节性波动(如大促期间)
提前预留 30-50% 的缓冲空间。
故障复盘模板
故障基本信息
| 项目 | 内容 |
|---|---|
| 故障时间 | 2026-03-11 17:00 - 17:30 |
| 故障等级 | P2 |
| 影响节点 | p0-lkg-data14 |
| 发现方式 | 监控告警 |
| 处理人 | 历飞雨 |
故障时间线
| 时间 | 事件 | 备注 |
|---|---|---|
| 17:00 | 监控系统触发 ImageGCFailed 告警 | 首次告警 |
| 17:05 | 值班人员确认告警,开始排查 | - |
| 17:10 | 定位为容器占用镜像导致 GC 失败 | 容器 ID: 7e9f6007bbee |
| 17:15 | 确认该容器为 chrony 系统组件 | 不能直接删除 |
| 17:20 | 执行磁盘清理操作 | 清理已停止容器和悬空镜像 |
| 17:25 | 磁盘使用率降至 78% | GC 恢复正常 |
| 17:30 | 告警消除,故障恢复 | - |
根因分析
直接原因
节点上存在一个运行中的 chrony 容器(ID: 7e9f6007bbee),该容器引用了旧版本镜像 dkr-registry:5000/chrony:v3.5。当磁盘使用率超过 85% 触发 GC 时,由于该镜像被容器引用而无法删除,导致 GC 失败。
根本原因
- 缺乏定期清理机制:节点上没有部署自动化清理脚本,已停止的容器和悬空镜像长期累积
- 监控告警不完善:缺少对磁盘使用率趋势的预警,等到触发 GC 失败时才发现问题
- 镜像管理不规范:chrony DaemonSet 升级后,旧版本容器未被及时清理
影响评估
- 业务影响:无直接业务影响
- 潜在风险:如不及时处理,可能触发 Pod 驱逐
- 持续时间:30 分钟
- 影响范围:单节点
改进措施
| 序号 | 措施 | 负责人 | 预计完成时间 | 状态 |
|---|---|---|---|---|
| 1 | 部署节点定期清理脚本 | 陈欢 | 2026-03-15 | 待办 |
| 2 | 优化 Prometheus 告警规则 | 陈欢 | 2026-03-12 | 待办 |
| 3 | 更新 chrony DaemonSet 配置 | 陈欢 | 2026-03-12 | 待办 |
| 4 | 制定镜像管理规范 | 运维团队 | 2026-03-20 | 待办 |
| 5 | 建立容量规划机制 | 运维团队 | 2026-03-25 | 待办 |
经验教训
- 预防胜于治疗:定期清理比故障后紧急处理更安全、成本更低
- 监控要前置:不要等到 GC 失败才告警,应该在磁盘使用率达到 70% 时就预警
- 自动化是关键:人工清理容易遗漏,应该用脚本固化最佳实践
- 文档要及时更新:将本次故障的处理过程整理成知识库,方便团队学习
附录:常用命令速查表
节点状态查询
# 查看节点详细信息
kubectl describe node <node-name>
# 查看节点事件
kubectl get events --field-selector involvedObject.name=<node-name> --sort-by='.lastTimestamp'
# 查看节点状态条件
kubectl get node <node-name> -o jsonpath='{.status.conditions}' | jqDocker 空间管理
# 查看 Docker 系统总览
docker system df
# 查看详细空间使用
docker system df -v
# 清理已停止容器
docker container prune -f
# 清理悬空镜像
docker image prune -f
# 清理未使用卷
docker volume prune -f
# 清理构建缓存
docker builder prune -f
# 一键清理所有(谨慎使用)
docker system prune -a --volumes磁盘分析
# 查看磁盘使用率
df -h /data/docker
# 查看目录大小
du -sh /data/docker/*
# 查找大文件
find /data/docker -type f -size +100M -exec ls -lh {} \;
# 按大小排序目录
du -ah /data/docker | sort -rh | head -20容器和镜像查询
# 查看所有容器(包括已停止)
docker ps -a
# 查看已停止容器
docker ps -a | grep Exit
# 查看容器详细信息
docker inspect <container-id>
# 查看镜像列表
docker images
# 查看镜像详细信息
docker inspect <image-id>
# 查找引用某镜像的容器
docker ps -a --filter ancestor=<image-name>kubelet 配置查询
# 查看 kubelet 启动参数
systemctl cat kubelet
# 查看 kubelet 配置文件
cat /var/lib/kubelet/config.yaml
# 通过 API 查询(K8s 1.11+)
kubectl get kubeletconfiguration -o yaml