Docker容器日志占满磁盘怎么办?日志轮转与空间清理指南

"服务器磁盘突然 100%,网站挂了"——这是运维群里出现频率极高的场景,而罪魁祸首里,Docker 容器日志占大头。默认情况下,Docker 用 json-file 驱动记录容器里进程输出到标准输出的每一行,并且不设上限,日积月累就有单个日志文件大到几 GB、十几 GB 的情况。更糟的是,很多人第一次遇到这个问题时手忙脚乱地删容器、删日志,反而丢了现场或误删数据。这篇文章讲清 Docker 日志的存储机制,给出"事前预防"(配置全局与容器级日志轮转)和"事中清理"(安全地释放空间)两套方案,并明确哪些删除操作有风险。目标是让你以后不再被日志撑满磁盘搞崩服务器。

一、先搞懂 Docker 日志是怎么存的

Docker 默认的日志驱动是 json-file。容器里进程写入 stdout/stderr 的每一行,都会被 dockerd 收集,以 JSON 格式追加写入宿主上的日志文件。文件位置在:

# 容器日志文件的实际路径(<容器ID> 是完整 ID)
ls -lh /var/lib/docker/containers/<容器ID>/<容器ID>-json.log

# 快速找出占用最大的几个容器日志
du -sh /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -rh | head -10

# 用 docker 自己的视角看各容器日志大小
docker ps -s                       # 看容器可写层大小(不含日志)

特点很清楚:json-file 驱动默认不轮转、不限制大小,只要容器在跑、只要有输出,文件就一直长大。这就是"日志撑满磁盘"的根本原因。理解了这一点,解决思路就两个方向——要么限制它长多大(事前配置),要么把它清掉(事中处理)。

先看现象确认是不是日志的锅:

# 看磁盘整体使用
df -h
# 看 Docker 各类资源占用
docker system df -v
# 揪出最大的日志文件
du -ah /var/lib/docker/containers 2>/dev/null | sort -rh | head -20

二、事前预防:配置日志轮转

最稳妥的做法是一开始就把日志上限配好。Docker 支持两处配置:全局的(对所有新容器生效)和容器级的(针对单个容器)。

全局配置(推荐)

在 /etc/docker/daemon.json 里配置默认的日志驱动和上限:

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

参数含义:max-size 是单个日志文件的上限,超过就轮转(生成 .1、.2 后缀);max-file 是最多保留几个文件(含当前文件)。上面的配置意味着:单个日志最大 100 MB,最多保留 3 个,也就是一个容器最多占用约 300 MB 日志。这个量级对绝大多数服务够用,也能有效防止磁盘被打满。

改完要重载并重启 Docker:

sudo systemctl daemon-reload
sudo systemctl restart docker

风险提示:重启 Docker 会停止所有容器(取决于配置和 restart 策略,通常会停再拉起),务必在业务低峰期操作,并确认容器设置了 restart 策略以便自动恢复。同时,全局配置只对"新建的容器"生效,已经运行的老容器不会自动应用新配置,需要重建容器才生效——重建前确认数据在卷里,不会丢。

容器级配置

个别容器输出量特别大(比如访问日志很密的 Nginx),可以单独给它更严的限制,而不必全局收紧:

docker run -d --name web \
  --log-driver json-file \
  --log-opt max-size=50m \
  --log-opt max-file=5 \
  nginx:1.25-alpine

容器级配置优先于全局配置。max-size 支持的单位有 k、m、g,写 50m 即 50 MB。

换成更省空间的驱动

如果不想让日志以 JSON 形式存两份(原始行 + 元数据),可以换驱动:

驱动 特点 适用
json-file 默认,docker logs 可用,体积较大 通用,需 docker logs 查看
local Docker 自研,压缩存储,体积更小,docker logs 可用 推荐替代 json-file
syslog 转发到系统 syslog 集中日志收集
journald 转发到 systemd journal 系统统一管理日志
none 不记录 不需要日志的容器

local 驱动是很好的选择,它同样支持 max-size/max-file,但存储格式更紧凑:

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

需要提醒的是:切换日志驱动会影响 docker logs 的行为和已有日志的读取,syslog/journald 这类驱动下 docker logs 可能不再直接可用(要去系统日志里看)。切换前评估好你依赖哪种查看方式。

三、事中清理:安全地释放空间

如果磁盘已经被撑满,需要立刻清理。下面按"从安全到危险"排列,危险操作都标注了风险。

方式一:用 Docker 命令清理(推荐)

# 清理已停止的容器、未使用的网络、悬空镜像、未使用的构建缓存
docker system prune

# 更激进:连未使用的镜像和卷一起清(危险,见下方提示)
docker system prune -a --volumes

# 只清构建缓存
docker builder prune

# 只清未使用的卷(危险:会删除卷里的数据)
docker volume prune

风险提示:docker system prune -a 会删除所有未被容器使用的镜像,下次启动可能需要重新拉取;--volumes 会删除未被使用的卷,里面的数据不可恢复。执行带 -a 或 --volumes 的命令前,务必先用 docker volume ls、docker images 核对,确认没有还需要的数据。千万别在生产机器上不假思索地跑 prune -a --volumes。

方式二:truncate 单个日志文件(慎用)

日志文件是正在被 dockerd 写入的,直接 rm 删除文件并不会释放空间——因为文件句柄还被进程持有,磁盘空间要等进程关闭句柄才回收。正确做法是用 truncate 把文件清空:

# 把指定容器的日志文件截断为 0 字节(释放空间,文件保留)
# 注意:这是危险操作,会永久丢失该日志的历史内容,先确认不再需要
sudo truncate -s 0 /var/lib/docker/containers/<容器ID>/<容器ID>-json.log

风险提示:truncate -s 0 会永久丢弃该文件里的所有历史日志,且不可恢复。排查线上问题时日志是重要证据,执行前务必确认这些日志不再需要,或已经备份。另外,rm 后再 truncate 是无效的(文件都没了),正确顺序是先判断清楚再用 truncate 而非 rm。还有一种情况:如果你已经用 rm 删过,空间可能没释放,需要重启对应容器或重启 Docker 让句柄释放。

方式三:日志轮转工具

对 json-file 驱动,Docker 自身不提供外部 logrotate,但可以用系统 logrotate 对日志文件做轮转(注意与 Docker 的 max-size 机制不要冲突,通常二选一即可)。更推荐直接用第二节的 max-size/max-file,因为它由 Docker 管理、最不容易误伤。

如果确实要接 logrotate,思路是配一个规则定期 rotate、压缩、删除旧文件,并在 postrotate 里给 Docker 发信号重开文件。但这类自定义配置容易出错,建议只在有明确需求且有测试环境时采用。

四、磁盘监控:把问题挡在前面

日志清理只是补救,真正的解法是监控,让磁盘在到 80% 时就告警。几个实用做法:

# 方案 A:用 df 做简单阈值检查(放 cron 里,超阈值发告警/记录)
df -h /var/lib/docker | awk 'NR==2 {gsub("%","",$5); if ($5+0 > 80) print "磁盘告警: " $5 "%"}'

# 方案 B:监控单个容器日志增长速度
du -sh /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -rh | head -5

# 方案 C:接入监控系统(Prometheus node_exporter + 告警规则)
#   /var/lib/docker 所在分区的可用空间、inode 使用率都建议做告警

经验阈值:磁盘使用率 80% 告警、90% 严重告警;同时监控 inode 使用率(大量小文件也会把 inode 耗尽,df -i 查看)。inode 用满的表现和磁盘满很像,都会导致无法写文件,排查时别忘了看。

df -h        # 看空间
df -i        # 看 inode
docker system df -v   # 看 Docker 层面的占用明细

五、一份可落地的日志治理清单

把上面的内容整理成一套可执行的做法:

步骤 动作 说明
1 配置 daemon.json 的 max-size/max-file 全局兜底,防患于未然
2 对高输出容器单独收紧限制 如 Nginx 访问日志单独设更小上限
3 应用自身控制日志级别 生产别开 debug,减少无效输出
4 关键日志集中收集 用 syslog/日志系统收集,本地只留少量
5 磁盘与 inode 监控告警 80% 告警,提前介入
6 定期清理悬空资源 docker system prune(不含 -a --volumes)
7 紧急时 truncate 而非 rm 并做好"日志不再需要"的确认

第 3 条常被忽视但很关键:如果应用把 debug 日志全打开,日志量可能是正常时的几十倍。治本要从源头的日志级别控制做起,容器层面的限制只是兜底。

常见问题

Q:为什么我删了日志文件,磁盘空间没释放?
因为文件句柄还被 dockerd 持有。用 rm 删除后空间不会立即回收,要等进程关闭句柄(如重启容器或 Docker)。正确做法是用 truncate -s 0 清空内容,而不是删除文件本身。

Q:配置了 max-size 后老容器还是很大?
全局配置只对新建容器生效。已经在运行的老容器需要重建(docker rm 后重新 run,或用 Compose up -d --force-recreate)才会应用新配置。重建前确认数据在卷里。

Q:docker system prune -a 能随便用吗?
不能。它会删掉所有未被当前容器使用的镜像,下次启动要重新拉取,离线环境可能直接拉不到。带 --volumes 还会删卷数据。生产机器上执行前必须先看清将删除什么。

Q:local 驱动和 json-file 有什么区别?
local 是 Docker 自研的日志驱动,同样支持 max-size/max-file 和 docker logs,但存储更紧凑,通常占用更小。想省空间又不想改查看方式时,推荐切到 local。

Q:日志已经涨到几十 GB,最快怎么救急?
先 df -h 确认是日志导致,du 定位到具体容器;如果是可丢弃的日志,用 truncate -s 0 清空对应文件立即释放空间;然后立刻配置 max-size 防止再次发生。整个过程优先保住业务,别直接重启宿主机。

总结

默认的 json-file 日志驱动不设上限,这是容器日志撑满磁盘的根本原因;解决它要"事前限制"加"事后清理"两条腿走路。

事前在 daemon.json 配 max-size/max-file 做全局兜底,对高输出容器单独收紧,必要时把驱动换成压缩存储的 local;注意全局配置只对新容器生效,老容器要重建。

事中清理优先用 docker system prune(不含 -a --volumes);对正在写的日志文件,用 truncate -s 0 清空而非 rm 删除,否则空间不会释放。

所有删除类操作都有风险:prune -a --volumes 可能删掉需要的镜像和数据,truncate 会永久丢弃历史日志,执行前必须确认清楚,优先在低峰期操作。

治本还靠监控和应用自身的日志级别控制:磁盘与 inode 都做 80% 告警,生产环境别长期开 debug,让日志量从源头降下来,容器限制只作最后一道防线。