服务器磁盘爆满排查实录:Docker 与 Systemd 日志的临时止血与永久根治
目录
- 概述
- 故障现象:根分区 97%,Docker 容器告警
- 定位"空间刺客":从 / 逐级下钻
- 两大元凶的本质:为什么日志会无限膨胀
- 应急止血:临时方案(秒级生效)
- 永久根治:配置固化
- 长效预防:自动巡检脚本
- 重启验证清单
- 总结与思考
- 附录:速查手册
概述
问题:服务器磁盘每周增长几个 G,df -h 告警根分区仅剩 1.4GB。 目标:定位"空间刺客",分场景处理 Docker 与 Systemd 日志,从临时止血到永久根治。 适合谁看:负责 Linux 服务器运维的开发者、SRE、运维工程师。 前置条件:能 SSH 到目标机器,具备 sudo 权限。
一、故障现象:根分区 97%,Docker 容器告警
df -hFilesystem 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 根目录排序
du -sh /* 2>/dev/null | sort -rh | head -n 10| 目录 | 大小 |
|---|---|
/var | 23G |
/www | 7.2G |
/usr | 4.5G |
/swap.img | 2.1G |
/var 独占 23GB,首要怀疑对象。
2.2 进入 /var
du -sh /var/* 2>/dev/null | sort -rh | head -n 10| 子目录 | 大小 |
|---|---|
/var/lib | 17G |
/var/log | 5.1G |
/var/cache | 127M |
两个重灾区:/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.log | ls -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 日志
# 限制总容量为 200MB(推荐)
sudo journalctl --vacuum-size=200M
# 或保留最近 7 天
sudo journalctl --vacuum-time=7d执行完毕立即生效,df -h 可见空间释放。无需重启任何服务。
4.2 清理 Docker 容器日志
⚠️ 千万不要
rm -f *.log! 容器进程仍持有文件句柄,磁盘空间不会立即释放,df与du还会出现"显示不一致"的诡异现象。除非重启容器(生产环境等于业务中断)。
正确做法是截断:
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 目录的优先级最高且独立维护,符合生产环境的配置规范。
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:全局配置(推荐)
一次性解决所有容器,无需逐个项目配置。
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,更精细)
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,不影响其他容器。
配置优先级:compose 的 logging > daemon.json 全局配置。
六、长效预防:自动巡检脚本
把每周清理设为兜底任务,即使永久配置失效也不会爆盘:
#!/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 执行:
0 3 * * 0 /root/cleanup_logs.sh七、重启验证清单
完成所有配置后,建议进行一次重启验证:
# 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 控制) |
八、总结与思考
df与du不一致?必有进程持有已删除文件。 排查命令lsof +L1;处理时尽量用truncate -s 0代替rm,避免触发句柄残留。临时方案只救火,永久方案才治病。
vacuum和truncate不写入任何配置,重启后"上限"自动归零——下次还会撑爆。Docker 日志是服务器磁盘爆满的"头号惯犯"。 初始化服务器时务必第一时间配置
/etc/docker/daemon.json。生产环境重启 Docker = 全量容器中断,务必在维护窗口或业务低峰期操作;推荐用 compose 项目级配置降低影响面。
建立"发现即根治"的运维意识。 单次清理只能解燃眉之急,把限制写入文件 + 巡检脚本双保险,才能彻底告别每周告警的噩梦。
附录:速查手册
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.conf | SystemMaxUse=500M |
| Docker 全局 | /etc/docker/daemon.json | max-size=100m, max-file=3 |
| Compose 项目 | docker-compose.yml 的 logging 字段 | 同上 |
所有的磁盘爆满,都是日志在无声地呐喊:你忘记给我设定边界了。
