K8s 节点磁盘打满导致 Pod 被驱逐:4 类占用与回收顺序

我不会起名字322 · 后端 / 算法 / 数据库

📘 技术栈 | 🧩 力扣 Hot100 | 🐹 Go 项目 | 🟥 Redis | 🐬 MySQL

文章目录

  • [K8s 节点磁盘打满导致 Pod 被驱逐:4 类占用与回收顺序](#K8s 节点磁盘打满导致 Pod 被驱逐:4 类占用与回收顺序)
    • [一、先搞清:kubelet 有两类阈值,不是一个](#一、先搞清:kubelet 有两类阈值,不是一个)
    • [二、第 1 类:镜像层堆积](#二、第 1 类:镜像层堆积)
    • [三、第 2 类:容器日志](#三、第 2 类:容器日志)
    • [四、第 3 类:emptyDir 与临时数据](#四、第 3 类:emptyDir 与临时数据)
    • [五、第 4 类:文件系统碎片与 reserve 块](#五、第 4 类:文件系统碎片与 reserve 块)
    • [六、回收顺序:kubelet 先杀谁?](#六、回收顺序:kubelet 先杀谁?)
    • 七、一个急救清单(按优先级)
    • 八、总结

K8s 节点磁盘打满导致 Pod 被驱逐:4 类占用与回收顺序

凌晨三点,告警连响:一批 Pod 被 Evicted。上去一看 kubectl describe node,满屏 DiskPressure------节点根分区使用率 92%。

这是个很典型的"连锁事故":磁盘涨 → kubelet 判定 DiskPressure → 开始驱逐 Pod → 业务抖动。这篇说清 kubelet 的驱逐逻辑,拆 4 类磁盘占用,给出"先回收谁"的顺序表。

一、先搞清:kubelet 有两类阈值,不是一个

很多人以为节点磁盘只有一个"满了就驱逐"的开关。实际上 kubelet 管着两个文件系统,各有软硬两档阈值:

信号 监控对象 影响
nodefs 节点根文件系统(/var/lib/kubelet 所在) 影响 Pod 创建、日志、emptyDir
imagefs 容器运行时镜像存储(/var/lib/docker 或 containerd 目录) 影响镜像拉取、容器启动

每类又有两个阈值:

  • 软阈值(soft) :达到后 kubelet 开始尝试驱逐 ,但有宽限期(默认 soft 阈值 + 2 分钟);
  • 硬阈值(hard) :达到后 kubelet 立即驱逐,不给宽限期。
bash 复制代码
# 看节点当前状况和驱逐条件(含各类阈值)
kubectl describe node <node> | sed -n '/Conditions:/,/Addresses:/p'
kubectl describe node <node> | grep -A12 "Allocatable"

# 直接看 kubelet 的阈值配置
kubectl get --raw "/api/v1/nodes/<node>/proxy/configz" | python -m json.tool | grep -A5 eviction

关键判据 :DiskPressure=True 且 Reason=KubeletHasDiskPressure。而真正决定"先杀谁"的,是下一节的回收顺序。

二、第 1 类:镜像层堆积

最大的隐形占用。每次 CI 构建推一个新 tag,节点就多拉一份镜像;旧镜像不会自动删(除非你配了 GC 阈值)。

bash 复制代码
# containerd 环境
crictl images | wc -l
crictl images | awk '{print $3}' | head          # SIZE 列
# 或直接看目录占用
du -sh /var/lib/containerd/io.containerd.content.v1.content 2>/dev/null
# docker 环境
docker system df

注意:同一个应用的多个历史版本镜像,可能每个 500MB~1GB。如果一天发 5 次版、留 30 天,光镜像就能吃掉几十 G。

三、第 2 类:容器日志

容器 stdout/stderr 被写到节点的 /var/log/pods/ 或 /var/lib/docker/containers/,不受 json-file 的 max-size 以外任何限制。一个疯狂打日志的应用能在一夜之间写满磁盘。

bash 复制代码
# 找最大的容器日志
du -sh /var/log/pods/* 2>/dev/null | sort -rh | head -10
du -sh /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -rh | head -10

修法(治本):

yaml 复制代码
# kubelet 配置里限制单容器日志体积
containerLogMaxSize: "50Mi"
containerLogMaxFiles: 5

以及在应用层降日志级别 ------DEBUG 级别在生产环境打日志,是磁盘杀手。

四、第 3 类:emptyDir 与临时数据

emptyDir 卷落在节点磁盘上(除非指定了 medium: Memory)。它生命周期跟 Pod 走,但如果 Pod 一直在跑、往里写数据不清理,就会持续膨胀。

bash 复制代码
# 定位哪个 Pod 的 emptyDir 在涨
kubectl get pods --all-namespaces -o json | \
  python -c "
import json,sys
d=json.load(sys.stdin)
for p in d['items']:
    for v in p['spec'].get('volumes',[]):
        if 'emptyDir' in v: print(p['metadata']['namespace'], p['metadata']['name'], v['name'])
"

还有一类是日志文件写到 emptyDir 里(应用自定义日志路径指向挂载点)------排查时别只看容器的 stdout。

五、第 4 类:文件系统碎片与 reserve 块

/var/lib/docker 或 overlay2 目录文件数极多时 (每个镜像层是一堆小文件),会消耗 inode 和元数据空间,导致"df 显示还有空间,但写不进去"。

bash 复制代码
# 同时看空间和 inode
df -h /var/lib/docker
df -i /var/lib/docker          # inode 用满一样写不了

六、回收顺序:kubelet 先杀谁?

这是本篇最有价值的部分。磁盘压力下 kubelet 的顺序是:先驱逐 Pod,再 GC 镜像------很多人以为是反过来的。

原因:镜像 GC 是"低优先级、被动"的,kubelet 认为先腾出 Pod 相关的临时空间更快。所以你会看到 Pod 被杀了、镜像还在。

内置的驱逐排序(kubelet 的 eviction ranking)大致是:

  1. 超出 requests 最多的 Pod;
  2. 使用量 > requests 的 Pod;
  3. 按 Pod 优先级(priorityClass)低的先杀;
  4. 同优先级下,创建时间晚的先杀。

imagefs 相关的 GC 阈值可以调:

bash 复制代码
# kubelet 启动参数(或 config 文件)
--image-gc-high-threshold=85   # 使用率超 85% 开始 GC 镜像
--image-gc-low-threshold=80    # GC 到 80% 停止
--eviction-hard=imagefs.available<10%,nodefs.available<10%
--eviction-soft=nodefs.available<15%
--eviction-soft-grace-period=nodefs.available=2m

一个可带走物 :把上面这段"先驱逐后 GC"的顺序记牢------别指望删镜像能立刻止住驱逐,真正的急救是先找出写爆磁盘的那个 Pod/日志。

七、一个急救清单(按优先级)

顺序 动作 命令
1 找出吃磁盘的容器日志 `du -sh /var/log/pods/*
2 清理最大的日志文件(确认可删) truncate -s 0 <logfile>
3 手动触发镜像清理 crictl rmi --prune / docker image prune -a
4 检查 emptyDir 大户 上面的 Pod 扫描脚本
5 长期:配 containerLogMaxSize + image-gc 阈值 kubelet config

八、总结

  1. kubelet 管 nodefs 和 imagefs 两套阈值,各有软/硬两档;
  2. 4 类占用:镜像层、容器日志、emptyDir、文件系统碎片;
  3. 回收顺序是先驱逐 Pod、后 GC 镜像 ------ 删镜像止不住正在发生的驱逐;
  4. 急救的第一刀是找日志 ,长期要配 containerLogMaxSize + image-gc 阈值。

留个问题给你:你们的镜像 GC 阈值用的默认 85%,还是调过?有没有遇到过 Pod 被驱逐了但镜像一个没删的情况?

相关推荐
雪落漂泊19 分钟前
Linux进程(五):进程地址空间篇
linux·运维·服务器
瑞克-桑切斯30 分钟前
docker mail商城
运维·docker·容器
AR-26710-1 小时前
Linux Day32——网络,ROCE-IB与以太网
linux·运维
不庸.2 小时前
WPScan 实战入门:从 Docker 靶场到弱密码爆破
运维·安全·web安全·docker·容器·安全性测试·wpscan
Decay__4 小时前
SQL Server 数据库全栈运维实战系列 — 合集索引(24 篇)
运维·数据库·sql·mysql·架构
Decay__12 小时前
12_MySQL InnoDB 内存与锁调优 —— Buffer Pool、redo log、隔离级别与锁等待
运维·数据库·sql·mysql·架构
java_logo14 小时前
Docker 部署 Dify:轻松搭建 LLM 应用开发平台
运维·docker·容器
方便面不加香菜15 小时前
Docker Container -- 综合实战案例详解
运维·docker·容器
海洋之恒16 小时前
GitHub Actions 自动化:5 个真实运维提效案例
运维·自动化·github