Ubuntu服务器运维的核心痛点不是"出了故障怎么修",而是"怎么在故障发生前就知道、在故障发生时自动恢复"。日常日志监控配合故障自愈脚本,本质上是把运维人员从7×24小时盯屏幕的状态中解放出来。具体做法是:用rsyslog或journalctl统一收集日志,用logwatch或自定义脚本做关键指标告警,再用Shell或Python编写自愈脚本,针对磁盘满、服务崩溃、内存溢出、SSH连接异常等高频故障实现自动处理。下面我把整套方案从日志采集到自愈实现,一步步拆开讲清楚。

一、Ubuntu日志体系快速认知

Ubuntu系统的日志主要来自三个地方:systemd的journal日志(/var/log/journal/)、传统syslog(/var/log/syslog和/var/log/auth.log)、以及各应用自己写的日志文件(如Nginx的/var/log/nginx/access.log)。很多运维新手只盯着syslog看,其实journalctl能提供更结构化的信息。建议先执行journalctl --list-boots确认日志轮转正常,再用journalctl -u nginx.service查看特定服务日志。日志分散是监控的第一道障碍,必须先统一归集。

二、日志监控方案搭建:从采集到告警

第一步是日志采集。如果服务器数量少,直接用Shell脚本定时读取关键日志文件即可。如果服务器多,建议部署轻量级的日志收集方案,比如用filebeat或者直接写一个rsync+cron的方案把日志汇总到一台监控机。核心原则是:不要让日志只躺在单台机器上,单点故障会导致日志丢失,你连故障原因都查不到。

第二步是关键指标监控。不是所有日志都需要告警,要抓重点。以下几类必须监控:

1. 磁盘使用率超过85%——日志写满磁盘是最常见的"隐形杀手"。

2. OOM Killer触发记录——说明内存不够用了,进程被杀。

3. 服务进程意外退出——比如nginx、mysql、docker容器挂掉。

4. SSH登录失败次数异常——可能是暴力破解。

5. 内核报错和硬件错误——dmesg里的I/O error、machine check异常。

第三步是告警通知。最简单的方式是用mail命令发邮件,稍微好一点用企业微信/钉钉的Webhook推送。下面给一个基于Shell的磁盘监控告警脚本示例:

#!/bin/bash
# disk_alert.sh - 磁盘使用率告警脚本
THRESHOLD=85
USAGE=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')

if [ "$USAGE" -ge "$THRESHOLD" ]; then
    echo "警告:/ 分区使用率已达 ${USAGE}%,请立即处理!" | \
    mail -s "Ubuntu磁盘告警 - $(hostname)" admin@example.com
    # 同时记录到运维日志
    echo "$(date '+%Y-%m-%d %H:%M:%S') DISK_ALERT / ${USAGE}%" >> /var/log/ops_monitor.log
fi

把这个脚本放进crontab,每5分钟跑一次:*/5 * * * * /opt/scripts/disk_alert.sh。简单粗暴,但管用。

三、故障自愈脚本编写:核心思路与实战

自愈脚本的设计逻辑是"检测→判断→执行→验证→记录"。不是所有故障都适合自愈,必须设定安全边界。比如磁盘满可以自动清理,但数据库损坏不能自动删数据。下面按故障类型逐一给出可直接用的脚本。

故障类型一:磁盘空间不足自动清理

这是最高频的故障。策略是:清理旧日志、清理apt缓存、清理临时文件。注意不要误删正在使用的文件。

#!/bin/bash
# auto_clean_disk.sh - 磁盘空间自愈脚本
LOG_FILE="/var/log/ops_auto_heal.log"
MIN_SPACE_GB=5

# 获取根分区可用空间(GB)
AVAIL=$(df -BG / | awk 'NR==2 {print $4}' | tr -d 'G')

if [ "$AVAIL" -lt "$MIN_SPACE_GB" ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') 开始自动清理磁盘..." >> $LOG_FILE

    # 1. 清理7天前的旧日志
    find /var/log -name "*.log.*" -mtime +7 -delete 2>>$LOG_FILE
    find /var/log -name "*.gz" -mtime +30 -delete 2>>$LOG_FILE

    # 2. 清理apt缓存
    apt-get clean >> $LOG_FILE 2>&1

    # 3. 清理tmp目录超过3天的文件
    find /tmp -type f -mtime +3 -delete 2>>$LOG_FILE

    # 4. 清理journal旧日志(保留最近3天)
    journalctl --vacuum-time=3d >> $LOG_FILE 2>&1

    # 验证清理结果
    NEW_AVAIL=$(df -BG / | awk 'NR==2 {print $4}' | tr -d 'G')
    echo "$(date '+%Y-%m-%d %H:%M:%S') 清理完成,可用空间:${NEW_AVAIL}GB" >> $LOG_FILE
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') 磁盘空间正常,无需处理" >> $LOG_FILE
fi

故障类型二:服务进程崩溃自动重启

Ubuntu上用systemd管理的服务,本身就有Restart=on-failure的机制。但有些服务不是systemd管理的,或者systemd的重启策略不够用(比如重启太频繁会触发RestartSec限制)。这时候需要自己写监控脚本。

#!/bin/bash
# service_watchdog.sh - 服务守护脚本
SERVICES=("nginx" "mysql" "docker")
LOG_FILE="/var/log/ops_service_watch.log"

for svc in "${SERVICES[@]}"; do
    if ! systemctl is-active --quiet $svc; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') 检测到 $svc 已停止,尝试重启..." >> $LOG_FILE
        systemctl restart $svc
        sleep 3
        if systemctl is-active --quiet $svc; then
            echo "$(date '+%Y-%m-%d %H:%M:%S') $svc 重启成功" >> $LOG_FILE
        else
            echo "$(date '+%Y-%m-%d %H:%M:%S') $svc 重启失败!需要人工介入!" >> $LOG_FILE
            # 发送紧急告警
            echo "$svc 服务重启失败,请立即处理!" | \
            mail -s "紧急:$(hostname) $svc 故障" admin@example.com
        fi
    fi
done

这个脚本建议每分钟执行一次,配合crontab使用。如果某个服务连续重启3次都失败,就升级为人工告警,避免死循环。

故障类型三:内存溢出自动处理

当系统内存不足触发OOM时,通常是某个进程占用过高。自愈策略是找到占用内存最高的进程,判断是否为非核心进程后进行kill。注意:不要杀掉系统关键进程。

#!/bin/bash
# oom_handler.sh - OOM自愈脚本
LOG_FILE="/var/log/ops_oom_handler.log"
# 不允许杀掉的进程列表
PROTECTED=("sshd" "systemd" "rsyslogd" "cron")

# 检查是否有OOM记录
if dmesg | grep -q "Out of memory"; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') 检测到OOM事件,正在处理..." >> $LOG_FILE

    # 找出内存占用前5的进程(排除保护列表)
    ps aux --sort=-%mem | awk 'NR>1 {print $2, $11, $4}' | \
    head -5 | while read pid name mem; do
        SKIP=0
        for p in "${PROTECTED[@]}"; do
            if [ "$name" = "$p" ]; then SKIP=1; break; fi
        done
        if [ "$SKIP" -eq 0 ] && [ "$mem" -gt 30 ]; then
            echo "$(date '+%Y-%m-%d %H:%M:%S') 终止高内存进程: PID=$pid NAME=$name MEM=${mem}%" >> $LOG_FILE
            kill -9 $pid 2>>$LOG_FILE
        fi
    done
fi

故障类型四:SSH暴力破解自动封禁

通过监控auth.log中的Failed password记录,自动把多次失败的IP加入iptables黑名单。这是非常实用的安全自愈。

#!/bin/bash
# ssh_brute_protect.sh - SSH暴力破解防护
LOG_FILE="/var/log/ops_ssh_protect.log"
MAX_ATTEMPTS=5
BAN_DURATION=3600  # 封禁1小时

# 统计过去10分钟内失败次数超过阈值的IP
FAILED_IPS=$(grep "Failed password" /var/log/auth.log | \
    awk '{print $(NF-3)}' | sort | uniq -c | \
    awk -v max=$MAX_ATTEMPTS '$1 > max {print $2}')

for ip in $FAILED_IPS; do
    if ! iptables -C INPUT -s $ip -j DROP 2>/dev/null; then
        iptables -A INPUT -s $ip -j DROP
        echo "$(date '+%Y-%m-%d %H:%M:%S') 已封禁IP: $ip" >> $LOG_FILE
        # 设置定时解封
        echo "iptables -D INPUT -s $ip -j DROP" | at now + ${BAN_DURATION} seconds 2>/dev/null
    fi
done

四、运维日志管理的最佳实践

脚本写好了不代表万事大吉,日志管理本身也需要规范。第一,所有自愈操作必须有日志记录,出了问题才能回溯。第二,自愈脚本本身也要被监控——如果自愈脚本挂了,你连最后一道防线都没了。建议用另一台机器做心跳检测,或者在脚本里加自我健康检查。第三,定期review自愈脚本的执行记录,每周看一次,发现误杀或漏杀的情况及时调整阈值。

还有一个容易忽略的点:日志轮转。如果不配置logrotate,日志文件会无限增长,最终吃光磁盘。Ubuntu默认已经配了/etc/logrotate.d/下的规则,但建议根据实际情况调整,比如nginx日志保留30天、syslog保留14天。配置示例:

/var/log/nginx/*.log {
    daily
    missingok
    rotate 30
    compress
    delaycompress
    notifempty
    create 0640 www-data adm
    sharedscripts
    postrotate
        [ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
    endscript
}

五、进阶:用Python实现更智能的监控

Shell脚本适合简单场景,但如果需要更复杂的逻辑判断(比如多条件组合告警、历史趋势分析),建议用Python。Python的psutil库可以直接获取系统资源信息,watchdog库可以监控文件变化。一个Python监控daemon可以替代多个Shell脚本,维护起来也更方便。不过要注意,Python脚本要做成systemd服务常驻运行,而不是靠cron轮询,这样响应更快。

总结一下整套方案的架构:日志采集(rsyslog/journalctl)→ 指标提取(Shell/Python定时分析)→ 告警通知(邮件/Webhook)→ 自愈执行(分类脚本)→ 结果记录(运维日志)→ 定期复盘。这套流程跑通之后,80%的常见故障都不需要人工半夜起来处理了。运维的价值不在于救火,而在于让火烧不起来。