Docker json-file 日志大小限制:容器日志清理与轮转配置

Docker json-file 日志轮转配置封面:容器日志被限制在固定大小并循环覆盖

如果你用 docker logs 查过某个容器,发现输出滚动了几万行还停不下来,或者服务器磁盘在没有任何业务增长的情况下被慢慢写满,那大概率是容器日志没有配置轮转。Docker 默认的 json-file 日志驱动不会自动限制大小,一个高频输出错误的容器,几天就能把几十 GB 的磁盘吃光,最终导致数据库写入失败、容器异常重启。这篇文章教你如何解决这个问题:先弄清楚日志去哪了、占了多少,再通过 max-size 和 max-file 两个参数把日志总量锁定在一个可控范围内,最后给出全局配置和单容器配置两种落地方案。

为什么 json-file 日志会无限增长

Docker 容器的标准输出和标准错误,默认由 json-file 驱动接收,写成宿主机上的一个 JSON 文件,路径通常是 /var/lib/docker/containers/<容器id>/<容器id>-json.log。关键问题在于:这个文件默认没有任何大小上限,也没有轮转机制。只要容器进程持续往 stdout 写内容,这个文件就一直追加,直到磁盘写满为止。

实际场景里最容易出问题的是这几类容器:业务代码里把异常堆栈打进循环重试逻辑,每秒输出几十条相同错误;第三方探针或 SDK 的调试级别日志没有关闭;应用把访问日志同时写到 stdout,流量高峰时日志量随请求线性上涨。前两类属于“异常刷屏”,日志增速远超正常业务量;第三类虽然增长正常,但没有上限保护时,一次流量峰值同样可能把磁盘打满。三种情况的共同点是:日志文件本身没有自我保护机制,风险完全转嫁给了宿主机磁盘。

你可以在服务器上用一条命令快速确认风险敞口:

du -sh /var/lib/docker/containers/*/*-json.log | sort -rh | head -10

这条命令会列出当前占用空间最大的 10 个容器日志文件。如果最大的一个已经超过 1GB,或者多个日志加起来接近磁盘总量的 10%,就说明轮转配置必须尽快补上。关于存储容量和性能排查的整体思路,也可以参考 服务器相关文章,把磁盘、内存、CPU 三个维度分开判断。

max-size 和 max-file:两个参数锁定日志总量

解决 json-file 日志无限增长的核心方案,是给日志驱动加两个参数:max-size 限制单个日志文件的最大体积,max-file 限制轮转后保留的文件数量。两者配合,日志总占用量的上限就等于 max-size × max-file。

举个具体例子:配置 max-size=10m、max-file=3,意味着单个日志文件写到 10MB 就会轮转,最多保留 3 个文件。日志文件会以 -json.log、-json.log.1、-json.log.2 的形式循环覆盖,最旧的内容被最新的替换掉。这样单个容器的日志占用永远不超过 30MB,一台跑 20 个容器的服务器,日志总量上限也只有 600MB 左右,完全在可控范围内。

参数取值没有统一标准,可以按业务特点估算:一般 Web 应用容器,单文件 10MB 到 50MB、保留 3 到 5 个文件就足够排障使用;日志密集型的数据同步或爬虫类容器,可以适当放宽到单文件 100MB;对于几乎不输出日志的基础设施容器,用默认配置也不会有风险。我们建议的做法是宁可单文件小一点、多保留几个文件,这样 docker logs 每次读取的数据量小,翻历史日志也更方便。

需要注意一个容易踩的坑:max-size 和 max-file 只对新创建的容器生效。已经在运行的容器不会因为修改了配置而自动获得轮转能力,必须重建容器(重新 docker run 或重新 docker compose up)才能应用新的日志参数。这一点在规划变更窗口时要提前算进去。

全局配置:daemon.json 一次覆盖所有新容器

如果你希望服务器上所有新建容器默认都带轮转,不用每个容器单独写参数,最省事的方式是修改 Docker 守护进程的全局配置文件 /etc/docker/daemon.json:

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

修改后需要重载 Docker 服务让配置生效:

sudo systemctl reload docker

配置生效后,可以用一条命令验证某个容器的实际日志配置:

docker inspect -f '{{.HostConfig.LogConfig}}' 容器名

输出里如果能看到 max-size:10m 和 max-file:3,说明轮转已经生效。这里要区分两种结果:如果容器是在修改配置之后创建的,它会自动继承全局配置;如果容器创建于配置修改之前,inspect 结果里 Config 部分会是空的 map[],这类容器必须重建才能获得轮转能力。

全局配置有一个现实约束:它只能影响“新建容器”。对于已经在生产环境长期运行、不能随意重启的服务(比如数据库容器),可以先把全局配置落地,让之后新建的容器都有保护,再挑维护窗口逐个重建存量容器。分两批走,能把变更风险降到最低。

Docker daemon 全局日志配置覆盖各容器的层级示意

单容器配置:docker run 与 compose 的写法

当某个容器需要不同于全局默认的日志策略时,可以在创建时显式指定参数。用 docker run 的写法是通过 --log-driver 和 --log-opt:

docker run -d \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  nginx:latest

如果你习惯用 Docker Compose 管理多容器服务,等价配置写在服务的 logging 段里。关于 compose 的整体用法,可以先看这篇 Docker Compose 多服务编排教程,再回来加日志配置:

services:
  web:
    image: nginx:latest
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

单容器配置优先级高于全局配置,所以同一台服务器上可以共存多种策略:大多数业务容器走全局默认的 10MB × 3,个别日志量特别大的容器单独放宽到 50MB × 5,关键业务的容器反而可以收紧保留期。组合使用比一刀切更贴近真实运维需求。

json-file 日志轮转前后对比示意

另外提醒一点:如果你的容器使用了 bind mount 把日志直接写到宿主机文件,或者应用自己做了日志切割(比如输出到按日期命名的文件),那么 json-file 轮转参数管不到这些文件,需要另用 logrotate 之类的工具处理。本文讨论的范围是容器 stdout/stderr 的默认采集路径,这也是大多数容器日志占满磁盘问题的真正来源。

旧日志清理:先止血,再补轮转

给新容器配上轮转之后,存量的大日志文件还需要单独清理一次,否则磁盘空间不会自己回来。清理前先确认两个前提:业务已经不需要这份历史日志做排障,或者已经把需要的部分导出归档。

最直接的清理方式是清空日志文件内容而不是删除文件本身。直接 rm 掉 -json.log 文件会有一个问题:容器进程持有的文件句柄还在,删除后空间不会立刻释放,必须重启容器才行。更稳妥的做法是用 truncate 把文件截断为零:

truncate -s 0 /var/lib/docker/containers/<容器id>/<容器id>-json.log

这条命令立即释放空间,且不影响容器运行。如果服务器上容器很多,可以批量处理所有超过 100MB 的日志文件:

shopt -s globstar
for f in /var/lib/docker/containers/*/*-json.log; do
  size=$(stat -c%s "$f")
  if [ "$size" -gt 104857600 ]; then
    truncate -s 0 "$f"
  fi
done

清理完成后,用 df -h 确认磁盘占用已经回落。止血只是第一步,真正避免问题复发的,还是前面两节的全局轮转配置和容器重建计划。日志清理这类操作涉及生产数据,如果你更希望把服务器层面的维护交给服务商处理,也可以了解 Hostease 的 VPS(虚拟专用服务器)主机 方案,技术支持可以协助处理磁盘和日志类的运维问题。

常见问题与验证清单

落地过程中有几个高频问题提前说明。第一,改了 daemon.json 但老容器日志仍然无限增长——这不是配置失效,而是老容器需要重建,检查 docker inspect 的 LogConfig 就能确认。第二,docker logs 突然看不到更早的日志——这是轮转覆盖的正常表现,max-file 之外的旧文件已经被循环替换,重要日志要靠外部采集(如 Fluentd、Vector)而不是依赖本地文件无限保留。第三,容器重启后日志“消失”了——json-file 的日志随容器删除而删除,docker rm 之后再 docker logs 自然为空,这属于设计行为而非故障。

最后给一份可以直接对照执行的验证清单:先用 du -sh 确认当前最大的容器日志体积,记录基线;然后检查 /etc/docker/daemon.json 是否已配置 log-opts;再用 docker inspect 抽查 3 到 5 个核心容器的 LogConfig,确认轮转参数已生效;最后观察一周,确认磁盘占用曲线平稳、没有单文件突破 max-size 上限。四步都通过,json-file 日志的风险就算真正收口了。如果你的业务已经到了需要系统性规划日志与监控的阶段,建议把日志轮转和磁盘容量预警一起纳入巡检清单,参考 Docker 日志磁盘爆满排障指南 里的完整排障流程,把“清理—轮转—预警”形成闭环。