调度任务挂了只报'No space left on device'——怎么系统性排查磁盘问题?
先说结论
No space 不止是磁盘没空间。它还可能是 inode 满、文件被打开未释放、quota 超限、overlayfs 层满、tmpfs 满、reserved blocks 用光。一上来就 `df -h` 看一眼说没问题的,才刚开始。
处理流程:先止血再溯源。调度任务优先级是恢复业务,第一步是让任务能跑起来(临时扩空间/清临时文件/重定向临时目录),第二步再找根因防复发。
预防比救火重要。磁盘监控告警要分层:80% warn、90% critical、95% page。还要监控 inode、deleted 文件数、大目录 top-5。光监控 `df -h` 是不够的。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
这是上一篇的实战延续。上一篇讲了磁盘满的四种原因,这一篇讲的是线上真实场景:凌晨调度任务挂了,日志只有一句 'No space left on device',你一个 ssh 上去,怎么 5 分钟内搞清楚是哪种磁盘问题、怎么止血、怎么防复发。这种题面试问得少,但真正能体现你是一个人能处理故障的老兵,还是只会写 SQL 的新人。
业务场景:DataWorks 跨时区同步任务半夜挂了
某短剧出海公司有个跨时区的用户行为同步任务,每天凌晨 2 点(北京时间)跑,把当天的美东时区数据同步到 Hologres。有一天早上 9 点打开 DataWorks,发现前一晚的任务红了,日志只有最后两行:
# 任务日志:
# 2024-03-15 02:14:23 INFO [Shell] Running command: /opt/sync/sync.sh
# 2024-03-15 02:41:15 ERROR [Shell] Shell exited with code 1
# 2024-03-15 02:41:15 ERROR [Shell] stderr: tar: write error: No space left on device
# 2024-03-15 02:41:15 FATAL Task failed
下游 20 个报表全阻塞,运营催数据。我 ssh 上调度机第一反应是 `df -h`,结果:
df -h /data
# /dev/nvme0n1 200G 120G 80G 60% /data
# <-- 还剩 80G,怎么会报 No space?
这就是线上经验关键时刻了。`df -h` 没问题不代表就没问题,立刻继续查 `df -ih`:
df -ih /data
# Filesystem Inodes IUsed IFree IUse%
# /dev/nvme0n1 13M 13M 0 100% <-- inode 耗光了
inode 100%,问题就清楚了。查原因:sync.sh 脚本在 /data/tmp/ 下解压一个 tar 文件,里面有 1400 万个小文件(每个 JSON 几 KB),inode 直接爆表。新人看完这里就停了,我接着查为什么前几天不炸:原来前几天每天都会清理 /data/tmp/,但前晚清理脚本因为另一个 bug 没跑,两天的临时文件累计到 2800 万,就炸了。修复是:① `find /data/tmp -type f -delete` 紧急清理(花了 20 分钟,因为文件太多);② 重跑任务;③ 改脚本用 awk 流式处理,不落盘;④ 加 inode 监控告警,超过 80% 就告警。
手段一:系统性排查脚本
线上处理这种 'No space' 我有一个固定的排查脚本,5 分钟内能把所有可能原因全筛一遍。
#!/bin/bash
# disk_check.sh - 磁盘问题一键诊断
echo '=== 1. 所有文件系统使用率 ==='
df -hT
echo '=== 2. inode 使用率 ==='
df -ih
echo '=== 3. 每个挂载点前 5 大目录 ==='
for mp in $(df --output=target | tail -n +2); do
echo "--- $mp ---"
du -h --max-depth=1 $mp 2>/dev/null | sort -h | tail -5
done
echo '=== 4. deleted 但未释放的文件 ==='
lsof 2>/dev/null | awk '/deleted/ {sum += $7} END {printf "Total: %.2f GB\n", sum/1024/1024/1024}'
echo '=== 5. 最近 1 小时新增的大文件 ==='
find /data /var/log -type f -size +100M -mmin -60 2>/dev/null
echo '=== 6. quota 情况(如果开了)==='
repquota -a 2>/dev/null | head -20
echo '=== 7. overlayfs(容器环境)==='
du -sh /var/lib/docker/overlay2 2>/dev/null
echo '=== 8. dmesg 最近的 IO 错误 ==='
dmesg -T | grep -iE 'error|no space|disk' | tail -10
手段二:紧急止血的几招
处理线上磁盘问题,止血比溯源优先。下面这几招是常备武器。
# 招 1:清理明显的临时文件
find /tmp -type f -mtime +3 -delete
find /var/tmp -type f -mtime +7 -delete
# 招 2:清理旧日志
find /var/log -name '*.log.*.gz' -mtime +30 -delete
find /var/log -name '*.log.*' -size +1G -mtime +7 -delete
# 招 3:截断(而不是删)大日志,避免影响正在写的进程
> /var/log/app/big.log
# 或用 truncate
truncate -s 0 /var/log/app/big.log
# 招 4:释放被 deleted 持有的空间
lsof +L1 / 2>/dev/null | head
# 对持有 deleted 文件的进程做 > /proc/pid/fd/N 清空
# 招 5:清容器层
docker system prune -f # 清停止的容器和悬空镜像
docker system prune -a -f # 连没在用的镜像也清(谨慎)
docker builder prune -f # 清 build cache
# 招 6:清 journald 日志(systemd 的日志容易吃空间)
journalctl --vacuum-size=500M # 保留 500M
journalctl --vacuum-time=7d # 只保留 7 天
# 招 7:清 APT / YUM 缓存
apt-get clean # Debian/Ubuntu
yum clean all # CentOS/RHEL
# 招 8:reserved blocks(ext4 默认保留 5%)
tune2fs -m 1 /dev/nvme0n1p1 # 从 5% 降到 1%,腾出空间
手段三:logrotate 配置(预防吃日志空间)
很多磁盘满是日志累积导致的。logrotate 是 Linux 的标准日志切割工具,但很多新人不会配。
# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
daily # 每天切
rotate 14 # 保留 14 天
compress # 压缩老日志
delaycompress # 延迟一天压缩(前一天不压,方便查)
missingok # 文件不存在不报错
notifempty # 空文件不切
size 1G # 超过 1G 强制切(日高峰防爆)
maxsize 5G # 硬上限
copytruncate # 关键:复制后截断,避免 deleted 文件坑
dateext # 文件名带日期后缀
dateformat -%Y%m%d
postrotate # 切完后的钩子
systemctl reload myapp 2>/dev/null || true
endscript
}
# 测试配置(不实际执行,只看结果)
logrotate -d /etc/logrotate.d/myapp
# 强制跑一次
logrotate -f /etc/logrotate.d/myapp
copytruncate 的重要性:默认 logrotate 是 `create`,也就是 mv 旧文件 + 新建同名文件。但对于一直 open 的进程来说,它还是持有旧 fd,新日志还写到旧文件去了(也就是前一篇讲的 deleted 文件坑)。`copytruncate` 是复制一份当前日志到 .1,然后清空原文件(truncate),进程 fd 不变,新日志继续写同一个 inode。
手段四:监控和告警
线上磁盘监控不能只看 df -h,要多维度监控:总体、inode、增长率、单大目录、deleted。
# Prometheus node_exporter 的磁盘指标
# node_filesystem_avail_bytes - 可用空间
# node_filesystem_files_free - inode 可用
# node_filesystem_size_bytes - 总大小
# 告警规则示例(Prometheus)
- alert: DiskSpaceWarning
expr: (1 - node_filesystem_avail_bytes / node_filesystem_size_bytes) > 0.85
for: 5m
- alert: InodeWarning
expr: (1 - node_filesystem_files_free / node_filesystem_files) > 0.80
for: 5m
- alert: DiskFillRate
expr: predict_linear(node_filesystem_avail_bytes[1h], 4*3600) < 0
for: 10m
# 按过去 1 小时增长速度,4 小时内会耗尽,提前告警
# 自己写监控脚本的话:
cat > /usr/local/bin/disk_monitor.sh <<'EOF'
#!/bin/bash
df -h | awk 'NR>1 && $5+0 > 85 {print "WARN: " $6 " " $5}'
df -ih | awk 'NR>1 && $5+0 > 80 {print "WARN: inode " $6 " " $5}'
EOF
'No space' 的 8 种根因
| 根因 | df -h | df -i | 排查关键 | 处理 |
|---|---|---|---|---|
| 普通磁盘满 | 100% | 正常 | du 排序 | 清大文件 |
| inode 满 | 正常 | 100% | find 数小文件 | 清小文件 |
| deleted 未释放 | 100% | 正常 | lsof +L1 | kill 或 >/proc/fd |
| overlayfs 满 | 100% | 正常 | du /var/lib/docker | docker prune |
| quota 限制 | 正常 | 正常 | repquota -a | 调 quota |
| tmpfs 满 | 100% | 正常 | mount | grep tmpfs | 清 /tmp |
| reserved blocks | 99%报100% | 正常 | tune2fs -l | tune2fs -m 1 |
| NFS 挂载异常 | 100% | 异常 | df + mount | 重挂载 |
面试高频追问
追问一:调度任务频繁挂 No space,长期怎么治?
治标三件事:① 监控分层(85% warn / 90% critical / 95% page),包括 df 和 df -i 都要监控;② logrotate 和 cleanup 脚本定时跑,每天早上 8 点前清完夜里的临时文件;③ 磁盘容量规划按业务增长预估,留 30% 余量。治本两件事:① 把任务的临时目录从系统盘挪到专用数据盘(比如 `/data/scratch`),系统盘绝不让业务临时文件滚进来;② 任务本身要有 retry + 失败告警,不要把磁盘管理的负担全压在运维身上。某短剧出海公司那次之后,我把调度任务所有 /tmp 的使用都改成了 /data/scratch/<task_id>/,任务结束自动删。再没出过事。
追问二:跨多个节点怎么快速查磁盘?
两个路子。快的:直接 ansible 或者 pssh,`ansible all -m shell -a 'df -h | tail -1'`,几秒出结果。稳的:上监控平台(Prometheus + Grafana),磁盘数据常驻可视化。运维场景我常用 ansible ad-hoc,比如 `ansible worker -m shell -a 'df -i | awk "\$5+0 > 80"'` 一把扫出所有 inode 将满的机器。
追问三:为什么有时候 df 显示还有空间但还是报 No space?
五个可能:① inode 满(df -ih);② reserved blocks,ext4 默认保留 5% 给 root,普通用户看到 95% 但写入会失败(`tune2fs -l` 看 'Reserved block count');③ quota,用户或组级的 quota 超了(`quota -s` 或 `repquota`);④ dir_index 损坏,某些目录无法再新建文件(很少见,fsck 修);⑤ 磁盘 inode 损坏但没报错,一般伴随 dmesg 有 IO error。生产上第 ① 种最常见,第 ③ 种在多租户机器上常见。
实战踩坑:调度机 /tmp 爆满拖挂所有任务
某短剧出海公司的 DataWorks 调度集群,有次所有 shell 任务全部失败,集群告警群刷屏。ssh 上去看:
df -h /tmp
# tmpfs 8.0G 8.0G 0 100% /tmp
# /tmp 是 tmpfs(内存盘),8G 全满
# 找谁在占 /tmp
du -h --max-depth=1 /tmp | sort -h | tail
# 7.8G /tmp/spark-events <-- Spark event log
ls /tmp/spark-events/ | wc -l
# 45000 多个文件,每个几 K 到几 M
# Spark 历史作业的 event log,本来应该定期清理的
find /tmp/spark-events -mtime +1 -delete
# 改配置把 spark event log 挪到数据盘
# spark.eventLog.dir hdfs:///user/spark/events
根因:默认 /tmp 是 tmpfs(内存文件系统),8G 上限,Spark event log 本来应该清理但配置没开,攒了两个月的 45000 个文件,把整个 /tmp 占满了。所有其他任务要写 /tmp 都失败了(比如 bash 脚本写 tmp 文件、JVM 写 hsperfdata_xxx),于是全集群的 shell 任务都挂了。修复:① 紧急清 /tmp/spark-events;② Spark event log 目录改到 HDFS;③ 加 /tmp 监控(/tmp 满比数据盘满危险 10 倍,因为影响所有进程);④ 给关键服务的临时目录单独挂一块磁盘,不要共用 /tmp。
总结
'No space left on device' 是线上最朴素但最能暴露经验的错误信息。能顺着 `df -h` → `df -ih` → `lsof +L1` → `du` → `dmesg` 这条线讲出 5-8 种根因和对应处理,并提到 logrotate copytruncate、tmpfs 单独分盘、监控预测模型这些长期预防措施的,面试官会认定你是真正在线上背过锅的人。记住:先止血(恢复业务),再溯源(找根因),最后预防(加监控改脚本)。
下一篇开始聊进程和网络:一个 Flink TaskManager 卡死了但没报错——lsof / strace / /proc 三板斧怎么用?
你们生产环境磁盘监控做到哪一层?评论区聊聊你的告警策略~
关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长
Linux 面试通关手册
第6-10篇:进程 / 网络 / IO 深入
Wood聊大数据
9年大数据老兵,聊技术、聊职场、聊成长
本册导读
第 1-5 篇讲的是 '线上出问题先看哪里'——日志、CPU、内存、磁盘、No space,全是 top/free/df 就能盖住的一层。
但真正把人卡住的,往往是下一层:进程明明在,CPU 却 780%;线程卡死在 D 状态 kill -9 都打不掉;Kafka ping 得通 telnet 也通,就是连不上;CLOSE_WAIT 堆了 5000 个,再不重启就雪崩;iostat 一看 %util 100%,可你连谁在写都不知道。
第 6-10 篇就是专门讲这一层——进程、线程、网络、IO 的深度排查。每一道题都是我或身边同事真实踩过的坑,带着完整的排查链路、真实数字、和后来的长期防复发方案。
看完这 5 篇,下一次凌晨被叫起来说 '服务挂了但不知道为什么',你会知道第二层该往哪里挖。
— Wood,2026 年春