从崩溃到防御:一个 emptyDir 引发的「蝴蝶效应」

Kathleen Lake, YT, Canada

 

引言

对于这种案例,你们的处理思路是怎么样的呢,是否真正的处理过,如果遇到,你们应该怎么处理。

我想大多数人都没有遇到过。

最后有相关的社区群,有兴趣可以加入。

开始

案例:emptyDir 磁盘爆满引发的「日志黑洞」

背景与问题场景

在 Kubernetes 集群中,emptyDir 是一种生命周期与 Pod 绑定的临时存储卷,常用于缓存、临时数据处理或日志缓冲。然而,由于其默认不限制存储容量且依赖节点磁盘的剩余空间,一旦配置不当,可能迅速耗尽节点磁盘,引发级联故障

某金融系统生产环境曾因此类问题导致日志采集中断、节点不可用,最终影响核心业务。以下是完整的故障还原与技术解析。

1. 故障现象:从日志中断到节点崩溃

时间线:15分钟的系统崩塌
  • • T+0
    • • 监控系统触发 node_storage_usage 告警,显示节点 worker-03 的 /var/lib/kubelet 目录磁盘使用率达 95%
    • • 运维团队收到告警但未及时响应(正值夜间值班空窗期)。
  • • T+5分钟
    • • Fluentd DaemonSet Pod 日志出现大量 Errno::ENOSPC (No space left on device) 错误。
    • • 日志采集完全停止,Elasticsearch 中的业务日志流中断,影响实时风控系统。
  • • T+10分钟
    • • 节点上运行的其他 Pod(如支付网关)因无法写入临时卷(/var/log 目录)进入 CrashLoopBackOff 状态。
    • • kubelet 日志报错:"failed to create container: disk I/O error"
  • • T+15分钟
    • • 节点被标记为 NotReady,控制平面触发 Pod 驱逐(Eviction)。
    • • 部分有状态服务(如 Redis)因存储卷未正确解绑导致数据短暂不一致。
关键现象排查记录
# 检查节点磁盘使用情况  
$ df -h /var/lib/kubelet  
Filesystem      Size  Used Avail Use% Mounted on  
/dev/nvme0n1p2  100G   95G   0G  100% /var/lib/kubelet  

# 定位占用最大的 Pod 存储目录  
$ du -sh /var/lib/kubelet/pods/* | sort -rh | head -n 3  
78G    /var/lib/kubelet/pods/7d8e12a3-.../volumes/kubernetes.io~empty-dir/fluentd-buffer  
12G    /var/lib/kubelet/pods/3fb2a1c1-.../volumes/kubernetes.io~empty-dir/tmp  

# 检查 Fluentd Pod 日志  
$ kubectl logs fluentd-abcde -n logging  
2023-10-01 02:15:03 +0000 [warn]: [output_es] failed to write data: Elasticsearch::Transport::Transport::Error::NoSpace  
2023-10-01 02:15:04 +0000 [error]: no space left on device @ dir_write - /var/log/fluentd/buffer/...

2. 根因分析:从日志洪峰到存储雪崩

emptyDir 的“沉默杀手”特性
  • • 默认无容量限制
    Kubernetes 不会自动限制 emptyDir 的存储使用量,仅依赖节点磁盘的物理空间。
    # 错误配置示例:未设置 sizeLimit  
    volumes:
      - name: fluentd-buffer
        emptyDir: {}  # 隐患点!
  • • 存储路径集中化
    所有 Pod 的 emptyDir 数据集中在 /var/lib/kubelet,单点故障风险极高。
Fluentd 缓冲机制的致命缺陷
  1. Buffer 配置详解
    Fluentd 使用文件缓冲(File Buffer)时,会将日志数据暂存到磁盘,直到成功发送到下游(如 Elasticsearch)。
    <buffer>
      @type file
      path /var/log/fluentd/buffer  # 挂载到 emptyDir 卷
      chunk_limit_size 32MB         # 单个块大小
      total_limit_size 512MB        # 错误!此参数不控制总大小(仅控制内存中的队列长度)
      retry_max_interval 30s
    </buffer>
    • 误区:开发者误认为 total_limit_size 会限制磁盘总使用量,实际该参数仅控制内存中的队列长度。
    • 后果:磁盘缓冲区可能无限增长,直到占满节点空间。
  2. 业务日志的异常洪峰
    • 触发条件:某订单处理服务因循环逻辑错误,在 1 分钟内持续打印 DEBUG 日志,生成速率达到 1GB/s
    • 日志内容示例
      [DEBUG] Processing order ID: 12345, details: {"items": [...]}  # 单条日志约 10KB,每秒写入 10 万次
故障链推演

 

 

3. 解决方案:分层防御与弹性设计

阶段一:紧急止血(5分钟恢复)
  1. 快速释放磁盘空间
    • 强制清理
      # 找到占用最高的 emptyDir 目录  
      du -sh /var/lib/kubelet/pods/* | grep G  
      # 手动删除数据(需确认无关键数据)  
      rm -rf /var/lib/kubelet/pods/<pod-id>/volumes/kubernetes.io~empty-dir/fluentd-buffer/*  
    • 风险:直接删除文件可能导致 Fluentd 数据丢失,需评估日志重要性。
  2. 临时扩容与调度
    • 垂直扩容:临时为节点挂载云盘并扩展文件系统(如 AWS EBS 卷扩容)。
    • Pod 迁移
      kubectl cordon worker-03          # 停止调度新 Pod  
      kubectl drain worker-03 --force   # 驱逐现有 Pod  
阶段二:根因修复(配置与架构优化)
  1. emptyDir 容量硬限制
    volumes:
      - name: fluentd-buffer
        emptyDir:
          sizeLimit: 10Gi  # 关键!超出此限制时,Pod 会被驱逐并自动清理数据
    • 驱逐机制:当 emptyDir 卷超过 sizeLimit,Kubelet 会标记 Pod 为 Evicted,释放存储空间。
  2. Fluentd 缓冲层加固
    • 启用内存缓冲优先
      <buffer>
        @type hybrid         # 混合缓冲模式
        <memory>
          chunk_limit_size 256MB
          total_limit_size 8GB   # 内存缓冲上限
        </memory>
        <file>
          path /var/log/fluentd/buffer
          chunk_limit_size 512MB
        </file>
      </buffer>
    • 流量熔断机制
      <match **>
        @type elasticsearch
        # 当日志堆积超过阈值时,丢弃新日志(根据业务需求选择)  
        overflow_action throw_exception  
        # 或降级到本地文件  
        overflow_action block  
      </match>
  3. 日志管道的弹性设计

    • 动态采样与降级
      <filter app.logs>  
        @type sample  
        interval 10  
        # 当日志速率超过阈值时,仅每10条记录1条  
      </filter>
    • 引入消息队列:在 Fluentd 与 Elasticsearch 之间增加 Kafka,解耦生产与消费速率。
阶段三:防御体系构建(监控与自动化)
  1. 存储配额与 LimitRange
    apiVersion: v1
    kind: LimitRange
    metadata:
      name: storage-limiter
    spec:
      limits:
      - type: Container
        maxLimitRequestRatio:
          ephemeral-storage: "2"  # 限制临时存储超售比例
        defaultRequest:
          ephemeral-storage: "1Gi"
  2. 精细化监控
    • Prometheus 规则示例
      - alert: HighEmptyDirUsage
        expr: (kubelet_volume_stats_used_bytes{persistentvolume="~empty-dir.*"} / kubelet_volume_stats_capacity_bytes) > 0.7  
        for: 5m  
        labels:  
          severity: critical  
        annotations:  
          summary: "EmptyDir volume usage exceeds 70% on {{ $labels.node }}"  
    • Grafana 看板:监控每个 Pod 的 emptyDir 使用量排名。
  3. 混沌测试验证
    • 使用 Chaos Mesh 模拟日志洪峰,验证防御机制是否生效:
      apiVersion: chaos-mesh.org/v1alpha1  
      kind: IOChaos  
      metadata:  
        name: disk-pressure-test  
      spec:  
        action: "latency"  
        volumePath: "/var/lib/kubelet"  
        path: "/var/lib/kubelet/**/*.log"  
        delay: "100ms"  
        duration: "10m"  

4. 深度总结:云原生存储的防御哲学

从故障中学到的教训
  1. “默认安全”并不存在
    • Kubernetes 的灵活性需要显式的防护约束,如 sizeLimit、资源配额等。
  2. 日志系统的容错性优先级
    • 日志管道需设计降级策略(如本地缓存、动态采样),避免影响核心业务。
  3. 监控的颗粒度决定响应速度
    • 不仅要监控节点级指标,还需细化到 Pod 的临时存储使用量。
扩展风险场景
  • • CI/CD 流水线:Jenkins Pod 的临时构建目录可能因大文件占满磁盘。
  • • 机器学习训练:训练任务的临时模型缓存需限制 emptyDir 大小。
  • • 临时数据库:Redis 或 SQLite 若使用 emptyDir,需设置存储阈值。
防御体系的金字塔模型
        熔断降级  
          ▲  
弹性存储架构  
          ▲  
资源限制(sizeLimit)  
          ▲  
实时监控告警  
          ▲  
默认安全配置  

通过分层防御,将存储风险从“事后补救”转变为“事前预防”,确保云原生系统的韧性。

结语

以上就是我们今天的内容,希望可以帮助到大家,在面试中游刃有余,主动出击。


 

往期回顾

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值