外观
案例:磁盘被日志写满
现象
凌晨 2:14,订单服务开始大量报错:
text
java.io.IOException: No space left on device登录机器:
bash
df -htext
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 -10text
28G /var
6.2G /usr
1.8G /data
.../var 28G。继续往下:
bash
du -xh --max-depth=2 /var | sort -hr | head -10text
24G /var/lib
3.1G /var/log
24G /var/lib/dockerDocker 占了 24G。
第二步:Docker 的哪一部分
bash
docker system df -vtext
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-apitext
/data/order-api/logs -> /app/logs只挂了 /app/logs,但应用配置里日志实际写在 /opt/app/logs(镜像内路径)。
bash
docker exec order-api du -sh /opt/app/logstext
17.2G /opt/app/logs第三步:确认没有"已删除但被占用"的文件
bash
lsof +L1 2>/dev/null | headtext
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 -fbash
df -h /text
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 40G 18G 20G 48% /服务恢复。
根治
- 修正日志路径:把应用日志改到挂载的
/app/logs(对应宿主机/data/order-api/logs)。 - 配置 logrotate:
ini
# /etc/logrotate.d/order-api
/data/order-api/logs/*.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
copytruncate
}应用自己持有文件句柄时用 copytruncate(复制后清空),避免需要发信号。
- Docker 日志轮转(daemon.json):
json
{
"log-driver": "local",
"log-opts": { "max-size": "50m", "max-file": "5" }
}- 迁
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"%"}'这个案例的通用教训
"磁盘满"永远先问三个问题:
- 哪个分区满了?
- 谁占的(
du逐层)? du和df对得上吗(对不上就是lsof +L1)?