emptyDir 监控与驱逐机制实战
一句话点透原理
emptyDir.sizeLimit 约束的是单个卷,不会为节点预留磁盘,也不能代替 ephemeral-storage 的 request/limit。即使每个 Pod 都没有超过自己的 sizeLimit,所有 Pod 的 emptyDir、容器日志和可写层仍可能共同耗尽节点磁盘并触发 DiskPressure;此时 kubelet 按节点压力驱逐规则选择 Pod,被驱逐的不一定是 emptyDir 使用量最大的 Pod。
emptyDir 是什么
emptyDir 是生命周期与 Pod 绑定的临时卷:
- Pod 被调度到节点后创建,Pod 被删除时清理。
- 容器崩溃或重启不会删除该卷,只要 Pod 仍然存在,数据就还在。
- 同一 Pod 内的主容器、Sidecar 和 Init Container 可以共享该卷。
- 默认使用节点本地磁盘;设置
medium: Memory时使用 tmpfs,并按内存使用量计量。 - 数据没有持久性保证,不适合保存不可丢失的数据。
apiVersion: v1
kind: Pod
metadata:
name: emptydir-demo
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "sleep 3600"]
volumeMounts:
- name: cache
mountPath: /cache
- name: sidecar
image: busybox
command: ["sh", "-c", "sleep 3600"]
volumeMounts:
- name: cache
mountPath: /cache
volumes:
- name: cache
emptyDir: {}
适合 emptyDir 的数据通常具有“生命周期短、可丢失、可重建、容量可控”的特点,例如:
- 图片、下载文件、依赖包和推理中间结果的短期缓存;
- 视频转码、批处理、AI 和 ETL 任务的中间文件;
- 主容器与 Sidecar 之间交换的文件;
- 可丢失的
/tmp、/work、/cache工作目录; - 小容量、低延迟的内存临时数据。
数据不能丢、需要跨 Pod 共享、需要跨节点迁移或需要长期保留时,应使用 PVC、对象存储、数据库或分布式文件系统。
为什么容器内 df 看不到 sizeLimit
下面的配置把卷上限设为 100 GiB:
volumes:
- name: cache
emptyDir:
sizeLimit: 100Gi
sizeLimit 不是独立块设备或独立文件系统的容量。磁盘型 emptyDir 仍是节点文件系统中的目录,所以在容器里对挂载点执行 df -h,通常看到的是底层节点文件系统容量,而不是 100 GiB。
kubelet 通过周期性目录扫描,或在支持的文件系统上通过项目配额统计本地临时存储使用量,再依据限制决定是否驱逐。项目配额用于更准确地统计,并不等同于由文件系统直接拒绝写入。
计量有前提
kubelet 只会在受支持的本地临时存储布局中正确计量。若把额外文件系统挂到 /var/lib/kubelet、/var/log 或容器运行时目录下,计量和限制可能失效。目录扫描也无法统计“文件已删除但进程仍保持打开”的占用;项目配额可以准确记录这种空间。
两类驱逐必须分开理解
单个卷或 Pod 超限
单个 emptyDir 超过 sizeLimit 时,违规对象明确,kubelet 可以将对应 Pod 标记为驱逐候选。事件中可能看到:
Usage of EmptyDir volume "cache" exceeds the limit "100Gi"
本地临时存储还可以通过容器级 request/limit 管理:
resources:
requests:
ephemeral-storage: 50Gi
limits:
ephemeral-storage: 100Gi
Pod 的本地临时存储使用量不只包含 emptyDir,还包括容器可写层、Pod 日志以及 Kubernetes 为 Pod 管理的部分文件。若容器可写层与日志超过容器限制,或 Pod 内所有容器和磁盘型 emptyDir 的总使用量超过 Pod 聚合限制,kubelet 也会触发驱逐。
节点磁盘压力
假设六个 Pod 的 emptyDir.sizeLimit 都是 100 GiB,实际各用了 80 GiB。每个 Pod 都没有越界,但总量已经达到 480 GiB,再加上镜像、日志、容器可写层、kubelet 和系统组件占用,节点仍可能触发:
nodefs.available below eviction threshold
这时需要解决的是节点级资源不足,而不是某个卷违反配额。kubelet 会持续回收节点资源;若镜像和容器垃圾回收不足以恢复到安全阈值,就会驱逐 Pod,直到压力解除。
为什么不一定驱逐写得最多的 Pod
节点压力驱逐不是按“谁最接近 emptyDir.sizeLimit”排序。kubelet 依次考虑:
- Pod 对紧缺资源的实际使用量是否超过 request;
- Pod Priority;
- 实际使用量相对于 request 的比例。
对于 DiskPressure,QoS Class 不直接决定驱逐顺序;QoS 只能在某些资源压力场景中帮助估计结果。没有声明 ephemeral-storage.requests 的 Pod,其 request 相当于 0,只要产生了可计量的本地临时存储使用,就属于超过 request。
因此,以下现象都是可能的:
emptyDir使用量较小但没有 request、Priority 较低的 Pod 先被驱逐;- 使用量更大但 request 合理、Priority 更高的 Pod 暂时保留;
- 驱逐一个 Pod 后仍未恢复阈值,kubelet 继续驱逐其他 Pod;
- 控制器重新创建 Pod,而调度与容量治理没有改善,形成反复驱逐。
kubelet 的目标是让节点尽快回到安全状态,并不是寻找一个“最有责任”的 Pod。
sizeLimit 与 ephemeral-storage 的分工
| 配置 | 粒度 | 主要作用 |
|---|---|---|
emptyDir.sizeLimit |
单个 emptyDir 卷 |
控制该卷的最大使用量 |
requests.ephemeral-storage |
容器,调度时汇总到 Pod | 告诉调度器预计需要的本地临时存储 |
limits.ephemeral-storage |
容器,kubelet 汇总与执行 | 限制容器/Pod 的本地临时存储使用 |
| kubelet eviction threshold | 节点文件系统 | 在 nodefs、imagefs 或 containerfs 压力下保护节点 |
推荐同时配置:
apiVersion: v1
kind: Pod
metadata:
name: emptydir-practice
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "sleep 3600"]
resources:
requests:
cpu: 500m
memory: 512Mi
ephemeral-storage: 50Gi
limits:
cpu: "1"
memory: 1Gi
ephemeral-storage: 100Gi
volumeMounts:
- name: cache
mountPath: /cache
volumes:
- name: cache
emptyDir:
sizeLimit: 100Gi
Memory 类型的差异
medium: Memory 的 emptyDir 计入容器内存使用,而不是磁盘型 local ephemeral storage。其有效容量还会受到 Pod/容器内存限制和节点可用内存影响。
逐 Pod、逐卷监控方案
常见 Prometheus 采集栈通常缺少可直接用于告警的“逐 emptyDir 卷使用量 + sizeLimit”成对指标,因此可以补充一个自定义采集器,分为控制面元数据和节点目录用量两部分。
1. 采集 sizeLimit 元数据
通过 Kubernetes Informer 监听 Pod 的增删改,提取每个配置了 sizeLimit 的 emptyDir:
emptydir_size_limit_bytes{
namespace="default",
pod="app-0",
node="worker-1",
pod_uid="a74bdf6a-bb18-48ab-9462-3c92c7eb1aff",
volume="cache"
} 107374182400
关键关联字段是 pod_uid 和 volume。Pod 名称可能复用,不能只用 namespace + pod 关联历史目录。
2. 在每个节点采集卷目录使用量
以 DaemonSet 方式在每个节点扫描 kubelet 的 Pod 目录。默认路径形态为:
/var/lib/kubelet/pods/<pod_uid>/volumes/kubernetes.io~empty-dir/<volume_name>
暴露指标:
emptydir_used_bytes{
node="worker-1",
pod_uid="a74bdf6a-bb18-48ab-9462-3c92c7eb1aff",
volume="cache"
} 75161927680
采集器需要只读挂载 kubelet Pod 目录,并控制扫描频率和并发,避免对包含大量小文件的节点造成明显 I/O 压力。若集群修改了 kubelet rootDir,路径也要同步调整。
3. 在 Prometheus 中关联并计算使用率
两个指标应同时按 pod_uid 和 volume 关联,避免一个 Pod 有多个 emptyDir 时产生错误匹配:
emptydir_used_bytes
/
on (pod_uid, volume)
group_left(namespace, pod, node)
emptydir_size_limit_bytes
可通过 recording rule 生成 emptydir_usage_ratio。
推荐标签:
| 标签 | 用途 |
|---|---|
namespace、pod |
定位工作负载 |
node |
关联节点磁盘压力 |
pod_uid |
避免 Pod 重建后的名称复用 |
volume |
区分同一 Pod 的多个 emptyDir |
used_bytes |
当前占用 |
size_limit_bytes |
配置上限 |
usage_ratio |
占上限比例 |
4. emptyDir 告警建议
| 级别 | 条件 | 意图 |
|---|---|---|
| warning | 使用率 > 70%,持续 10 分钟 | 提前发现增长趋势 |
| critical | 使用率 > 85%,持续 5 分钟 | 需要人工介入 |
| emergency | 使用率 > 95%,持续 1 分钟 | 很可能即将触发驱逐 |
阈值需要结合采集周期和业务写入速度调整。对于突发写入任务,仅按百分比告警可能来不及,还应增加单位时间增长量或预计耗尽时间告警。
节点级监控
Kubernetes 可能观察以下文件系统:
| 名称 | 含义 |
|---|---|
nodefs |
kubelet、日志和部分本地数据所在文件系统 |
imagefs |
镜像与容器可写层所在的独立文件系统 |
containerfs |
部分运行时中单独承载容器可写层的文件系统 |
需要先确认集群实际磁盘布局,再将指标的 mountpoint 与 kubelet eviction signal 对齐。至少监控:
node_filesystem_avail_bytes与node_filesystem_size_bytes;kube_node_status_condition{condition="DiskPressure",status="true"};- Pod 状态或事件中的
Evicted; - kubelet 驱逐事件;
emptyDir使用量排行及增长速度;- 节点镜像、容器日志和可写层占用。
参考告警分层:
| 级别 | 条件 | 说明 |
|---|---|---|
| warning | nodefs 使用率 > 75%,持续 10 分钟 | 提前清理或扩容 |
| critical | nodefs 使用率 > 85%,持续 5 分钟 | 接近驱逐风险 |
| emergency | nodefs 使用率 > 90% 或 DiskPressure=True |
可能已经开始驱逐 |
| critical | 发现 Evicted Pod | 业务已经受到影响 |
最有价值的观测链路是:
单卷 emptyDir 使用率/增长率
→ Pod ephemeral-storage 接近 request/limit
→ nodefs/imagefs/containerfs 可用空间下降
→ DiskPressure
→ Pod Evicted
治理建议
应用层
- 给缓存设置容量上限和淘汰策略。
- 定期清理临时文件,控制日志滚动。
- 中间结果完成后及时上传对象存储。
- 避免在
emptyDir中长期保存大文件。 - 对“删除但仍保持打开”的文件检查进程文件描述符。
Pod 与命名空间
- 同时配置
emptyDir.sizeLimit和ephemeral-storagerequest/limit。 - 用实际峰值和增长模型设置 request,不要把所有 Pod 的 request 留为 0。
- 使用 LimitRange 提供默认 request/limit。
- 使用 ResourceQuota 控制 namespace 的临时存储总量;要让临时存储配额生效,Pod 也需要声明相应 limit。
- 为关键服务设置合理的 PriorityClass,但不要把高优先级当作容量治理方案。
集群
- 为构建、批处理、转码和大模型任务使用专用节点池。
- 预留
system-reserved、kube-reserved,并合理设置 kubelet eviction threshold。 - 监控
nodefs、imagefs和containerfs,不要只监控根分区。 - 控制单节点可调度的
ephemeral-storage.requests总量。 - 同时采集 emptyDir、容器日志、可写层、镜像垃圾回收和驱逐事件。
不适合 emptyDir 的场景
| 场景 | 更合适的方案 |
|---|---|
| 数据不能丢 | PVC、对象存储或数据库 |
| 单 Pod 需要数百 GiB 临时空间 | 专用节点池、Local PV 或 PVC |
| 多副本共享数据 | 共享存储或对象存储 |
| 长周期缓存 | 外部缓存或持久化卷 |
| 构建产物需要保留 | 制品库或对象存储 |
| 日志持续高速增长 | 日志采集系统与受控滚动策略 |
排查 Checklist
- [ ] 查看事件,区分“单卷/Pod 超限”与“节点 DiskPressure”。
- [ ] 检查
emptyDir.sizeLimit,不要用容器内df -h判断卷上限。 - [ ] 检查每个容器的
ephemeral-storage.requests/limits。 - [ ] 确认 kubelet 本地存储布局受支持,且没有额外挂载导致计量失效。
- [ ] 查看
nodefs、imagefs、containerfs的实际挂载点和可用空间。 - [ ] 排查容器日志、可写层、镜像和其他 Pod,而不只看 emptyDir。
- [ ] 检查已删除但仍由进程持有的文件。
- [ ] 核对 Pod Priority,理解节点压力下的实际驱逐排序。
- [ ] 防止控制器把被驱逐 Pod 反复调度回同一类高压节点。
核心结论
emptyDir.sizeLimit是卷级限制,不是节点磁盘预留,也不是调度 request。- 单卷或 Pod 超限时,kubelet 可以针对对应 Pod 触发驱逐。
- 所有 Pod 都未超过各自
sizeLimit,总量仍可能让节点进入DiskPressure。 - 节点压力驱逐先看是否超过 request,再看 Priority 和相对 request 的使用量;不是按 emptyDir 使用绝对值排序。
- 对磁盘压力,QoS Class 不直接决定驱逐顺序。
- 没有声明
ephemeral-storage.requests的 Pod 在驱逐排序和调度容量管理上都更脆弱。 - 监控应覆盖单卷、Pod 和节点三层,并同时观察使用量、增长速度、压力状态和驱逐事件。
medium: Memory的 emptyDir 按内存计量,不能套用磁盘型 emptyDir 的监控逻辑。
最终原则
emptyDir.sizeLimit 解决“单个卷不要越界”;ephemeral-storage request/limit 解决“调度和 Pod 总量要有边界”;节点容量、保留量和驱逐监控解决“不要让所有 Pod 一起打满节点”。