首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >把 Pod 从节点上“送走”的真正凶手:忽视ephemeral-storage与inode导致的频繁 Eviction

把 Pod 从节点上“送走”的真正凶手:忽视ephemeral-storage与inode导致的频繁 Eviction

原创
作者头像
编程小妖女
发布2025-09-28 12:06:16
发布2025-09-28 12:06:16
2010
举报
文章被收录于专栏:后端开发后端开发

本文我向大家分享,我在一次公司业务故障处理时,如何通过分析下面这张截图和一些日志信息,成功处理一次 Kubernetes 集群里的 pod 频繁出现 eviction 的故障。

技术环境

  • Kubernetes v1.28~v1.30 复现与排障;容器运行时 containerd
  • 节点磁盘为 NVMe SSD,/var/lib/containerd/var/lib/kubelet 位于同一块盘
  • 集群内置 kube-state-metrics 与 Prometheus Operator;额外抓取 kubelet metricsmetrics/cadvisor 端点
  • 日志采集为 stdout/stderr,无需 sidecar

故障现象

业务方报告某些命名空间的 Pod 间歇性被驱逐,Deployment 持续重建,波及面包含无状态与有状态服务。kubectl describe pod 的典型事件如下:

代码语言:sh
复制
Warning  Evicted  kubelet  The node was low on resource: ephemeral-storage.

这条信息在多家发行版与厂商的案例中高度一致,例如 Red Hat OpenShift 的知识库与社区贴都记录了相同的报错与上下文说明,包含 request=0using XXXKi 等具体文字片段,可据此快速判定为 ephemeral-storage 压力触发的节点级驱逐,而非 CPU/Memory 问题。

伴随节点侧 kubelet 日志出现 Eviction Manager 的判定与执行轨迹,例如:

代码语言:sh
复制
eviction_manager.go:340  "Eviction manager: must evict pod(s) to reclaim"  resourceName="ephemeral-storage"
eviction_manager.go:369  "Eviction manager: pods ranked for eviction"  ...
eviction_manager.go:577  "Eviction manager: pod is evicted successfully"  pod="ns/app-xxx"

这类 callstack/日志在社区长文与官方已知问题里均可见到同款关键字与函数文件名,足以佐证是 kubelet 的 Node-pressure Eviction 流程在工作。

更直观地,运维同学抓取了各节点 Current State vs Eviction Thresholds 的快照,可以看到 nodefs.availableimagefs.availableinodesFree 等信号与阈值的对比,imagefs.available 掉到 15% 以下就触发了驱逐控制环。这类截图在实践文章中亦有复现。


根因剖析

Kubernetes 官方把这种场景归类为 Node-pressure Eviction。当 nodefs.availableimagefs.availablenodefs.inodesFreepid.available 等信号低于阈值,kubelet 会先尝试节点级回收(删 dead pods/containers、回收未使用镜像),若无效才进入逐 Pod 驱逐排序与执行。阈值可分 evictionHardevictionSoft(含 GracePeriod),回收还受 eviction-minimum-reclaim 影响以避免抖动。

这次事故的触发因子有两个:

  1. 未对关键工作负载声明 ephemeral-storagerequests/limits 很多团队只给 CPU/Memory 设配额,却忘了本地临时存储。容器的日志、/tmpemptyDir、镜像可写层都会消耗本地盘,超出节点阈值就会被判定为“挤占公共资源”而被驱逐。
  2. 镜像层与日志增长过快,命中阈值imagefs/containerfs 压力触发时,kubelet 会先按 LRU 清理未用镜像;若清不下来,就按使用量排序驱逐 Pod。日志侧若未限制单容器日志大小与轮转份数,也会迅速吞噬本地盘与 inode

需要强调,描述里显示节点磁盘尚有空间 并不能反驳驱逐,因为触发条件看的是可用百分比与 inode 等多维信号,而非你肉眼看到的“还有几十 GB”。类似的疑惑在社区讨论中屡见不鲜。


复现与排查步骤

1) 观测:确认是 ephemeral-storage 驱逐

  • kubectl describe pod <pod>Reason=EvictedMessage=The node was low on resource: ephemeral-storage
  • 节点上查看 kubelet 日志,抓到 eviction_manager.go 相关行,定位资源名为 ephemeral-storage。(rakirahman.me)
  • 抓取节点当前信号与阈值的对比,确认哪一项触线(例如 imagefs.available < 15%)。

2) 溯源:谁在吃盘与 inode

  • 镜像侧:确认 imagefs 是否与 nodefs 分盘(分盘能降低相互影响,官方博客有专门说明)。
  • 容器层:排查容器写入量异常的服务,尤其是大日志/大临时文件/大编译缓存;这部分属于 containerfs
  • 监控侧:Prometheus 抓 kubelet metrics/cadvisorcontainer_fs_usage_bytesinodes 相关指标,或用现成的导出器聚合 ephemeral-storage 用量。需注意不同运行时的指标完备度差异(例如早期 containerd 的磁盘指标覆盖不全).

3) 策略:先避免抖动再治理根因

  • 配置合理的 evictionSofteviction-minimum-reclaim,让系统在缓冲区内消化波动,而不是一跌破阈值就大杀四方。示例与参数语义见官方与实战文章。
  • 立刻止血:提高镜像 GC 的清理速度与阈值间距,避免与驱逐阈值正面相撞导致先触发驱逐才开始清理的尴尬。
  • 落地限制:给关键工作负载补齐 ephemeral-storagerequests/limits 与命名空间级 ResourceQuota
  • 长期治理:统一日志轮转上限与份数,限制单容器日志体积;必要时启用压缩,本地落盘的临时文件迁移到更稳妥的介质。

可运行的配置与脚本示例

以下 YAML/脚本在通用 kubeadm/托管发行版上均可直接套用或少量调整。

A. 为核心业务补齐 ephemeral-storage 配额(Pod 级)

代码语言:yaml
复制
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-gateway
  namespace: prod
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api-gateway
  template:
    metadata:
      labels:
        app: api-gateway
    spec:
      containers:
      - name: gateway
        image: nginx:1.27
        resources:
          requests:
            cpu: "100m"
            memory: "256Mi"
            ephemeral-storage: "2Gi"
          limits:
            cpu: "500m"
            memory: "512Mi"
            ephemeral-storage: "4Gi"
        volumeMounts:
        - name: tmp
          mountPath: /tmp
      volumes:
      - name: tmp
        emptyDir:
          sizeLimit: "2Gi"

官方文档明确 ephemeral-storage 可作为一等资源进行 requests/limits 管控,上述写法会直接参与调度与驱逐排序。

B. 命名空间级资源配额兜底

代码语言:yaml
复制
apiVersion: v1
kind: ResourceQuota
metadata:
  name: rq-ephemeral
  namespace: prod
spec:
  hard:
    requests.ephemeral-storage: "50Gi"
    limits.ephemeral-storage: "100Gi"

社区给出了类似的配方,用于防止团队误配造成无限写盘

C. kubelet 配置:驱逐阈值、宽限期与最小回收量

代码语言:yaml
复制
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
evictionHard:
  memory.available: "500Mi"
  nodefs.available: "10%"
  nodefs.inodesFree: "5%"
  imagefs.available: "15%"
evictionSoft:
  memory.available: "1Gi"
  nodefs.available: "15%"
  nodefs.inodesFree: "10%"
  imagefs.available: "20%"
evictionSoftGracePeriod:
  memory.available: "1m30s"
  nodefs.available: "1m30s"
  nodefs.inodesFree: "1m30s"
  imagefs.available: "1m30s"
evictionMinimumReclaim:
  nodefs.available: "1Gi"
  imagefs.available: "2Gi"
containerLogMaxSize: "20Mi"
containerLogMaxFiles: 5

参数语义与官方参考一致:evictionSoft 叠加 GracePeriod 用于吸收短暂尖峰,evictionMinimumReclaim 避免回收一点就再次触发的锯齿震荡;containerLogMax* 控制每容器日志大小与份数。

D. 镜像 GC 与分盘建议

  • 在只有 nodefs 的节点上,GC 会同时删除 dead pod/container 与未用镜像;若存在独立的 imagefs,两者各自清理,影响更可控。HighThresholdPercent/LowThresholdPercent 需拉开间距,减少频繁触发。

E. Prometheus 告警:ephemeral-storage 接近上限

如果集群已抓取 kubelet 的 cadvisor 指标,可以按容器可写层使用量与声明的 ephemeral-storage limit 做比值预警(不同环境指标来源略有差异,思路如下;若你的环境缺少此指标,可采用社区 k8s-ephemeral-storage-metrics 导出器):

代码语言:yaml
复制
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: ephemeral-storage-rules
  namespace: monitoring
spec:
  groups:
  - name: k8s-ephemeral
    rules:
    - alert: ContainerEphemeralStorageNearLimit
      expr: |
        (container_fs_usage_bytes{image!=""}
         /
         on (namespace,pod,container)
         group_left
         kube_pod_container_resource_limits{resource="ephemeral_storage"})
        > 0.85
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "容器临时存储使用超过 85%"
        description: "{{ $labels.namespace }}/{{ $labels.pod }}/{{ $labels.container }}"

上述比值型告警依赖的两个度量在实践中常见:container_fs_usage_bytes 由 kubelet/cadvisor 暴露,kube_pod_container_resource_limits{resource="ephemeral_storage"}kube-state-metrics 暴露,若你的运行时对磁盘指标支持欠缺,需要按环境调整或采用专门的 ephemeral-storage exporter

F. Docker/节点级日志轮转(适用于仍用 docker 的环境)

代码语言:json
复制
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "5"
  }
}

在仍采用 docker 的节点上,直接用 daemon.json 控制容器日志体积与份数,避免日志把盘吃光。


真实错误与调用栈摘录(节选)

  • kubectl describe podThe node was low on resource: ephemeral-storage. Container ... was using 8369512Ki, which exceeds its request of 0.(来自 OpenShift/社区案例)
  • kubelet 日志:Eviction manager: must evict pod(s) to reclaim resourceName="ephemeral-storage"pods ranked for evictionpod is evicted successfully(来自实战文章与 kind 已知问题的日志样例)

上文开头配图展示了节点当前信号与阈值的对比界面,可作为错误截图的直观参照,用来佐证触发条件与阈值设置之间的关系。


经验总结(避坑清单)

  • 给关键业务补齐 ephemeral-storagerequests/limits。这决定了谁更容易被驱逐能不能被调度进来,别再只配 CPU/Memory。
  • 监控 nodefs/imagefsinode,而不是只看磁盘还剩多少 GB。驱逐阈值按百分比/绝对值/可用 inode等维度判断,表象空间还很多并不能免责。
  • 拉开镜像 GC 的 High/Low 阈值,并设置最小回收量,避免一碰就驱逐的抖动。
  • 统一日志轮转策略:用 kubelet 的 containerLogMax* 与运行时配置控制单容器日志规模,必要时做压缩与保留天数限制。
  • 尽可能把 imagefsnodefs 分盘,镜像与容器可写层互不拖累;若只有一块盘,更要上好告警阈值。
  • 为命名空间设置 ResourceQuota 限制 requests/limits.ephemeral-storage 的总量,避免团队内部某个任务失控写盘拖垮全局。
  • 在 Prometheus 中纳入 ephemeral-storage 相关的告警,无论是直接抓 kubelet/cadvisor,还是引入专门 exporter,关键是尽早预警,留出处置窗口.

反思

ephemeral-storage 是 Kubernetes 里极易被忽略但杀伤力巨大的第三资源。没有配额,它就像共享厨房的冰箱:谁都能塞,塞满了大家都得饿肚子。给关键工作负载补上 requests/limits,把阈值与最小回收量调得合理,再把日志与镜像的增长曲线用监控盯住,你的集群会安静许多,运维夜里也能睡得踏实。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 技术环境
  • 故障现象
  • 根因剖析
  • 复现与排查步骤
    • 1) 观测:确认是 ephemeral-storage 驱逐
    • 2) 溯源:谁在吃盘与 inode
    • 3) 策略:先避免抖动再治理根因
  • 可运行的配置与脚本示例
    • A. 为核心业务补齐 ephemeral-storage 配额(Pod 级)
    • B. 命名空间级资源配额兜底
    • C. kubelet 配置:驱逐阈值、宽限期与最小回收量
    • D. 镜像 GC 与分盘建议
    • E. Prometheus 告警:ephemeral-storage 接近上限
    • F. Docker/节点级日志轮转(适用于仍用 docker 的环境)
  • 真实错误与调用栈摘录(节选)
  • 经验总结(避坑清单)
  • 反思
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档