切换语言
切换主题

Docker日志清理完全指南:防止磁盘被json.log撑爆的5种方法

Docker日志清理与管理示意图

凌晨3点17分,手机震动把我从浅睡中拽了出来。

屏幕上跳着刺眼的红色告警:“生产服务器磁盘使用率100%,所有服务已停止响应”。那一刻我的心都凉了——这是一个承载着几十万用户的电商平台,每停1分钟都意味着真金白银的损失。

我立刻SSH登录服务器,df -h显示根分区确实已经满了。跑了一圈检查,最后在/var/lib/docker/containers/目录下发现元凶:一个容器的xxx-json.log文件居然有82GB

说实话,我当时就懵了。这个容器运行得好好的,怎么日志就膨胀成这样了?

后来我才知道,这不是个例。Docker默认不会限制日志文件大小,stdout和stderr的所有输出都会写到json.log文件里,然后一直增长、增长、再增长,直到把你的磁盘撑爆为止。

如果你也被Docker日志搞得焦头烂额,别担心。这篇文章会教你:怎么应急清理大日志文件、怎么配置日志轮转防止复发、怎么选择适合你的日志驱动。我把踩过的坑都整理出来了,希望能帮你避开。

为什么Docker日志会撑爆磁盘

Docker的日志存储机制

先说说Docker是怎么处理日志的。

当你的容器通过console.log()System.out.println()或者任何方式往stdout和stderr输出内容时,Docker会把这些内容全部写到一个JSON格式的日志文件里。这个文件藏在/var/lib/docker/containers/<container-id>/<container-id>-json.log

听起来挺正常的对吧?问题就在于——Docker默认不会做任何日志轮转

什么意思呢?就是这个日志文件会一直增长、增长、再增长,永远不会自动分割或删除旧日志。你的容器跑一天是1GB,跑一周就是7GB,跑一个月就是30GB。如果应用特别话痨(比如日志级别设成了DEBUG),那增长速度更是快得吓人。

我算过一笔账:一个中等流量的Web应用,如果每秒输出100条日志,每条日志平均100字节,那一天下来就是:

100条/秒 × 86400秒 × 100字节 ≈ 864MB/天 ≈ 25GB/月

如果你有10个这样的容器,不到一个月就能把100GB的磁盘撑满。而且这还是保守估计。

哪些场景最容易中招

从我自己和身边朋友的经历来看,这几种情况最危险:

1. 应用日志级别太低

开发时为了debug方便,把日志级别设成INFO甚至DEBUG,然后部署到生产环境忘记改回来了。结果每个HTTP请求都打印一堆调试信息,日志疯狂增长。

我见过最夸张的,一个Node.js服务因为开启了请求体日志,每次用户上传图片都把整个base64字符串写进去,一个请求就是几MB的日志。三天不到,200GB磁盘就满了。

2. 程序出现循环报错

这个更致命。如果你的应用因为某个bug陷入死循环,不断抛出异常和堆栈信息,那日志文件的增长速度能把你吓哭。

有次我们一个微服务因为数据库连接池配置问题,每秒钟重试几百次连接,每次都打印完整的异常堆栈。结果3小时内,一个容器的日志从2GB飙到了120GB,直接把磁盘搞挂了。

3. 长期运行的生产容器

如果你的容器在生产环境连续运行几个月甚至几年,而且没配置日志轮转,那日志文件累积起来是相当可观的。

我曾接手过一个项目,有个运行了8个月的Nginx容器,日志文件已经到了150GB。每次用docker logs查看日志都要卡半天,因为Docker要读取这个巨大的文件。

4. 高流量应用

访问量大的应用自然日志也多。一个日PV几百万的网站,即使每个请求只打印一行正常的访问日志,累积起来也是天文数字。

关键是很多人意识不到这个问题,直到某天凌晨磁盘满了系统崩溃,才发现原来Docker日志是这么大的坑。

应急清理:立即释放磁盘空间

好了,现在磁盘已经满了,服务挂了,老板在群里疯狂@你。这时候别慌,先把空间腾出来再说。

第一步:找出罪魁祸首

你得先知道哪些日志文件最大。跑这个命令:

find /var/lib/docker/ -name "*.log" -exec ls -sh {} \; | sort -h -r | head -20

这会列出最大的20个日志文件,按大小排序。你会看到类似这样的输出:

82G /var/lib/docker/containers/a1b2c3d4.../a1b2c3d4...-json.log
35G /var/lib/docker/containers/e5f6g7h8.../e5f6g7h8...-json.log
12G /var/lib/docker/containers/i9j0k1l2.../i9j0k1l2...-json.log
...

找到最大的几个,记下container ID(就是那一串长字符)。

如果你想知道某个具体容器的日志在哪里,可以用这个命令:

docker inspect --format='{{.LogPath}}' <container_name_or_id>

比如:

docker inspect --format='{{.LogPath}}' nginx
# 输出:/var/lib/docker/containers/abc123.../abc123...-json.log

第二步:安全地清空日志

重点来了:千万别直接rm删除日志文件!

我一开始也犯过这个错,直接rm -f xxx-json.log,结果Docker完全懵逼了。因为Docker进程一直持有这个文件的句柄,你删掉文件后,Docker还以为文件在那儿,继续往里写,但实际上那块磁盘空间并没有被释放。

正确的做法是清空文件内容,而不是删除文件

方法1:使用cat命令清空

cat /dev/null > $(docker inspect --format='{{.LogPath}}' <container_id>)

这个命令把空内容写进日志文件,文件还在,但大小变成了0。Docker进程不会有任何感知,继续正常工作。

比如清空nginx容器的日志:

cat /dev/null > $(docker inspect --format='{{.LogPath}}' nginx)

方法2:使用truncate命令(推荐)

truncate -s 0 $(docker inspect --format='{{.LogPath}}' <container_id>)

truncate更直接,专门用来截断文件。-s 0表示把文件大小设为0字节。

清空nginx容器日志:

truncate -s 0 $(docker inspect --format='{{.LogPath}}' nginx)

方法3:批量清空所有容器日志

如果你的所有容器日志都很大,想一次性全清了,可以用这个命令:

sudo sh -c 'truncate -s 0 /var/lib/docker/containers/*/*-json.log'

注意:这个命令会清空所有容器的所有日志!慎用!用之前最好确认一下你不需要保留任何历史日志。

我一般只在应急情况下用,平时还是建议逐个清理,避免误删重要日志。

验证效果

清理完后,跑一下df -h看看磁盘空间:

df -h /var/lib/docker/

应该能看到使用率明显下降了。如果没下降,可能是因为某些容器还在疯狂写日志,你需要先解决那个应用的问题(比如关闭DEBUG日志、修复循环报错的bug)。

我当时清理完那个82GB的日志文件后,磁盘使用率从100%一下子降到了60%,服务立刻恢复了。但心里很清楚:这只是治标不治本。要彻底解决问题,得配置日志轮转。

治本之策:配置日志轮转

应急清理只能解燃眉之急。要彻底解决问题,你得让Docker自动管理日志大小,别让它无限增长。

全局配置:修改daemon.json

最简单也最常用的方法,就是在Docker的全局配置文件里设置日志轮转参数。

配置文件位置/etc/docker/daemon.json

如果文件不存在,就创建一个。然后加入这些内容:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3",
    "compress": "true"
  }
}

来解释一下这几个参数:

  • max-size:单个日志文件的最大大小。到了这个大小就会轮转(创建新文件)
  • max-file:最多保留几个日志文件。超过这个数量就删除最旧的
  • compress:轮转后是否压缩旧日志文件,省空间

在这个配置下,每个容器最多占用 10MB × 3 = 30MB 的日志空间(不算压缩)。

生产环境建议值

根据我的经验和业界实践,生产环境可以这么设:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3",
    "compress": "true"
  }
}

max-size: "100m"给了更多的日志保留空间,方便排查问题。max-file: "3"保留3个文件,相当于最近300MB的日志历史。

你也可以根据实际情况调整:

  • 日志量小的应用:max-size: "10m", max-file: "3"
  • 日志量中等的应用:max-size: "50m", max-file: "3"
  • 日志量大的应用:max-size: "100m", max-file: "5"

重要提醒:所有的配置值都必须用引号括起来,因为Docker要求是字符串格式。写成"max-size": 10m(没引号)会报错。

重启Docker使配置生效

修改完daemon.json后,必须重启Docker服务:

sudo systemctl restart docker

重启后,Docker会加载新配置。但是——划重点——这个配置只对新创建的容器生效,已经在运行的容器不会受影响!

那怎么办?你得重建容器。

如果用的是docker run启动的容器:

docker stop <container_name>
docker rm <container_name>
docker run [原来的参数] <image>

如果用的是docker-compose

docker-compose down
docker-compose up -d

down会停止并删除容器,up -d会用新配置重新创建。这时候新容器就会应用日志轮转配置了。

针对单个容器配置

有时候你不想改全局配置,只想给某个特别话痨的容器限制日志。可以在启动容器时指定:

使用docker run

docker run -d \
  --name my-app \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  nginx:latest

使用docker-compose.yml

version: '3.8'
services:
  web:
    image: nginx:latest
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
        compress: "true"

这种方式的好处是灵活,不同容器可以有不同的日志策略。比如:

  • Nginx容器可以保留更多日志:max-size: "50m", max-file: "5"
  • 一个不太重要的定时任务容器:max-size: "5m", max-file: "2"

验证配置是否生效

怎么确认配置真的生效了呢?用docker inspect查看容器的日志配置:

docker inspect <container_name> | grep -A 10 LogConfig

你会看到类似这样的输出:

"LogConfig": {
    "Type": "json-file",
    "Config": {
        "max-file": "3",
        "max-size": "10m",
        "compress": "true"
    }
}

如果看到的是{}空对象,说明容器还是用的默认配置(无限增长),那你就得重建容器。

我当时配置完之后专门写了个小脚本,检查所有容器的日志配置,确保没有漏网之鱼。后来再也没被日志撑爆磁盘的问题困扰过。

日志驱动选择指南

前面说的都是基于默认的json-file驱动。但Docker其实支持好几种日志驱动,各有各的用途。

你可能会问:我该用哪个?

json-file(默认驱动)

这是Docker的默认选择。

优点

  • 支持docker logs命令,排查问题很方便
  • 配置简单,加上max-sizemax-file就行
  • 本地存储,访问快

缺点

  • 默认不轮转,容易撑爆磁盘(这也是我们今天讨论的主要问题)
  • 日志存在宿主机上,容器删了日志也没了

适用场景:大多数场景。只要配置好日志轮转,这个驱动足够用了。

推荐配置

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "3",
    "compress": "true"
  }
}

local驱动(推荐)

如果你用的是Docker 18.09或更新版本,我更推荐这个。

优点

  • 自动轮转,默认就会限制日志大小,不需要手动配置
  • 文件格式更高效,比json-file性能好
  • 也支持docker logs命令

缺点

  • 需要Docker 18.09+
  • 日志格式是二进制的,不能直接cat查看(不过一般也不需要)

适用场景:生产环境强烈推荐。省心,性能好。

配置方法

{
  "log-driver": "local",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}

我现在新项目都用这个驱动,确实比json-file省事。

journald驱动

如果你的服务器用的是systemd(比如Ubuntu 16.04+、CentOS 7+),可以考虑这个。

优点

  • 与系统日志集成,统一管理
  • 自动轮转,不会撑爆磁盘
  • 结构化日志,带元数据(容器ID、容器名等)
  • 支持docker logsjournalctl命令

缺点

  • 只能在systemd系统上用
  • 如果不熟悉journald,可能有学习成本

适用场景:如果你的运维体系已经基于systemd和journald,用这个能保持一致性。

配置方法

{
  "log-driver": "journald"
}

journalctl查看容器日志:

journalctl -u docker.service -f CONTAINER_NAME=my-app

syslog驱动

这个主要是把日志发到syslog服务器。

优点

  • 可以发到远程syslog服务器,做集中式日志管理
  • 支持TCP和UDP,TCP更可靠
  • 如果已有syslog基础设施,可以无缝集成

缺点

  • 不支持docker logs命令(这是个大问题,排查问题不方便)
  • 可能有网络延迟
  • 需要配置和维护syslog服务器

适用场景:大规模集群,已有成熟的syslog集中日志系统。

配置方法

{
  "log-driver": "syslog",
  "log-opts": {
    "syslog-address": "tcp://192.168.1.100:514",
    "syslog-facility": "daemon",
    "tag": "{{.Name}}/{{.ID}}"
  }
}

老实讲,如果不是特别需要,我不太推荐用syslog。因为docker logs不能用了,排查问题真的很不方便。

我的建议

根据你的场景选:

场景推荐驱动原因
单机或小集群local 或 json-file+轮转简单可靠,支持docker logs
systemd环境journald与系统日志集成,统一管理
大规模集群fluentd/loki + 本地驱动集中式日志,但本地保留便于应急
已有syslogsyslog + 本地驱动双保险,远程+本地都有

我自己的实践是:小项目用local驱动,大项目用json-file配合Loki做集中式日志,本地保留最近几百MB用于应急排查。

不管选哪个驱动,记住一个原则:一定要限制本地日志大小。别让它无限增长,否则早晚会撑爆磁盘。

最佳实践与运维建议

解决了眼前的问题,我们来聊聊长期运维的经验。

生产环境配置checklist

如果你负责生产环境,建议照着这个清单过一遍:

1. 全局日志配置

{
  "log-driver": "local",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}

或者用json-file:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3",
    "compress": "true"
  }
}

2. 检查所有容器配置

写个脚本检查所有容器是否应用了日志轮转:

#!/bin/bash
for container in $(docker ps -q); do
  name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
  logconfig=$(docker inspect --format='{{json .HostConfig.LogConfig}}' $container)
  echo "容器: $name"
  echo "日志配置: $logconfig"
  echo "---"
done

跑一遍,确保没有漏网之鱼。

3. 设置监控告警

别等到磁盘满了才知道。提前设置告警:

  • 磁盘使用率 > 80%:警告
  • 磁盘使用率 > 90%:严重告警
  • /var/lib/docker/containers/目录大小超过阈值:警告

我用的是Prometheus + Grafana + Alertmanager,配置很简单:

- alert: HighDiskUsage
  expr: (node_filesystem_size_bytes - node_filesystem_free_bytes) / node_filesystem_size_bytes > 0.8
  for: 5m
  annotations:
    summary: "磁盘使用率超过80%"

4. 定期巡检

每周或每月跑一次日志检查脚本,看看哪些容器的日志增长最快:

# 查看Docker目录总大小
du -sh /var/lib/docker/

# 查看日志目录大小
du -sh /var/lib/docker/containers/

# 找出最大的日志文件
find /var/lib/docker/containers/ -name "*-json.log" -exec du -h {} \; | sort -h -r | head -10

发现异常及时处理。

应用层优化建议

Docker日志管理是基础设施层面的事,但应用层也要配合。

1. 调整日志级别

生产环境别用DEBUG或INFO级别,太话痨了。建议:

  • 生产环境:WARN或ERROR
  • 测试环境:INFO
  • 开发环境:DEBUG

这样能减少至少80%的日志量。

2. 使用专门的日志系统

如果你的应用日志量特别大,别指望Docker日志能hold住。应该:

  • 应用直接写到ELK(Elasticsearch + Logstash + Kibana)
  • 或者用Loki + Grafana(更轻量)
  • 或者用云厂商的日志服务(阿里云SLS、腾讯云CLS等)

Docker日志只保留最近的少量日志,用于应急排查。

3. 结构化日志

输出JSON格式的结构化日志,方便后续分析:

// 不好的做法
console.log("用户登录成功,用户ID: " + userId);

// 好的做法
console.log(JSON.stringify({
  level: "info",
  event: "user_login",
  userId: userId,
  timestamp: new Date().toISOString()
}));

结构化日志更容易检索和分析。

4. 日志采样

如果某些日志量实在太大(比如每个HTTP请求都记录),可以考虑采样:

// 只记录10%的请求日志
if (Math.random() < 0.1) {
  console.log("HTTP请求详情...");
}

或者只在出错时记录详细信息:

if (error) {
  console.log("详细请求信息: ", requestDetails);
}

常见问题FAQ

Q1: daemon.json配置后重启Docker会影响运行中的容器吗?

A: 不会。重启Docker不会停止容器,容器会继续运行。但配置只对新容器生效,已有容器需要重建。

Q2: 清空日志文件会影响应用运行吗?

A: 不会。Docker进程一直持有文件句柄,你truncate文件后,Docker继续往里写,应用完全感知不到。

Q3: 如果用了log-driver=journald,还需要配置max-size吗?

A: 不需要。journald自己会管理日志大小,有自己的轮转机制。

Q4: 已经配置了日志轮转,为什么日志还是很大?

A: 检查两点:

  1. 容器是否重建过?daemon.json只对新容器生效
  2. 应用是否输出了超大的单行日志?日志轮转是按文件大小,不是按行数

Q5: 能直接删除/var/lib/docker/containers/目录下的旧日志文件吗?

A: 如果是已经轮转的旧文件(比如xxx-json.log.1.gz),可以删。但当前的xxx-json.log不要删,用truncate清空。

注意事项总结

最后总结几个关键点,别踩坑:

  1. daemon.json配置只对新容器生效,已有容器要重建
  2. 配置参数必须用引号,比如"max-size": "10m"而不是"max-size": 10m
  3. truncate安全,rm危险,清空日志用truncate,不要删除文件
  4. 不同日志驱动对docker logs支持不同,syslog不支持,json-file和journald支持
  5. 修改daemon.json后要重启Docker,用systemctl restart docker
  6. 定期检查日志配置,防止新部署的容器忘记配置

说了这么多,其实核心就一句话:提前配置,定期检查,别等磁盘爆了再救火

结论

回到文章开头那个凌晨3点的告警。

当时我花了半小时清理日志,2小时配置日志轮转和监控,然后给所有容器都重建了一遍。折腾到早上6点,终于搞定。

但这次事故也让我学到了一课:Docker日志管理绝对不能轻视。它就像定时炸弹,平时看不出来,一旦爆发就是大麻烦。

总结一下这篇文章的核心要点:

应急时:用truncate清空大日志文件,立即释放空间,让服务恢复。

预防时:在daemon.json配置日志轮转(max-size + max-file),选择合适的日志驱动(推荐local或json-file),重建所有容器使配置生效。

长期运维:设置磁盘监控告警,定期检查日志配置,优化应用日志输出。

如果你现在正在用Docker,不管是开发还是生产环境,我建议你立刻去检查一下:

  1. 跑一遍find /var/lib/docker/ -name "*.log" -exec ls -sh {} \; | sort -h -r | head -10,看看有没有超大日志文件
  2. 检查/etc/docker/daemon.json是否配置了日志轮转
  3. docker inspect确认所有容器都应用了日志限制

别等到凌晨被告警吵醒的那一刻,才意识到这个问题。

如果这篇文章帮到了你,欢迎分享给更多可能踩坑的朋友。大家一起少踩点坑,多睡点觉。

15 分钟阅读 · 发布于: 2025年12月18日 · 修改于: 2025年12月26日

评论

使用 GitHub 账号登录后即可评论

相关文章