Skip to content

案例:磁盘被日志写满 ​

现象 ​

凌晨 2:14,订单服务开始大量报错:

text
java.io.IOException: No space left on device

登录机器:

bash
df -h
text
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        40G   40G     0 100% /
/dev/vdb        500G  412G   63G  87% /data

根分区 100%。但业务数据在 /data,根分区上本来"没什么东西"。

定位过程 ​

第一步:谁占了根分区 ​

bash
du -xh --max-depth=1 / 2>/dev/null | sort -hr | head -10
text
28G	/var
6.2G	/usr
1.8G	/data
...

/var 28G。继续往下:

bash
du -xh --max-depth=2 /var | sort -hr | head -10
text
24G	/var/lib
3.1G	/var/log
24G	/var/lib/docker

Docker 占了 24G。

第二步:Docker 的哪一部分 ​

bash
docker system df -v
text
Images space usage:
REPOSITORY   TAG      SIZE      SHARED    UNIQUE    CONTAINERS
order-api    1.4.1    312MB     210MB     102MB     1
<none>       <none>   310MB     210MB     100MB     0
...

Containers space usage:
CONTAINER ID   NAME        SIZE      STATUS
a3f8c2e91b04   order-api   18.4GB    Up 3 days

Local Volumes space usage:
VOLUME NAME   LINKS   SIZE
mysql-data    1       1.84GB

容器可写层 18.4GB —— 应用在容器里把日志写到了容器内路径,不是挂载目录。

bash
docker inspect -f '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}' order-api
text
/data/order-api/logs -> /app/logs

只挂了 /app/logs,但应用配置里日志实际写在 /opt/app/logs(镜像内路径)。

bash
docker exec order-api du -sh /opt/app/logs
text
17.2G	/opt/app/logs

第三步:确认没有"已删除但被占用"的文件 ​

bash
lsof +L1 2>/dev/null | head
text
COMMAND   PID USER   FD   TYPE DEVICE  SIZE/OFF NLINK NODE NAME
java    31882  app    3w   REG  fd01h 42949672960 0 1832441 /data/app/app.log (deleted)

发现另一个问题:/data/app/app.log 有 42GB 被删除了但进程还握着句柄,空间没释放(它显示在 /data,不是根分区)。

处置 ​

立即止血 ​

bash
# 1. 释放被占用的已删除文件(不动进程)
truncate -s 0 /proc/31882/fd/3

# 2. 清 Docker 容器日志(json-file)
truncate -s 0 /var/lib/docker/containers/a3f8c2e91b04*/a3f8c2e91b04*-json.log

# 3. 清构建缓存与悬空镜像
docker builder prune -f
docker image prune -f
bash
df -h /
text
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        40G   18G   20G  48% /

服务恢复。

根治 ​

  1. 修正日志路径:把应用日志改到挂载的 /app/logs(对应宿主机 /data/order-api/logs)。
  2. 配置 logrotate:
ini
# /etc/logrotate.d/order-api
/data/order-api/logs/*.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    copytruncate
}

应用自己持有文件句柄时用 copytruncate(复制后清空),避免需要发信号。

  1. Docker 日志轮转(daemon.json):
json
{
  "log-driver": "local",
  "log-opts": { "max-size": "50m", "max-file": "5" }
}
  1. 迁 data-root 到 /data,别让 Docker 占根分区。

复盘 ​

问题根因改进
根分区被 Docker 占满data-root 默认在 /var/lib/docker迁到数据盘
容器可写层 18GB应用日志写在镜像内路径,未挂载修配置 + 加启动检查
42GB 已删除文件未释放清理脚本用 rm 删了正在写的日志改用 copytruncate / truncate
发现太晚只有磁盘 100% 才报警70% 告警,85% 严重

新增两条监控:

bash
# 每 5 分钟检查
df -h / | awk 'NR==2 {gsub("%","",$5); if ($5>70) print "WARN root "$5"%"}'
df -i / | awk 'NR==2 {gsub("%","",$5); if ($5>80) print "WARN inode "$5"%"}'

这个案例的通用教训

"磁盘满"永远先问三个问题:

  1. 哪个分区满了?
  2. 谁占的(du 逐层)?
  3. du 和 df 对得上吗(对不上就是 lsof +L1)?