Ubuntu服务器磁盘空间告警是运维工作中最常见也最让人头疼的问题之一。应用日志突然暴涨、数据库备份残留、Docker镜像堆积,都可能在深夜触发监控告警。手动登录清理不仅效率低下,还容易在疲劳操作中误删重要文件。解决这个问题的根本思路,是建立一套自动检测、智能清理、分级通知的脚本体系,让服务器在磁盘使用率达到阈值时自行处理常规垃圾文件,只在无法自动解决时才通知人工介入。

磁盘空间监控的核心逻辑

自动化处理的第一步是准确获取磁盘使用率。在Ubuntu系统中,df命令是最直接的工具,但脚本中需要提取百分比数值进行判断。使用df -h /可以查看根分区的使用情况,结合awk或grep可以精确提取使用率数字。一个典型的监控命令是:df -h / | awk 'NR==2 {print $5}' | sed 's/%//',这条命令会返回一个纯数字,比如85表示磁盘使用了85%。脚本的核心逻辑就是拿这个数字与预设阈值比较,比如80%触发清理,95%触发紧急告警。需要注意的是,不同分区可能需要分别监控,特别是/var、/home、/tmp这些容易爆满的挂载点。实际编写脚本时,建议用数组或循环结构同时监控多个分区,避免遗漏。

分级告警与清理策略设计

磁盘告警不能一刀切,应该根据使用率严重程度采取不同级别的响应。第一级是预警级别,比如使用率达到75%时,脚本只记录日志并发送轻微提醒,让运维人员有时间规划清理。第二级是自动清理级别,设定在85%左右,脚本主动删除已知的安全垃圾文件,比如系统日志、缓存文件、过期的软件包。第三级是紧急级别,设定在92%以上,脚本执行更激进的清理策略,同时通过多种渠道发送强提醒。这种分级机制避免了频繁告警造成的麻木感,也防止了自动清理误删重要数据的风险。每一级的阈值和动作都应该做成可配置的变量,方便不同服务器根据实际情况调整。

日志文件的智能清理方案

系统日志是磁盘空间的最大消耗者之一。journald日志在Ubuntu中默认会持续增长,如果没做限制,几个月就能吃掉几个GB的空间。自动清理脚本中应该包含journalctl --vacuum-size=500M这样的命令,限制日志总大小。对于传统的/var/log下的日志文件,不能简单粗暴地删除,需要用到logrotate的强制轮转或者find命令按时间和大小筛选。比如find /var/log -type f -name "*.log" -mtime +30 -delete可以删除30天前的日志,但更安全的做法是先用truncate命令清空文件内容而不删除文件本身,避免正在写入的进程出现异常。对于应用日志,最好在脚本中维护一个白名单,明确哪些目录和文件模式可以自动清理,不在白名单中的一律跳过。

清理APT缓存和软件包残留

Ubuntu系统长期运行后,APT包管理器会积累大量下载的deb安装包和缓存信息。这些文件存放在/var/cache/apt/archives/目录下,清理它们是完全安全的。脚本中可以执行apt-get clean来清除所有下载的包文件,或者apt-get autoclean只删除已经过时的版本。另外,系统中可能存在不再需要的依赖包,apt-get autoremove --purge可以一并清理。这些命令在脚本中执行时需要加上-y参数避免交互确认,同时建议将输出重定向到日志文件以便事后审计。如果服务器上运行着Snap应用,snap的旧版本也会占用大量空间,可以用snap list --all | awk '/disabled/ {print $1, $3}' | while read name rev; do snap remove "$name" --revision="$rev"; done来清理禁用的旧版本。

Docker环境下的空间回收

如果Ubuntu服务器上运行着Docker,磁盘告警十有八九跟Docker有关。Docker的镜像、容器、卷、构建缓存都会占用惊人空间。自动化脚本需要执行docker system prune -af来清理未使用的镜像和停止的容器,但这条命令比较激进,生产环境建议分步执行。先用docker image prune -a --filter "until=24h"清理24小时前创建的悬空镜像,再用docker container prune -f清理已停止的容器,最后用docker volume prune -f清理未使用的卷。对于Docker的日志问题,需要限制单个容器的日志大小,在docker run时加上--log-opt max-size=10m --log-opt max-file=3参数,或者在daemon.json中全局配置。脚本中可以检查容器日志的实际大小,对超出限制的日志文件进行截断处理。

完整的自动化脚本实现

下面是一个完整的磁盘空间自动处理脚本,整合了上述所有策略,可以直接部署在Ubuntu服务器上使用:

#!/bin/bash

# ============================================
# Ubuntu磁盘空间自动监控清理脚本
# 功能:多级监控、智能清理、多渠道告警
# ============================================

# ---------- 配置区域 ----------
# 监控的分区列表(挂载点:告警阈值:紧急阈值)
MONITOR_PARTITIONS=(
    "/:80:92"
    "/var:85:95"
    "/home:90:95"
)

# 日志保留天数
LOG_RETENTION_DAYS=30

# journald日志大小限制
JOURNALD_MAX_SIZE="500M"

# 是否启用Docker清理(1=启用,0=禁用)
ENABLE_DOCKER_CLEAN=1

# 告警通知方式(可配置多个)
NOTIFY_WEBHOOK=""  # 企业微信或钉钉webhook地址
NOTIFY_EMAIL=""    # 接收告警的邮箱

# 脚本日志文件
SCRIPT_LOG="/var/log/disk_cleanup.log"

# ---------- 函数定义 ----------

# 记录日志函数
log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$SCRIPT_LOG"
}

# 发送告警通知函数
send_alert() {
    local level="$1"
    local message="$2"
    local hostname=$(hostname)
    local full_message="[${level}] 服务器: ${hostname} - ${message}"
    
    log_message "告警: $full_message"
    
    # Webhook通知(企业微信/钉钉格式)
    if [ -n "$NOTIFY_WEBHOOK" ]; then
        curl -s -X POST "$NOTIFY_WEBHOOK" \
            -H "Content-Type: application/json" \
            -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$full_message\"}}" \
            > /dev/null 2>&1
    fi
    
    # 邮件通知
    if [ -n "$NOTIFY_EMAIL" ]; then
        echo "$full_message" | mail -s "磁盘告警 - $hostname" "$NOTIFY_EMAIL"
    fi
}

# 获取磁盘使用率
get_disk_usage() {
    local mount_point="$1"
    df -h "$mount_point" 2>/dev/null | awk 'NR==2 {print $5}' | sed 's/%//'
}

# 清理系统日志
clean_system_logs() {
    log_message "开始清理系统日志..."
    
    # 限制journald日志大小
    if command -v journalctl &> /dev/null; then
        journalctl --vacuum-size="$JOURNALD_MAX_SIZE" 2>&1 | tee -a "$SCRIPT_LOG"
        log_message "journald日志已限制为 $JOURNALD_MAX_SIZE"
    fi
    
    # 清理传统日志文件(保留最近N天,清空而非删除)
    find /var/log -type f -name "*.log" -mtime +"$LOG_RETENTION_DAYS" | while read logfile; do
        if [ -f "$logfile" ]; then
            truncate -s 0 "$logfile" 2>/dev/null
            log_message "已清空日志: $logfile"
        fi
    done
    
    # 清理压缩的旧日志
    find /var/log -type f -name "*.gz" -mtime +"$LOG_RETENTION_DAYS" -delete 2>/dev/null
    find /var/log -type f -name "*.1" -mtime +"$LOG_RETENTION_DAYS" -delete 2>/dev/null
    
    log_message "系统日志清理完成"
}

# 清理APT缓存
clean_apt_cache() {
    log_message "开始清理APT缓存..."
    
    apt-get clean -y 2>&1 | tee -a "$SCRIPT_LOG"
    apt-get autoclean -y 2>&1 | tee -a "$SCRIPT_LOG"
    apt-get autoremove --purge -y 2>&1 | tee -a "$SCRIPT_LOG"
    
    log_message "APT缓存清理完成"
}

# 清理Snap旧版本
clean_snap_versions() {
    if command -v snap &> /dev/null; then
        log_message "开始清理Snap旧版本..."
        
        snap list --all 2>/dev/null | awk '/disabled/ {print $1, $3}' | while read snapname revision; do
            snap remove "$snapname" --revision="$revision" 2>&1 | tee -a "$SCRIPT_LOG"
            log_message "已移除Snap: $snapname 版本: $revision"
        done
        
        log_message "Snap旧版本清理完成"
    fi
}

# 清理Docker资源
clean_docker() {
    if [ "$ENABLE_DOCKER_CLEAN" -eq 1 ] && command -v docker &> /dev/null; then
        log_message "开始清理Docker资源..."
        
        # 清理已停止的容器
        docker container prune -f 2>&1 | tee -a "$SCRIPT_LOG"
        
        # 清理24小时前的悬空镜像
        docker image prune -a --filter "until=24h" -f 2>&1 | tee -a "$SCRIPT_LOG"
        
        # 清理未使用的卷
        docker volume prune -f 2>&1 | tee -a "$SCRIPT_LOG"
        
        # 清理构建缓存
        docker builder prune -f 2>&1 | tee -a "$SCRIPT_LOG"
        
        log_message "Docker资源清理完成"
    fi
}

# 清理/tmp目录中的旧文件
clean_tmp_files() {
    log_message "开始清理/tmp目录..."
    
    # 删除7天前的临时文件
    find /tmp -type f -atime +7 -delete 2>/dev/null
    find /tmp -type d -empty -delete 2>/dev/null
    
    log_message "/tmp目录清理完成"
}

# 紧急清理(更激进的策略)
emergency_cleanup() {
    log_message "执行紧急清理策略..."
    
    # 清理所有Docker资源(包括正在使用的镜像)
    if [ "$ENABLE_DOCKER_CLEAN" -eq 1 ] && command -v docker &> /dev/null; then
        docker system prune -af --volumes 2>&1 | tee -a "$SCRIPT_LOG"
    fi
    
    # 清理所有日志(保留最近7天)
    find /var/log -type f -name "*.log" -mtime +7 -exec truncate -s 0 {} \; 2>/dev/null
    
    # 清理/var/crash下的崩溃转储
    rm -rf /var/crash/* 2>/dev/null
    
    # 清理/var/cache下的大文件
    find /var/cache -type f -size +100M -mtime +7 -delete 2>/dev/null
    
    log_message "紧急清理完成"
}

# ---------- 主程序 ----------
log_message "========== 磁盘空间检查开始 =========="

ALERT_TRIGGERED=0
EMERGENCY_TRIGGERED=0

for partition_config in "${MONITOR_PARTITIONS[@]}"; do
    IFS=':' read -r mount_point warn_threshold emergency_threshold <<< "$partition_config"
    
    # 获取当前使用率
    usage=$(get_disk_usage "$mount_point")
    
    if [ -z "$usage" ]; then
        log_message "警告: 无法获取 $mount_point 的使用率,跳过"
        continue
    fi
    
    log_message "分区 $mount_point 当前使用率: ${usage}%"
    
    # 判断是否触发紧急阈值
    if [ "$usage" -ge "$emergency_threshold" ]; then
        EMERGENCY_TRIGGERED=1
        send_alert "紧急" "分区 $mount_point 使用率达到 ${usage}%,超过紧急阈值 ${emergency_threshold}%"
    
    # 判断是否触发告警阈值
    elif [ "$usage" -ge "$warn_threshold" ]; then
        ALERT_TRIGGERED=1
        send_alert "警告" "分区 $mount_point 使用率达到 ${usage}%,超过告警阈值 ${warn_threshold}%"
    fi
done

# 根据触发级别执行相应清理
if [ "$EMERGENCY_TRIGGERED" -eq 1 ]; then
    log_message "检测到紧急状态,执行全面清理..."
    clean_system_logs
    clean_apt_cache
    clean_snap_versions
    clean_tmp_files
    emergency_cleanup
    send_alert "紧急" "已完成紧急清理,请检查磁盘空间是否恢复正常"
    
elif [ "$ALERT_TRIGGERED" -eq 1 ]; then
    log_message "检测到告警状态,执行常规清理..."
    clean_system_logs
    clean_apt_cache
    clean_snap_versions
    clean_tmp_files
    clean_docker
    send_alert "警告" "已完成常规自动清理"
    
else
    log_message "磁盘空间正常,无需清理"
fi

log_message "========== 磁盘空间检查结束 =========="
脚本部署与定时任务配置

将上述脚本保存为/usr/local/bin/disk_cleanup.sh,然后赋予执行权限chmod +x /usr/local/bin/disk_cleanup.sh。部署前务必先在测试环境验证,特别是Docker清理部分,确认不会误删正在使用的镜像。配置定时任务使用crontab -e,建议每小时执行一次:0 * * * * /usr/local/bin/disk_cleanup.sh。如果服务器磁盘问题比较频繁,可以调整为每30分钟执行一次:*/30 * * * * /usr/local/bin/disk_cleanup.sh。脚本本身已经内置了日志记录功能,所有操作都会写入/var/log/disk_cleanup.log,方便事后排查问题。告警通知部分需要根据实际环境配置webhook地址或邮箱,企业微信和钉钉的机器人webhook格式略有不同,需要替换脚本中的JSON结构。

扩展优化与安全注意事项

这套脚本可以根据实际需求进一步扩展。比如增加对特定应用目录的清理规则,像MySQL的binlog、Redis的dump文件、Nginx的访问日志等。可以维护一个外部配置文件,用键值对的形式定义每个目录的清理策略,脚本启动时读取配置,这样修改规则就不需要改动脚本本身。安全方面需要特别注意,自动清理脚本以root权限运行,删除操作有风险。建议在清理逻辑中增加文件大小和类型的检查,避免误删。对于生产环境的核心服务器,可以先让脚本只做监控告警不做自动清理,运行一段时间确认告警准确后,再逐步开启自动清理功能。另外,脚本中的webhook地址和邮箱配置如果包含敏感信息,应该单独存放在权限为600的配置文件中,由脚本source引入。

监控效果验证与持续改进

脚本部署后不能放任不管,需要定期检查执行日志和告警记录。重点关注几个指标:自动清理触发的频率、清理释放的空间量、是否有清理失败的错误日志。如果发现某个分区频繁触发告警,说明自动清理策略不够彻底,需要分析具体是什么文件在持续增长,然后针对性地增加清理规则。如果紧急清理被频繁触发,说明常规清理的阈值设置过高或者清理力度不够,应该下调告警阈值或者增加清理项目。磁盘空间管理是一个持续优化的过程,脚本只是工具,真正解决问题还需要配合应用层面的日志轮转配置、数据库的备份清理策略、以及合理的存储规划。将这套脚本与监控系统如Prometheus、Zabbix结合使用,可以构建更完善的磁盘空间管理体系。