如何防止 json-file 驱动打爆宿主机磁盘

解读

在国内生产环境,容器日志默认走 json-file 驱动,直接写宿主机 /var/lib/docker/containers//-json.log。一旦应用日志量失控(如大促、死循环、DEBUG 全开),单个文件可达数十 GB,把系统盘撑满触发 No space left on device,导致 kubelet/docker 异常、Pod 被驱逐甚至节点 NotReady。面试官想确认你是否具备“事前容量规划 + 事中实时治理 + 事后兜底清理”的全链路闭环能力,而不仅仅是“切到 journald”这种一句话答案。

知识点

  1. json-file 驱动的写盘机制:每行日志追加到本地 JSON 文件,无自动压缩、无自动清理。
  2. Docker 日志轮转参数:max-size、max-file、compress、labels 等,只能在容器创建时生效,运行时不可热更新
  3. 国内云厂商系统盘普遍 40~100 GiB,日志与镜像、kubelet 复用同一块盘,极易竞争。
  4. logrotate 与 Docker 轮转并存时,双锁竞争可能丢日志或切出空文件
  5. 合规要求:金融、运营商场景要求日志留存 ≥6 个月,不能简单 rm -f,必须转储到 S3/OSS/日志平台后再清理。
  6. 内核参数:fs.inotify.max_user_watches 不足会导致 filebeat 侧漏采,日志堆积后二次打盘

答案

我采用“三层防线”策略,已在 20+ 生产集群落地,实现全年零因日志打盘故障:

  1. 全局默认模板
    在 /etc/docker/daemon.json 统一注入日志驱动参数,强制所有新建容器生效
    { "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "5", "compress": "true", "labels": "app,env,team", "env": "LOG_LEVEL" } }
    随后 systemctl reload docker,reload 不中断已运行容器,但新容器立即受控。

  2. 存量热治理
    对未设置限额的旧容器,使用自研脚本 docker-log-rotate.sh:

    • 通过 docker inspect 拿到日志路径,利用 copytruncate + flock 避免与业务写冲突
    • 按 50 MB 切分,gzip 压缩后上传到内网 MinIO,返回生命周期 7 天自动沉降
    • 上传成功再执行 echo '' > log.log 清空头文件,秒级释放磁盘空间
    • 整体对容器无重启、无感知。
  3. 兜底监控与告警

    • Prometheus + node_exporter 采集 (docker_container_log_size_bytes / 1024 / 1024) > 80 立即告警到飞书;
    • 配套 Grafana 面板按 app、node、team 维度聚合,值班同学 5 分钟内可定位 top10 容器
    • 磁盘使用率 >85% 触发自动扩容云盘,阿里云 ESSD 支持在线扩容无需停机
    • 每周巡检脚本扫描大于 30 天未访问的 *.log.gz,自动提交解冻-删除工单,满足审计要求

通过以上三层,把日志增长从不可控变为可预测,系统盘年增长率 <5%,彻底杜绝“日志打爆”事件。

拓展思考

  1. 若公司强制使用 journald,如何兼容存量 filebeat 配置?
    答:可在 docker-compose 里给业务容器加 logging.driver=journald 并挂载 /run/log/journal,filebeat 通过 journald input 读取,注意字段映射与 multiline 正则保持一致

  2. 对短时 Job 容器,json-file 轮转意义不大,更优方案是 gelf 或 fluentd 驱动直接走 UDP 到外置日志栈,容器退出即自动释放空间,但需评估网络抖动导致丢日志的风险

  3. 在边缘节点(2C4G 盒子)上,磁盘仅 32 GB,可关闭容器日志落盘:
    docker run --log-driver=none ...
    业务日志通过 stdout -> memory buffer -> MQTT 上传到中心需预留 200 MB 内存给 ring-buffer,并做 QoS1 重传,防止日志丢失。