Skip to content

服务器磁盘爆满排查实录:Docker 与 Systemd 日志的临时止血与永久根治

目录

  1. 概述
  2. 故障现象:根分区 97%,Docker 容器告警
  3. 定位"空间刺客":从 / 逐级下钻
  4. 两大元凶的本质:为什么日志会无限膨胀
  5. 应急止血:临时方案(秒级生效)
  6. 永久根治:配置固化
  7. 长效预防:自动巡检脚本
  8. 重启验证清单
  9. 总结与思考
  10. 附录:速查手册

概述

问题:服务器磁盘每周增长几个 G,df -h 告警根分区仅剩 1.4GB。 目标:定位"空间刺客",分场景处理 Docker 与 Systemd 日志,从临时止血到永久根治。 适合谁看:负责 Linux 服务器运维的开发者、SRE、运维工程师。 前置条件:能 SSH 到目标机器,具备 sudo 权限。

一、故障现象:根分区 97%,Docker 容器告警

bash
df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        40G   36G  1.4G  97% /
overlay          40G   36G  1.4G  97% /var/lib/docker/overlay2/...

可用空间仅剩 1.4GB,一连串 overlay 挂载点表明 Docker 容器正在运行。任何一次日志写入都可能直接触发 No space left on device

二、定位"空间刺客":从 / 逐级下钻

df 告诉你"满了",du 告诉你"谁"满的。逐级下钻是定位大文件的标准动作。

2.1 根目录排序

bash
du -sh /* 2>/dev/null | sort -rh | head -n 10
目录大小
/var23G
/www7.2G
/usr4.5G
/swap.img2.1G

/var 独占 23GB,首要怀疑对象

2.2 进入 /var

bash
du -sh /var/* 2>/dev/null | sort -rh | head -n 10
子目录大小
/var/lib17G
/var/log5.1G
/var/cache127M

两个重灾区:/var/lib(持久化数据)与 /var/log(日志)。

2.3 精准区分 /var/log 里的两类"住客"

/var/log 下住着两种本质完全不同的日志,要用对应命令才能区分:

类别路径查大小命令
Systemd 统一日志/var/log/journal/journalctl --disk-usage
应用 / Docker 容器日志/var/log/*.log/var/lib/docker/containers/*-json.logls -lhS /var/log/

本次排查发现:journalctl --disk-usage 只有 450MB,真正的元凶是 Docker 容器下一个 5GB 的 -json.log

三、两大元凶的本质:为什么日志会无限膨胀?

3.1 元凶一:Systemd Journald

systemd-journald 接管了内核与系统服务(SSH、Cron 等)的日志。默认配置下不限制总容量上限,文件会一直追加直到撑爆磁盘。

判定标准journalctl --disk-usage 超过 1GB 即可确认。

3.2 元凶二:Docker json-file 日志驱动

容器内应用把日志输出到 stdout/stderr,Docker 用 json-file 驱动接收并落到 /var/lib/docker/containers/<id>/<id>-json.log默认同样无大小限制——一个高频输出的容器几天内能写出几十 GB。

判定标准journalctl --disk-usage 正常,但单个 *-json.log 达数 GB。

这两类日志"默认无界"的设计,是 Linux 服务器磁盘爆满的头号通缉犯

四、应急止血:临时方案(秒级生效)

业务告急时,先把空间抢回来,再谈根治。临时方案的本质:手动清扫垃圾,但不限制"垃圾桶容量",重启后限制规则会失效。

4.1 清理 Journald 日志

bash
# 限制总容量为 200MB(推荐)
sudo journalctl --vacuum-size=200M

# 或保留最近 7 天
sudo journalctl --vacuum-time=7d

执行完毕立即生效,df -h 可见空间释放。无需重启任何服务

4.2 清理 Docker 容器日志

⚠️ 千万不要 rm -f *.log 容器进程仍持有文件句柄,磁盘空间不会立即释放,dfdu 还会出现"显示不一致"的诡异现象。除非重启容器(生产环境等于业务中断)。

正确做法是截断

bash
find /var/lib/docker/containers/ -name "*.log" -exec truncate -s 0 {} \;

容器不重启,docker logs 仍能查看新产生的日志,5GB 空间秒级归还。

4.3 临时方案 vs 永久方案:一张表看懂

维度临时方案(止血)永久方案(根治)
生效速度毫秒级需重启服务(journald 不影响业务;docker 中断容器)
重启后限制规则失效永久生效
适用场景磁盘已满(Use% ≥ 95%)的紧急救火日常预防与固化
核心价值快速止血,保住业务彻底根治,告别复发

运维黄金法则:先用临时方案秒级止血保住业务,再配置永久方案杜绝复发。两者缺一不可。

五、永久根治:配置固化

临时方案只能救一时,写入配置文件才能让限制规则在重启后依然生效。

5.1 固化 Journald:限制 500MB

为什么用 override 目录而不是直接改主配置? 升级系统时主配置文件可能被覆盖,override 目录的优先级最高且独立维护,符合生产环境的配置规范。

bash
sudo mkdir -p /etc/systemd/journald.conf.d
sudo tee /etc/systemd/journald.conf.d/override.conf > /dev/null <<EOF
[Journal]
SystemMaxUse=500M
SystemMaxFileSize=100M
EOF
sudo systemctl restart systemd-journald

影响:仅重启 journald 服务,业务零感知

5.2 固化 Docker:限制单文件 100MB × 3 个

为什么选 100MB × 3? 单文件 100MB 足够保留完整上下文,3 个轮转文件满足大多数排障需求,总上限 300MB 是一个平衡点——既不浪费磁盘,又不会因为轮转过频丢失关键日志。

方案 A:全局配置(推荐)

一次性解决所有容器,无需逐个项目配置。

bash
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}
EOF
sudo systemctl restart docker

⚠️ systemctl restart docker 会中断所有运行中的容器,请在业务低峰期操作。

方案 B:项目级配置(docker-compose.yml,更精细)

yaml
services:
  library-service:
    image: library:0.0.1
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "3"

只需 docker compose up -d --force-recreate不影响其他容器

配置优先级composelogging > daemon.json 全局配置。

六、长效预防:自动巡检脚本

把每周清理设为兜底任务,即使永久配置失效也不会爆盘:

bash
#!/bin/bash
# /root/cleanup_logs.sh —— 每周巡检清理
LOG=/var/log/cleanup.log
echo "[$(date '+%F %T')] 开始巡检..." >> $LOG

# 1. Journald 超过 1GB 则压缩到 200MB
if [ "$(journalctl --disk-usage | awk '{print $3}' | sed 's/[^0-9]//g')" -gt 1000 ]; then
    journalctl --vacuum-size=200M >> $LOG 2>&1
fi

# 2. Docker 单文件超过 1GB 则截断
find /var/lib/docker/containers/ -name "*.log" -size +1G \
    -exec truncate -s 0 {} \; >> $LOG 2>&1

# 3. 清理 24 小时前的构建缓存
docker builder prune -f --filter "until=24h" >> $LOG 2>&1

df -h / >> $LOG

加入 crontab,每周日 03:00 执行:

cron
0 3 * * 0 /root/cleanup_logs.sh

七、重启验证清单

完成所有配置后,建议进行一次重启验证:

bash
# 1. 记录清理后的磁盘使用率
df -h / > /tmp/disk_before.txt

# 2. 重启服务器
sudo reboot

# 3. 重启后再次检查
df -h /
journalctl --disk-usage
find /var/lib/docker/containers/ -name "*.log" -exec ls -lh {} \; | head -10
检查项预期结果
根分区使用率仍保持在 80% 以下(与重启前一致)
Systemd 日志大小≤ 500MB(由 SystemMaxUse 控制)
Docker 日志文件单个 ≤ 100MB(由 max-size 控制)

八、总结与思考

  1. dfdu 不一致?必有进程持有已删除文件。 排查命令 lsof +L1;处理时尽量用 truncate -s 0 代替 rm,避免触发句柄残留。

  2. 临时方案只救火,永久方案才治病。 vacuumtruncate 不写入任何配置,重启后"上限"自动归零——下次还会撑爆。

  3. Docker 日志是服务器磁盘爆满的"头号惯犯"。 初始化服务器时务必第一时间配置 /etc/docker/daemon.json

  4. 生产环境重启 Docker = 全量容器中断,务必在维护窗口或业务低峰期操作;推荐用 compose 项目级配置降低影响面。

  5. 建立"发现即根治"的运维意识。 单次清理只能解燃眉之急,把限制写入文件 + 巡检脚本双保险,才能彻底告别每周告警的噩梦。

附录:速查手册

A.1 常用命令

目的命令
查看磁盘df -h
定位大目录du -sh /* | sort -rh | head
Journald 占用journalctl --disk-usage
Docker 占用明细docker system df
单容器日志大小find /var/lib/docker/containers/ -name "*.log" -exec ls -lh {} \;
截断容器日志find /var/lib/docker/containers/ -name "*.log" -exec truncate -s 0 {} \;

A.2 关键配置

组件路径核心参数
Journald/etc/systemd/journald.conf.d/override.confSystemMaxUse=500M
Docker 全局/etc/docker/daemon.jsonmax-size=100m, max-file=3
Compose 项目docker-compose.ymllogging 字段同上

所有的磁盘爆满,都是日志在无声地呐喊:你忘记给我设定边界了。