适用场景

这类问题常见于单机 Docker、docker-compose 或没有接入集中日志系统的测试/生产节点。应用本身还在运行,但主机磁盘空间持续下降,最终出现接口异常、容器重启失败、镜像拉取失败、数据库写入失败等问题。

典型环境特征:

  • Docker 使用默认 json-file 日志驱动。
  • 容器持续输出访问日志、调试日志、异常堆栈或健康检查日志。
  • /var/lib/docker 单独挂载或直接占用根分区。
  • 没有配置 Docker 日志轮转。

现象描述

线上节点告警显示根分区或 Docker 数据分区空间不足:

df -h

示例输出:

Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        50G   48G  2.0G  97% /

应用侧可能同时出现这些现象:

  • 容器无法启动,提示 no space left on device
  • docker pulldocker build 失败。
  • 应用写临时文件、写 SQLite、写本地缓存失败。
  • 日志平台没有明显异常,但机器磁盘增长很快。

可能原因

Docker 默认日志驱动通常是 json-file。如果不设置大小限制,容器标准输出和标准错误会持续写入宿主机文件:

/var/lib/docker/containers/<container-id>/<container-id>-json.log

常见诱因包括:

  • 应用把 debug 日志直接打到 stdout。
  • 访问日志按请求逐条打印,高峰期日志量暴涨。
  • 异常重试循环不断打印堆栈。
  • 健康检查、定时任务或 sidecar 输出过多。
  • Docker daemon 没有配置 max-sizemax-file

排查思路

1. 先确认磁盘是否真的打满

df -h
df -ih

df -h 看磁盘容量,df -ih 看 inode。如果容量没满但 inode 满了,说明小文件数量过多;如果容量接近 100%,再继续定位大文件。

2. 找到 Docker 目录占用

sudo du -xh --max-depth=1 /var/lib/docker | sort -h

关键点:

  • -x 表示不跨文件系统,避免扫到其他挂载点。
  • --max-depth=1 先看一级目录,不要一上来全盘扫描。
  • 如果 containers 很大,优先怀疑容器日志。
  • 如果 overlay2 很大,可能是镜像、容器层或应用写入容器文件系统。

3. 定位最大的容器日志文件

sudo find /var/lib/docker/containers -name '*-json.log' -type f -printf '%s %p\n' \
  | sort -nr \
  | head -20 \
  | awk '{printf "%.2fG %s\n", $1/1024/1024/1024, $2}'

如果系统的 find 不支持 -printf,可以使用:

sudo du -h /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -h | tail -20

看到某个 *-json.log 达到几 GB 或几十 GB,基本就能确认日志膨胀问题。

4. 根据日志路径反查容器

日志路径里包含完整容器 ID,例如:

/var/lib/docker/containers/9f8a1c.../9f8a1c...-json.log

取前 12 位反查容器:

docker ps -a --no-trunc | grep 9f8a1c

也可以用 inspect 直接确认日志文件:

docker inspect --format='{{.Name}} {{.LogPath}}' $(docker ps -aq)

如果容器很多,可以按日志大小输出容器名:

for id in $(docker ps -aq); do
  log=$(docker inspect --format='{{.LogPath}}' "$id")
  name=$(docker inspect --format='{{.Name}}' "$id" | sed 's#^/##')
  [ -f "$log" ] && printf '%s %s %s\n' "$(du -h "$log" | awk '{print $1}')" "$id" "$name"
done | sort -h

定位示例

某台主机根分区使用率 97%。排查发现 Docker 的 containers 目录占用最大:

sudo du -xh --max-depth=1 /var/lib/docker | sort -h

输出节选:

1.8G    /var/lib/docker/overlay2
39G     /var/lib/docker/containers
42G     /var/lib/docker

继续查看日志文件:

sudo du -h /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -h | tail -5

输出节选:

7.4G    /var/lib/docker/containers/2b6.../2b6...-json.log
31G     /var/lib/docker/containers/9f8.../9f8...-json.log

反查容器:

docker ps -a --no-trunc | grep 9f8

确认是 api-service 容器,查看最近日志:

docker logs --tail=100 api-service

发现应用在不断打印同一个数据库连接异常堆栈,每秒几十次,导致日志文件快速膨胀。

临时止血方案

1. 释放单个容器日志文件

不要直接 rm 正在被 Docker 打开的日志文件。删除文件后,进程仍可能持有文件句柄,空间不一定立即释放。推荐截断:

sudo truncate -s 0 /var/lib/docker/containers/<container-id>/<container-id>-json.log

截断后确认空间:

df -h
sudo du -sh /var/lib/docker/containers

2. 先降低应用日志量

如果日志持续高速写入,单纯截断只能短暂缓解。需要先处理源头:

docker logs --tail=200 <container-name>
docker restart <container-name>

重启只能解决部分临时异常。如果是配置错误、依赖不可用或异常循环,需要修复应用配置或依赖服务。

长期治理方案

1. 配置 Docker daemon 日志轮转

编辑 /etc/docker/daemon.json

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

含义:

  • max-size: 单个日志文件最大 100 MB。
  • max-file: 最多保留 5 个日志文件。
  • 单个容器最多保留约 500 MB 本地日志。

重启 Docker:

sudo systemctl restart docker

注意:重启 Docker 会影响容器,生产环境要先确认业务容器是否由 systemd、compose、swarm 或其他编排系统托管。

2. 让配置对已有容器生效

Docker daemon 的日志配置通常只对新创建的容器生效。已有容器可能需要重建:

docker compose up -d --force-recreate

或者单容器重新创建:

docker stop api-service
docker rm api-service
docker run -d --name api-service <原有参数> <image>

重建前必须确认启动参数、挂载卷、环境变量、端口映射都已记录,避免误删后无法恢复。

3. docker-compose 单服务配置日志限制

如果不能修改全局 daemon,可以在 docker-compose.yml 中为服务配置:

services:
  api:
    image: example/api:1.0
    logging:
      driver: json-file
      options:
        max-size: "100m"
        max-file: "5"

更新后重建服务:

docker compose up -d --force-recreate api

4. 应用层减少无效日志

建议同时从应用层治理:

  • 生产环境默认使用 INFO,不要长期打开 DEBUG
  • 对重复异常增加限频或采样。
  • 健康检查不要每次都输出完整请求日志。
  • 大对象、请求体、响应体不要直接打印到 stdout。
  • 关键错误保留上下文,但避免无限重试刷屏。

预防措施

1. 增加磁盘和日志文件告警

至少监控这些指标:

df -h /
sudo du -sh /var/lib/docker/containers
sudo du -sh /var/lib/docker/overlay2

在 Prometheus node_exporter 中,可以关注:

  • node_filesystem_avail_bytes: 文件系统可用空间。
  • node_filesystem_files_free: inode 可用数量。
  • node_filesystem_size_bytes: 文件系统总容量。

告警规则可以设置为根分区使用率超过 85% 预警、超过 95% 严重告警。

2. 发布检查项中加入日志轮转

新机器初始化时应检查:

docker info --format '{{.LoggingDriver}}'
cat /etc/docker/daemon.json

如果没有 max-sizemax-file,就不应该直接承载长期运行服务。

3. 区分日志膨胀和容器层膨胀

如果大头在 containers,优先查 *-json.log。如果大头在 overlay2,要进一步确认是否有程序把上传文件、缓存、临时文件写到了容器内部文件系统。容器内可用下面的命令快速查看:

docker exec -it <container-name> sh -c "du -xh --max-depth=1 / | sort -h"

应用数据应写入明确的数据卷,而不是写在容器可写层里。

总结

Docker 容器日志打满磁盘的核心排查路径是:先用 df -h 确认磁盘压力,再用 du 判断 /var/lib/docker 的占用分布,最后定位 containers 目录下最大的 *-json.log 并反查容器。临时处理优先使用 truncate 截断日志,长期治理必须配置 max-sizemax-file,并从应用侧减少重复异常日志。对生产环境来说,Docker 日志轮转应该是机器初始化和服务上线前的基础检查项。