Docker容器日志默认写本地磁盘会不会把系统盘撑爆
Docker容器日志默认写本地磁盘,会不会把系统盘撑爆
会,而且这类问题在线上并不少见。
Docker 默认情况下会把容器标准输出 stdout 和标准错误 stderr 写到宿主机本地磁盘。很多业务容器启动时习惯把日志直接打到控制台,开发环境看起来很方便,docker logs 一敲就能看到。但到了生产环境,如果没有限制日志大小,日志文件会一直增长,最后把 /var/lib/docker 所在分区写满。
实际使用中发现,很多机器不是业务数据撑爆的,而是 Docker 日志、overlay2、临时文件一起把系统盘吃干。尤其是系统盘只有 40GB、60GB 的云服务器,跑几个 Java、Node.js、Nginx 容器,debug 日志没关,几天就能出问题。
Docker 默认日志到底写在哪里
默认 Docker log driver 通常是 json-file。容器日志路径一般在:
/var/lib/docker/containers/
可以用下面的命令看当前 Docker 使用的日志驱动:
docker info | grep "Logging Driver"
如果输出是:
Logging Driver: json-file
那就说明容器 stdout、stderr 默认会落到本地 json 文件里。
单个容器日志文件可以这样查:
docker inspect --format='{{.LogPath}}' 容器名或容器ID
再看大小:
ls -lh $(docker inspect --format='{{.LogPath}}' 容器名或容器ID)
这里补充一点,docker logs 看到的内容,本质上也是从这些日志文件里读出来的。不是 Docker 帮你存到什么远端日志系统里,也不是自动帮你清理。
默认会不会自动轮转
默认 json-file 驱动没有开启 max-size 限制时,日志文件可以一直写下去。也就是说,只要容器持续输出日志,宿主机磁盘就会一直被消耗。
不少人误以为 Docker 会像系统日志一样自动 rotate,这个认知很容易踩坑。Docker 可以配置日志轮转,但不是所有环境默认都配好了。
可以看某个容器的日志配置:
docker inspect 容器名或容器ID | grep -A 10 LogConfig
如果看到类似:
"Type": "json-file"
"Config": {}
那基本就是没有限制。
日志增长有多快,算一下就不玄学了
容器日志撑爆系统盘,很多时候不是“突然发生”,而是日志量本来就很大,只是没人盯。
场景 | 粗略估算
Nginx access log 每秒 1000 行,每行 300 字节 | 约 25GB/天
Java 应用 debug 日志每秒 200 行,每行 500 字节 | 约 8.6GB/天
异常堆栈每次 20KB,每分钟 100 次 | 约 2.8GB/天
10 个容器每个每天 2GB 日志 | 约 20GB/天
如果系统盘是 60GB,Docker 目录又在根分区 / 下,业务镜像、overlay2、容器日志、系统包缓存一起占空间,撑满并不需要太久。
线上更容易出问题的情况是:业务发生异常后疯狂刷错误日志。平时一天 1GB,异常时一小时 20GB,这种很常见。磁盘写满后,连 SSH 登录、systemd 启动服务、数据库写临时文件都可能受影响。
系统盘被撑满以后会发生什么
比较典型的现象:
df -h 看到 / 分区 100%。
docker ps 卡顿或者 Docker daemon 异常。
容器无法创建新文件,应用报 No space left on device。
数据库、Redis、Elasticsearch 这类服务开始报写入失败。
日志继续刷,但业务已经不正常。
还有一种情况容易误判:df -h 看起来满了,du -sh /var/lib/docker 又对不上。这时可能是某个大日志文件被删除了,但进程还持有文件句柄,空间没有真正释放。可以用:
lsof | grep deleted
如果看到 Docker 或业务进程持有 deleted 文件,就要重启对应容器或进程释放句柄。
先看现场,不要上来就删 overlay2
排查 Docker 磁盘占用,可以从这些命令开始:
df -h
du -sh /var/lib/docker/*
docker system df
find /var/lib/docker/containers -name "*-json.log" -size +1G -exec ls -lh {} \;
看容器日志大小:
find /var/lib/docker/containers -name "*-json.log" -exec du -h {} \; | sort -hr | head
多说一句,别随手 rm -rf /var/lib/docker/overlay2。overlay2 里是镜像层和容器文件系统,删错了 Docker 环境基本就乱了。真要清理镜像、停止容器、无用 volume,用 Docker 自己的命令更稳:
docker system df
docker image prune
docker container prune
docker volume prune
docker system prune -a
最后这个 docker system prune -a 会清掉未使用镜像,生产环境要确认镜像是否还需要回滚,不要直接复制执行。
临时救急:清空某个容器日志
如果已经快满了,先找最大的 json.log:
find /var/lib/docker/containers -name "*-json.log" -exec du -h {} \; | sort -hr | head -20
清空某个日志文件可以用 truncate:
truncate -s 0 /var/lib/docker/containers/
不要直接 rm 这个日志文件。删除后 Docker 进程可能还持有文件句柄,df 看到的空间不一定释放,而且后续日志行为也可能不符合预期。
truncate 会让 docker logs 的历史日志没了,但容器一般不用重启,适合救急。救急之后还是要把日志轮转配置补上,不然过几天还会回来。
推荐做法:给 Docker daemon 配日志轮转
比较常用的是在 /etc/docker/daemon.json 里配置 json-file 的 max-size 和 max-file。
示例:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
含义很直接:单个日志文件最大 100MB,最多保留 3 个。一个容器最多大约占 300MB 日志空间。
改完之后重启 Docker:
systemctl restart docker
注意,这个配置通常只对新创建的容器生效。已有容器不一定自动套用,需要重建容器。生产环境里,如果容器是通过 docker compose 管的,就改 compose 配置后重建;如果是手工 docker run,就要重新 run。
实际使用中,max-size 不建议设得太小。比如 10m、3 个文件,遇到问题时可能日志很快被覆盖,排障窗口太短。普通 Web 服务可以从 100m * 3 或 200m * 5 开始。日志特别大的服务,更建议接入集中日志系统,而不是靠本机文件硬扛。
docker compose 里单独配置日志限制
如果不想改全局 Docker daemon,或者不同服务要不同策略,可以在 docker-compose.yml 里配置 logging。
示例:
services:
api:
image: your-api:latest
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "5"
然后重建服务:
docker compose up -d --force-recreate
这种方式对多业务混部比较友好。比如 Nginx 日志量大,可以给 200m * 10;后台 worker 日志少,给 50m * 3 就够用。
也可以换 local log driver
Docker 还有 local log driver,相比 json-file,它在磁盘使用上更友好,支持自动轮转,日志以更紧凑的格式保存。
daemon.json 示例:
{
"log-driver": "local",
"log-opts": {
"max-size": "100m",
"max-file": "5"
}
}
local driver 对普通 Docker 单机场景挺合适。需要注意的是,不同日志采集工具对 json-file 的兼容更普遍。如果已经有 Filebeat、Fluent Bit 直接采集 /var/lib/docker/containers/*/*.log,那切 driver 前要确认采集链路是否支持。
应用日志到底该打到 stdout 还是文件
容器化场景里,应用打到 stdout/stderr 是常见做法,配合 Docker、Kubernetes、日志采集系统使用很方便。但这不代表可以无限打。
更稳的方式是:
应用输出关键日志到 stdout。
Docker 本地限制日志大小。
Filebeat、Fluent Bit、Vector 这类 agent 采集后发到 Elasticsearch、Loki、ClickHouse、Kafka 或云日志服务。
异常堆栈、访问日志、审计日志按重要性区分保留周期。
debug 日志不要在生产环境长期打开。
这里有个经验:不要把本地 Docker 日志当长期日志归档。它更像一个短期缓冲区,方便 docker logs 临时排查。真正要查 7 天、30 天日志,应该进日志平台。
云服务器选型时也要考虑日志和磁盘
很多小规格云服务器系统盘不大,Docker 又默认把数据放在 /var/lib/docker。如果业务日志量高,系统盘 30GB、40GB 很容易紧张。购买服务器时不能只看 CPU、内存、带宽,磁盘容量和业务日志量也要一起估。
如果是海外业务、游戏服、接口服务、需要大带宽出口的 Docker 部署,可以看看129云。比如美国精品大宽带-C型有 8C、8G DDR4 ECC、120GB SSD、1Gbps 峰值带宽、2.5TB 流量,适合对带宽和磁盘都有一定要求的容器业务。韩国-C型是 4C、4G、60GB SSD、7Mbps、傲盾防火墙、回国优化,适合一些轻量服务或需要韩国节点的场景。客服热线 400-9177118,选规格时可以顺便确认系统盘、带宽、DDoS 防护和线路情况。
如果系统盘固定偏小,可以把 Docker data-root 单独放到数据盘。例如挂载一块更大的 SSD 到 /data/docker,然后改 Docker 配置。
/etc/docker/daemon.json:
{
"data-root": "/data/docker",
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
迁移 Docker data-root 要停 Docker,备份原目录,再同步数据。生产环境别在业务高峰期操作。
生产环境比较容易忽略的细节
容器重建后配置才生效
daemon.json 改完并重启 Docker,不代表旧容器一定用新日志策略。保险做法是重建容器,然后 inspect 确认 LogConfig。
Kubernetes 场景路径不完全一样
如果是 Kubernetes,容器运行时可能是 containerd,日志路径常见在 /var/log/containers、/var/log/pods。kubelet 也有 containerLogMaxSize、containerLogMaxFiles 这类参数。不要拿 Docker 单机场景的路径直接套到 Kubernetes 集群。
logrotate 不是首选
有人会给 /var/lib/docker/containers/*/*.log 写 logrotate。不是不能用,但 Docker 自己已经支持 max-size、max-file,优先用 Docker 原生能力更清晰。logrotate 如果配置 copytruncate,也要注意日志瞬间写入和截断窗口。
应用本身也要控日志
Docker 侧限制只能防止磁盘被打爆,不能解决日志噪音。应用如果异常时每次请求都打印完整堆栈,日志平台也会被打穿。生产环境的 log level、采样、错误聚合、access log 字段都要控制。
可以直接放到巡检里的命令
查看根分区:
df -h /
查看 Docker 总占用:
du -sh /var/lib/docker
查看 Docker 内部资源占用:
docker system df
查看最大的容器日志:
find /var/lib/docker/containers -name "*-json.log" -exec du -h {} \; | sort -hr | head -20
查看某个容器日志路径:
docker inspect --format='{{.LogPath}}' 容器名
清空某个容器日志:
truncate -s 0 $(docker inspect --format='{{.LogPath}}' 容器名)
查看 Docker 日志驱动:
docker info | grep "Logging Driver"
查看容器日志配置:
docker inspect 容器名 | grep -A 10 LogConfig