《Pod Evicted 排查实战:K8s 资源驱逐与磁盘压力定位》

一、Pod Evicted 是什么:kubelet 的”保命”机制

Pod Evicted(Pod 被驱逐)是 Kubernetes 生产环境最常见的异常之一:当节点在内存、磁盘或 PID 资源告急时,kubelet 会主动杀掉并驱逐低优先级 Pod 来保障节点本身不被拖垮。本文从驱逐机制、定位命令到根治方案,手把手带你排查 Pod Evicted,并区分 DiskPressure、MemoryPressure 与主动维护驱逐三种场景,让你下次看到 Evicted 状态不再慌。

和 节点 NotReady、ImagePullBackOff 不同,Evicted 通常不是镜像或网络问题,而是资源天花板被触顶。Pod 消失后会被控制器(Deployment/StatefulSet)立刻重建,于是你看到的现象是”Pod 反复被干掉又起来”,业务表现为间歇性 5xx 或请求抖动。

二、为什么偏偏是你被驱逐:QoS 与驱逐优先级

kubelet 驱逐谁、留谁,遵循 Kubernetes 的 QoS(服务质量)等级。理解了等级,就知道为什么隔壁 Pod 稳如老狗,你的却”首当其冲”。

2.1 三类 QoS 等级

QoS 等级requests/limits 设置驱逐优先级典型场景
GuaranteedCPU 与内存都设了相等且非 0 的 requests=limits最后被驱逐核心数据库、支付服务
Burstable至少一类资源设了 requests(limits 可不设或 > requests)中间普通业务服务
BestEffort完全不设置任何 requests/limits最先被驱逐临时任务、批处理 Job

2.2 驱逐顺序的两个维度

当节点触发驱逐阈值时,kubelet 先在同一 QoS 内按资源实际使用超出 requests 的比例排序(用得越狠越先走),再跨 QoS 按 BestEffort → Burstable → Guaranteed 的顺序清理。换句话说:不设 requests 的 Pod 永远第一个倒下。

2.3 主动驱逐:drain 与维护窗口

除了资源触发的被动驱逐,运维执行 kubectl drain 做节点升级、打补丁或换盘时,也会主动驱逐节点上的所有 Pod。这类 Evicted 是”计划内”的:kubelet 先发 SIGTERM,等优雅退出超时后再强制删除,并由调度器在其他节点重建。区分二者很简单——被动驱逐的 Message 一定带 MemoryPressure / DiskPressure 等条件;主动驱逐则来自 drain 流程,节点会先进入 SchedulingDisabled(cordon)状态。生产上务必给关键服务配 PodDisruptionBudget,否则一次例行维护就可能把无状态网关全量干掉。

三、三大触发信号:DiskPressure / MemoryPressure / PIDPressure

kubelet 通过节点 Conditions 暴露压力信号,Evicted 只是结果,根因藏在下面三种压力里:

需要注意 kubelet 把阈值分为硬驱逐(evictionHard)与软驱逐(evictionSoft)两类:硬驱逐一旦触顶立即杀 Pod,不留商量余地;软驱逐则会等待 evictionSoftGracePeriod 配置的宽限期,给 Pod 一点自我恢复的时间。日常运维更常遇到的是硬驱逐,因为默认值偏保守(如内存可用低于 100Mi 就动手),节点稍有波动就会触发。理解这张表,定位时才知道该往哪个资源维度排查。

压力信号触发条件典型现象首选修复
MemoryPressure节点可用内存低于 memory.available 阈值Pod OOM、频繁重建、节点卡顿给 Pod 设置内存 limits、扩容节点
DiskPressure容器文件系统(imagefs)或根分区可用空间不足镜像拉取失败、日志写满磁盘、Pod 被逐限制容器日志体积、清理镜像、加盘
PIDPressure节点进程数超过 pid.available 上限无法 fork 新进程、命令卡死限制容器的 pid 上限、排查 fork 炸弹

其中 DiskPressure 是线上最高发的 Evicted 根因:默认 containerd/docker 不会自动收敛容器日志,一个疯狂打日志的 Pod 能在几小时内写满 40G 系统盘,随后 kubelet 一边驱逐 Pod、一边因磁盘满连新镜像都拉不下来,形成雪崩。

四、定位四步法:从 Events 到根因

4.1 第一步:describe 看 reason

被驱逐的 Pod 会进入 Failed 状态并带上 Evicted reason,Events 里藏着直接原因:

kubectl describe pod <pod-name> -n <ns>
# 关键输出示例:
# Reason:  Evicted
# Message: The node had condition: [DiskPressure].
#          低优先级 Pod 因节点磁盘压力被 kubelet 驱逐

4.2 第二步:过滤 Evicted 事件

用 field-selector 把 Evicted 事件单独拎出来,避免被海量正常事件淹没:

kubectl get events --field-selector reason=Evicted -A
# 或按涉及对象过滤
kubectl get events -n <ns> --field-selector involvedObject.kind=Pod

4.3 第三步:看节点 Conditions 与阈值

定位到节点后,检查它的压力信号和 kubelet 驱逐阈值配置:

kubectl get nodes -o wide
kubectl describe node <node> | grep -A6 Conditions
# 关注 MemoryPressure / DiskPressure / PIDPressure 是否为 True

# 查看当前生效的驱逐硬阈值
kubectl get node <node> -o yaml | grep -A10 evictionHard

4.4 第四步:登节点查磁盘与进程

若怀疑 DiskPressure,直接 SSH 到节点统计大目录(缓存与日志混用磁盘时极易踩坑):

df -h /var/lib/containerd   # 容器存储所在盘
du -sh /var/log/containers/* 2>/dev/null | sort -rh | head
du -sh /var/lib/docker/* 2>/dev/null | sort -rh | head
# 找出膨胀最快的容器日志,定位"写盘元凶"

五、根治方案:从 requests 到日志轮转

5.1 给 Pod 设置 requests/limits

最基础的防线:任何常驻服务都应设 requests,核心服务设 Guaranteed。同时用 ephemeral-storage 给容器文件系统划上限,超限即被 kubelet 优先清理而非拖垮整机:

resources:
  requests:
    memory: "512Mi"
    cpu: "250m"
    ephemeral-storage: "2Gi"
  limits:
    memory: "1Gi"
    ephemeral-storage: "5Gi"

5.2 限制容器日志体积(关键)

在 containerd 配置里给每个容器日志设上限,避免单 Pod 写满系统盘。修改 /etc/containerd/config.toml 后重启 containerd:

[plugins."io.containerd.grpc.v1.cri".container_log]
  max_size = "50MB"
  max_container_log_files = 5

Docker 用户则在 /etc/docker/daemon.json 设 log-opts 的 max-size 与 max-file。改完记得在 CI 流水线里加一步重启校验,避免配置漂移。

5.3 用 emptyDir 限制临时文件

业务若会往本地写临时文件,给 emptyDir 设 sizeLimit,超出直接写入失败而非无限增长:

emptyDir:
  sizeLimit: 1Gi
  medium: ""

5.4 调优驱逐阈值(谨慎)

在 kubelet 配置(/var/lib/kubelet/config.yaml)中可抬高阈值,给运维留缓冲,但别调太松以免节点真被拖死:

经验值:内存阈值建议留 10%、磁盘 15%,并配合 evictionMinimumReclaim 保证每次驱逐后回收出一段安全余量,避免”刚驱逐完又立刻触顶”的抖动。修改后务必 systemctl restart kubelet 并观察节点 Conditions 是否回落,切忌一次性把阈值改到极低——那样节点会在”假健康”状态下默默把业务拖垮,比 Evicted 更隐蔽。

evictionHard:
  memory.available: "10%"
  nodefs.available: "15%"
  nodefs.inodesFree: "5%"
  imagefs.available: "15%"

六、防御性配置:PDB、PriorityClass、优雅终止

除了补资源,还要降低”被驱逐”对业务的影响:

  • PodDisruptionBudget:主动维护(drain)时保证最小可用副本,避免一刀切。
  • PriorityClass:给核心服务更高优先级,资源紧张时优先保命。
  • 优雅终止:配置 preStop 与合理的 terminationGracePeriodSeconds,让连接平滑 draining 而非硬切断。
# PriorityClass 示例
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false

七、排障清单(速查表)

现象先看根因大概率动作
Pod 反复重建describe 的 Reason资源超限被逐加 requests/limits
节点上其他 Pod 也异常node ConditionsDiskPressure/MemoryPressure清日志/加盘/扩容
镜像拉取变慢/失败df -h imagefs磁盘满清镜像 + 限日志
命令卡死无法 forkps 进程数PIDPressure限 pid + 查 fork 炸弹

八、结语

Pod Evicted 的本质是节点资源保护机制,而非 Bug。排查链路始终是:看 Events 的 reason → 定位节点 Conditions → 登节点核实磁盘/内存/进程 → 从 requests、日志轮转、emptydir 三处根治。把它和 Pod Pending、CrashLoopBackOff 并列收进你的 K8s 排障工具箱,下次告警响起就能在 5 分钟内定责。

上一篇 ExternalDNS 实战:用 K8s 自动管理 DNS 解析
下一篇 A2A 协议实战:打通多智能体协作的开放标准
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]