Docker 日志别放任:json-file 塞爆磁盘怎么办

Docker 默认把容器日志全写进 json-file,跑几个月磁盘就满了。小够教你看日志的各种姿势、限制日志大小、定期清理、local 驱动,以及什么时候该上集中收集。

我是小够,只推够用的 VPS。

有个经典翻车:VPS 跑得好好的,某天网站挂了,一看——磁盘 100% 满了。凶手经常是 /var/lib/docker/containers/ 下面几个 GB 的 *-json.log。Docker 默认的日志驱动叫 json-file,容器输出的每一行日志它都存,永不删除。不管它,等于埋了颗定时炸弹。

先看看自己中没中招

sudo du -sh /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -rh | head

如果看到几百 MB 甚至几个 GB 的日志文件,就是中招了。再看一眼整体:

docker system df

看日志的各种姿势

docker logs 容器名              # 看全部日志
docker logs --tail 100 容器名   # 只看最后 100 行(最常用)
docker logs -f 容器名            # 持续跟踪,Ctrl+C 退出
docker logs --since 1h 容器名    # 看最近一小时的
docker logs --since 2025-07-13T10:00 --until 2025-07-13T12:00 容器名  # 按时间段
docker logs -t 容器名            # 每行前面加时间戳

compose 用户把 docker 换成 docker compose,后面可以跟服务名:docker compose logs -f web。排错时先 --tail 看最近的,别一上来就全量输出,几 GB 的日志能把终端卡死。-t 加时间戳在对时间线时很有用,比如"我是 10 点改的配置,报错是什么时候开始的"。

治标:限制日志大小

Docker 守护进程的配置文件 /etc/docker/daemon.json(没有就新建):

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

意思是:单个日志文件最多 10MB,保留 3 个轮转,超了就删最老的。改完重启 Docker 生效:

sudo systemctl restart docker

注意:这个配置只对新建的容器生效,已经在跑的容器不受影响。已经在跑的,要么重建,要么手动清一次:

# 清空某个容器的日志(容器不用停)
sudo truncate -s 0 /var/lib/docker/containers/<容器ID>/*-json.log

别直接 rm 删文件——文件被进程占着,删了空间也不释放,truncate 清空才是正解。这是个 Linux 基础知识点,顺手记下来:删文件不释放空间,是因为进程还拿着文件句柄。

治本:compose 里单独配

如果不想动全局配置,也可以在 compose 文件里给单个服务配:

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

local 驱动:更省磁盘的选择

Docker 自带的 local 驱动,专为"省磁盘"设计:

logging:
  driver: local
  options:
    max-size: "10m"
    max-file: "3"

和 json-file 用法一样,但存储格式更紧凑,官方说能省一半左右空间。缺点是 docker logs 之外的工具兼容性差一点(比如某些日志收集 agent 只认 json-file)。个人用户随便选,小够用 json-file,图的是通用。

进阶:什么时候换日志驱动

个人 VPS 上,json-file + 大小限制完全够用。什么情况下要换?

驱动适合
json-file(默认)个人用户,配好大小限制走天下
local想再省点磁盘
none完全不想留日志,日志直接丢弃
syslog / gelf日志要集中收集、多台机器统一看

driver: none 适合那种日志毫无价值的容器,但排错时会抓瞎,慎用。多台机器的集中收集(比如 ELK、Loki)是进阶话题,个人用户一台机器没必要上,知道有这条路就行。

应用日志和容器日志分开想

容器日志是 stdout/stderr;应用自己写文件的日志(比如 Nginx 的 access.log 写进容器里),走的是可写层,容器重建就没了。重要的应用日志要么挂数据卷,要么直接打到 stdout 让 Docker 管——后者是云原生推荐做法。Nginx 官方镜像默认就是把访问日志打到 stdout,所以你 docker logs 能直接看到访问记录,设计得很贴心。

自己写应用的话,记住这个原则:日志打 stdout,别写文件。写文件还要操心轮转、清理、持久化,全是坑。

顺手做的三件事

第一,日志轮转配好后,顺手看一眼磁盘。 df -h 确认根分区还有余量,Docker 默认把什么都放 /var/lib/docker,小硬盘 VPS(20G 那种)要尤其注意。

第二,加个定时检查。 把磁盘使用率检查写进每周巡检,cron 每周跑一次,超过 80% 就告警。磁盘满是渐进式的,早发现早处理,别等网站挂了才知道。

第三,日志也是备份的一部分吗? 一般不是。日志的价值在于排错,过期了就没用。备份的是数据,不是日志。别把几个 GB 的日志也打进备份包,浪费空间。

兜底:logrotate 守最后一道门

daemon.json 配了轮转,基本够了。但小够习惯再加一道系统级的 logrotate,防"某个容器没走 daemon.json 配置"的漏网之鱼。/etc/logrotate.d/docker-logs:

/var/lib/docker/containers/*/*.log {
    rotate 7
    daily
    compress
    missingok
    delaycompress
    copytruncate
}

copytruncate 是关键:Docker 一直拿着日志文件的句柄,直接 mv 会出问题,copytruncate 是"复制一份再清空原文件",进程无感。双保险,睡觉安稳。

日志里的敏感信息

应用日志经常不小心打出密码、token:数据库连接串、API Key。养成两个习惯:一是应用配置里把日志级别设成 info,别开 debug 跑生产——debug 日志最爱打印敏感信息;二是定期 grep -i password 扫一眼日志,看看有没有漏。日志文件权限默认只有 root 能读,别为了图方便改成所有人可读。

集中收集:什么时候上 Loki

一台机器 docker logs 够用,三台以上就该考虑集中收集了。轻量方案是 Grafana Loki:一个容器收所有日志,配个 Promtail 转发,Web 界面按服务名、时间查。比 ELK 轻一个数量级,个人多机用户可以试试。但记住:一台机器别折腾这个,docker logs + 轮转就是最优解,工具是为规模服务的。