1. 事故背景与紧急响应
那天凌晨3点17分,监控系统的告警短信像催命符一样连续震醒了我。服务器磁盘使用率突破95%阈值,而这是一台承载着核心PostgreSQL数据库的生产环境服务器。登录服务器后,df -h显示根分区已用99.8%,剩余的200MB空间正在以肉眼可见的速度减少。
关键提示:当磁盘空间低于5%时,Linux系统会强制终止占用磁盘最多的进程,这可能导致数据库损坏等灾难性后果。
我立即启动应急预案:
- 通过
lsof +L1查找被删除但未释放空间的大文件(常见于日志文件被轮转但进程仍持有文件描述符的情况) - 使用
ncdu --exclude /proc /sys快速定位磁盘占用最高的目录 - 临时清理
/var/log下的归档日志(保留最近7天) - 对PostgreSQL执行
VACUUM FULL回收死元组空间
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 根因深度分析
2.1 PostgreSQL的WAL日志风暴
检查pg_wal目录发现竟有50GB的WAL日志,远超正常值(通常配置为1-2GB)。这是由于:
- 长事务运行超过2小时,导致WAL保留策略失效
- 未配置合理的
archive_command,使WAL积压 max_wal_size参数被设置为默认值1GB,但实际业务产生了大量DML操作
bash复制# 紧急处理命令示例
pg_controldata $PGDATA | grep "Latest checkpoint location"
pg_resetwal -f $PGDATA # 慎用!仅在最坏情况下使用
2.2 日志系统失控
发现某Java应用的日志配置存在严重问题:
xml复制<!-- 错误配置示例 -->
<RollingFile name="FileAppender" fileName="/var/log/app.log"
filePattern="/var/log/app-%d{yyyy-MM-dd}.log">
<PatternLayout pattern="%m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
<!-- 缺失SizeBasedTriggeringPolicy导致单个日志文件膨胀 -->
</RollingFile>
2.3 监控盲区
运维体系存在三大漏洞:
- 未监控
/var/log目录的inode使用情况(小文件占满inode同样致命) - 缺少对PostgreSQL WAL增长速率的趋势预测
- 告警阈值设置不合理(95%才告警为时已晚)
3. 完整恢复方案
3.1 立即止血措施
bash复制# 1. 创建紧急空间(即使只有100MB也可能救命)
dd if=/dev/zero of=/emergency.swap bs=1M count=100
chmod 600 /emergency.swap
mkswap /emergency.swap
swapon /emergency.swap
# 2. 暂停非核心服务
systemctl list-units --type=service --state=running | grep -vE '(postgres|ssh|rsyslog)' | awk '{print $1}' | xargs -r systemctl stop
# 3. 数据库紧急维护
psql -U postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle' AND xact_start < now() - interval '30 min'"
3.2 PostgreSQL专项优化
sql复制-- 调整WAL参数
ALTER SYSTEM SET wal_level = replica;
ALTER SYSTEM SET max_wal_size = '4GB';
ALTER SYSTEM SET checkpoint_timeout = '15min';
-- 添加自动清理任务
CREATE EXTENSION pg_cron;
SELECT cron.schedule('0 3 * * *', 'VACUUM ANALYZE');
3.3 日志体系改造
采用三层日志治理策略:
- 应用层:Log4j2配置增加
<SizeBasedTriggeringPolicy size="100MB"/> - 系统层:部署logrotate每日切割
conf复制/var/log/app/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 0640 appuser appgroup
sharedscripts
postrotate
kill -USR1 `cat /var/run/app.pid 2>/dev/null` 2>/dev/null || true
endscript
}
- 传输层:通过Filebeat将日志实时推送至ELK集群
4. 长效预防机制
4.1 智能监控体系
bash复制# 磁盘预测脚本(加入crontab每日运行)
#!/bin/bash
THRESHOLD=80
PREDICT_DAYS=3
df -h | awk -v threshold=$THRESHOLD '$5+0 > threshold {print $6}' | while read mount; do
growth_rate=$(find $mount -type f -mtime -1 -exec du -sh {} + | awk '{sum+=$1} END{print sum/1024}')
remaining=$(df -m $mount | awk 'NR==2 {print $4}')
days_left=$(echo "$remaining / $growth_rate" | bc)
[ $days_left -le $PREDICT_DAYS ] && \
alert "WARNING: $mount will be full in $days_left days (Growth: ${growth_rate}MB/day)"
done
4.2 自动化维护流程
python复制# 数据库维护机器人示例
import psycopg2
from datetime import datetime
def pg_maintenance():
conn = psycopg2.connect("dbname=postgres user=monitor")
cursor = conn.cursor()
# 检查长事务
cursor.execute("""
SELECT pid, now()-xact_start AS duration, query
FROM pg_stat_activity
WHERE state IN ('idle in transaction', 'active')
AND now()-xact_start > interval '1 hour'
""")
for pid, duration, query in cursor.fetchall():
notify(f"Long transaction ({duration}): PID={pid}\n{query[:200]}...")
# 自动清理旧WAL
cursor.execute("SELECT pg_current_wal_lsn(), pg_walfile_name_offset(pg_current_wal_lsn())")
current_lsn, current_file = cursor.fetchone()
purge_files = sorted([f for f in os.listdir(pg_wal_dir) if f.endswith('.partial')],
key=lambda x: os.path.getmtime(os.path.join(pg_wal_dir, x)))
for f in purge_files[:10]: # 保留最新10个
os.remove(os.path.join(pg_wal_dir, f))
4.3 灾备演练方案
设计三级应急场景:
- 磁盘使用率>90%:自动触发日志清理脚本
- 磁盘使用率>95%:自动隔离非核心Pod(K8s环境)
- 磁盘使用率>98%:触发数据库只读模式切换
5. 经验总结与避坑指南
5.1 必须建立的监控项
| 监控指标 | 告警阈值 | 检测频率 |
|---|---|---|
| 根分区使用率 | >85% | 5分钟 |
| PostgreSQL WAL目录大小 | >10GB | 15分钟 |
| /var/log inode使用率 | >80% | 1小时 |
| 最长事务持续时间 | >30分钟 | 30分钟 |
5.2 典型误操作警示
- 直接删除大文件:可能导致正在写入的服务崩溃。正确做法是使用
truncate -s 0 filename清空内容 - 重启数据库服务:在磁盘满情况下可能无法正常启动。应先确保有至少10%的剩余空间
- 误删PostgreSQL数据文件:删除前务必确认文件类型
file /path/to/suspect_file
5.3 高级排查技巧
当常规方法找不到大文件时:
bash复制# 查找被隐藏的磁盘占用(比如被删除但仍被进程占用的文件)
lsof -nP +L1 | grep deleted | awk '{print $2,$7,$9}' | sort -k2 -nr | head
# 检查是否被恶意挖矿(常见于安全漏洞)
find / -xdev -type f -name "*.elf" -o -name "*.miner" -exec ls -lh {} \;
这次事故让我深刻认识到:磁盘空间管理不是简单的容量监控,而是需要建立从存储层、应用层到监控层的立体防御体系。现在我们的运维手册里新增了一条铁律——当磁盘使用率超过80%时,必须当天的值班工程师立即处理,绝不允许拖延到第二天
