Docker 容器日志管理与磁盘空间清理实战:告别磁盘告警

前言:磁盘告警从哪来

很多个人站长都经历过这样的场景:服务器用了半年一年之后,突然收到磁盘空间不足的告警,SSH 登录一看,df -h 显示根分区已经用了 90% 以上。排查一圈,发现罪魁祸首往往不是网站代码或者数据库,而是 Docker 的 /var/lib/docker 目录。这个目录就像一个无底洞,日志文件、废弃镜像、构建缓存、悬空数据卷,全都被它吞了进去。本文就结合实际运维经验,讲清楚 Docker 容器日志管理与磁盘空间清理的完整套路。

一、先搞清楚空间被谁吃掉了

动手清理之前,一定要先定位问题,否则就是瞎忙活。Docker 提供了几个非常实用的查看命令。

第一步,看整体占用情况:

docker system df

这个命令会输出四类占用:Images(镜像)、Containers(容器)、Local Volumes(本地卷)、Build Cache(构建缓存),每一类都会显示总大小、可回收大小(RECLAIMABLE)。如果可回收部分占比很高,说明有大量僵尸资源躺在磁盘上。

第二步,看具体的日志文件大小。使用 json-file 日志驱动的容器,日志默认存放在:

/var/lib/docker/containers/<容器ID>/<容器ID>-json.log

可以用下面的命令找出日志最大的几个容器:

find /var/lib/docker/containers -name "*-json.log" -exec ls -lh {} \; | sort -k5 -h | tail -20

第三步,用 du 从根目录逐层往下找,确定大文件的具体位置:

du -sh /var/lib/docker/*
du -sh /var/lib/docker/overlay2/* | sort -h | tail

实战中经常发现,某些日志量大的容器(比如 Nginx、PHP-FPM、Java 应用),单个 json.log 就能长到几个 GB,而且日志文件被进程占用,直接删除后空间并不会释放,必须用 truncate 或者重启容器。

二、Docker 日志管理的正确姿势

1. 限制日志文件大小

最推荐的做法是从源头限制,让 Docker 自动滚动日志。在 /etc/docker/daemon.json 中全局配置:

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

这样每个容器最多保存 5 个日志文件,每个最大 50MB,总共不超过 250MB。修改后需要重启 Docker 服务才能生效:

systemctl restart docker

注意:这个配置只对重启后新建的容器生效,已经存在的容器要 recreate 才会应用新配置。

2. 单容器指定日志参数

如果不想全局生效,可以在启动容器时单独指定:

docker run -d --log-opt max-size=20m --log-opt max-file=3 --name nginx nginx:alpine

使用 docker-compose 时,同样可以在服务定义里加 log 配置:

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

3. 使用更省空间的日志驱动

json-file 驱动每个日志条目都是 JSON 格式,冗余较多。如果不想丢日志,又希望更省空间,可以考虑 local 驱动:

"log-driver": "local",
"log-opts": {
  "max-size": "50m",
  "max-file": "5"
}

local 驱动不存 JSON 元数据,体积更小,但只能通过 docker logs 查看,无法直接读到文件。

4. 日志的二次处理

对于需要长期保存的访问日志,建议让 Nginx 等应用直接写文件,而不是只依赖 Docker 日志。通过挂载卷把日志写到宿主机,再用系统的 logrotate 做轮转压缩,这样既能控制体积,又能保留历史记录。

三、磁盘空间清理实战

1. 清理废弃镜像与构建缓存

最常用的组合拳:

docker system prune -a --volumes

这条命令会删除所有未被使用的镜像(包括悬空镜像)、停止的容器、未使用的网络、悬空数据卷和构建缓存。注意 -a 参数会删掉没有被容器引用的所有镜像,如果之后还要用,得重新拉取。更稳妥的做法是分步执行:

docker image prune          # 只清理悬空镜像
docker image prune -a       # 清理所有未使用镜像
docker builder prune        # 清理构建缓存
docker volume prune         # 清理未使用的数据卷

特别是频繁构建镜像的服务器,docker builder prune 往往能释放出惊人的空间,因为每次构建都会产生大量中间层缓存。

2. 清理容器日志

对于已经被占用的日志文件,用 truncate 而不是 rm:

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

也可以一条命令清掉所有容器的日志:

for id in $(docker ps -q); do
  log="/var/lib/docker/containers/$id/$id-json.log"
  [ -f "$log" ] && truncate -s 0 "$log"
done

3. 清理 overlay2 中的残留

如果删除了容器但 /var/lib/docker/overlay2 仍然很大,多半是残留的容器目录。可以用:

docker container prune

把所有停止状态的容器清掉,overlay2 中对应的层也会一并释放。对于已经完全废弃的容器目录,如果确认容器不存在,可以谨慎删除 /var/lib/docker/overlay2 下的对应目录,但操作前一定要确认没有正在运行的容器引用它,否则会损坏数据。

四、swap 与 inode 也是隐形杀手

除了磁盘容量,还有两个经常被忽略的问题:Swap 交换分区和 inode 耗尽。inode 是文件系统的索引节点,每个文件或目录都要占用一个。如果网站程序生成了海量小文件(比如缓存文件、Session 文件、上传的缩略图),inode 可能先于磁盘容量耗尽。检查方法很简单:

df -i        # 查看 inode 使用率
df -h        # 查看容量使用率

如果 inode 使用率接近 100%,哪怕磁盘还有空闲,系统也会报"设备上没有空间"。这种情况在小文件特别多的目录(如 /var/spool/postfix、容器 overlay2 层)里很常见。清理思路和小文件排查方法:

# 找出文件数量最多的目录
for d in /var/lib/docker /var/spool /tmp /var/log; do
  echo "$d: $(find $d -type f 2>/dev/null | wc -l) 个文件"
done

Swap 则是另一个问题。当物理内存不足时,系统会把不活跃的内存页换到 Swap,如果 Swap 本身配置得过大,或者系统频繁进行换入换出(可以通过 vmstat 1 观察 si/so 列),磁盘 IO 会被拖垮,整个服务器响应变慢。Docker 容器如果没限制内存,某些程序内存泄漏时也会把宿主机的 Swap 吃光。建议给容器设置内存限制:

docker run -d --memory=512m --memory-swap=768m --name myapp myapp:latest

compose 文件里对应写法是 mem_limit: 512m。限了内存之后,即使程序有内存泄漏,也只是容器被杀掉重启,不会拖垮整台服务器。

五、自动化:让清理变成定时任务

手动清理只能解一时之渴,最好的方案是写一个清理脚本,加入 crontab 定时执行。下面是一个常用的清理脚本:

#!/bin/bash
# /usr/local/bin/docker-clean.sh
# 删除停止超过 24 小时的容器
docker ps -a --filter "status=exited" --format "{{.ID}}" | xargs -r docker rm

# 删除悬空镜像
docker image prune -f

# 清理构建缓存
docker builder prune -f

# 清空超过 100MB 的容器日志
find /var/lib/docker/containers -name "*-json.log" -size +100M -exec truncate -s 0 {} \;

# 删除超过 7 天的临时卷
docker volume prune -f --filter "label!=keep"

加入定时任务,每周日凌晨执行一次:

0 3 * * 0 /bin/bash /usr/local/bin/docker-clean.sh >> /var/log/docker-clean.log 2>&1

五、配合监控,提前发现隐患

清理只是补救,真正的目标是让磁盘永远不会爆。建议在服务器上部署一个简单的磁盘监控脚本,或者接入已有的监控系统(如 Node Exporter + Prometheus),当磁盘使用率超过 80% 时自动告警。一个简单的告警脚本:

#!/bin/bash
# /usr/local/bin/disk-alert.sh
THRESHOLD=80
USAGE=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
  echo "$(date) 磁盘使用率已达 ${USAGE}%" | mail -s "磁盘告警" admin@example.com
fi

把它也加进 crontab,每小时检查一次:

0 * * * * /bin/bash /usr/local/bin/disk-alert.sh

如果你用的是腾讯云、阿里云这类云服务器,控制台本身就带磁盘监控和告警功能,直接在云监控里设置阈值即可,比自己写脚本更省心。另外,像 Netdata、Glances 这类轻量监控工具,安装后浏览器打开就能实时看到 CPU、内存、磁盘、网络的曲线图,对个人站长来说非常友好,也适合用来观察清理前后的磁盘占用变化。

1. 一个完整的排查流程示例

假设你现在收到磁盘告警,按照下面的顺序排查,十分钟内基本能定位问题:

# 第一步:确认总览
df -h && df -i

# 第二步:找出占用最大的目录(逐层下钻)
du -sh /* 2>/dev/null | sort -h | tail -5
du -sh /var/lib/docker/* 2>/dev/null | sort -h | tail -5

# 第三步:查看 Docker 各类资源占用
docker system df

# 第四步:找出超大的容器日志
find /var/lib/docker/containers -name "*-json.log" -size +50M -exec ls -lh {} \;

# 第五步:清理并确认效果
docker system prune -f
truncate -s 0 /var/lib/docker/containers/<容器ID>/<容器ID>-json.log
df -h

这套流程我实际用过很多次,绝大多数情况下,问题都出在容器日志和构建缓存这两块,把它们处理掉,磁盘使用率往往能从 90% 直接回落到 40% 以下。

六、总结

Docker 容器日志管理与磁盘清理,核心思路就四句话:日志要从源头限大小、镜像缓存要定期清、清理操作要写成脚本、磁盘状态要有监控。做到这四点,服务器的磁盘基本不会再给你惊喜。个人站长的时间和精力有限,把这类重复性工作自动化,才能把心思放在内容和技术本身上。

如果你也遇到过磁盘被 Docker 日志吃满的情况,不妨先跑一遍 docker system df 看看哪一类资源占用最多,再有针对性地清理,效率会高很多。

Last modification:August 2nd, 2026 at 08:16 am

Leave a Comment