在日常的服务器运维工作中,我们经常会遇到一些服务莫名其妙挂掉的情况。作为运维人员,总不能24小时盯着服务器吧?这时候,一个能够自动监控服务状态并在异常时自动重启的脚本就显得尤为重要了。今天,我们就来聊聊如何用Shell脚本来实现这个功能。

一、为什么选择Shell脚本

Shell脚本是Linux系统管理员的瑞士军刀。它直接运行在系统环境中,不需要额外的依赖,执行效率高,而且几乎所有的Linux发行版都预装了bash。相比其他语言,Shell脚本特别适合编写这种系统级的监控任务。

想象一下,你正在度假,突然收到报警说某个关键服务挂了。这时候如果有一个自动重启的脚本,就能帮你避免半夜爬起来处理问题的尴尬。Shell脚本就像是一个不知疲倦的运维助手,7×24小时帮你盯着系统。

二、基础监控脚本编写

我们先从一个最简单的监控脚本开始。假设我们要监控的是Nginx服务,下面是一个基础版的监控脚本:

#!/bin/bash

# 定义要监控的服务名称
SERVICE_NAME="nginx"

# 检查服务状态
if systemctl is-active --quiet $SERVICE_NAME; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $SERVICE_NAME 运行正常"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $SERVICE_NAME 已停止,尝试重启..."
    
    # 尝试重启服务
    systemctl restart $SERVICE_NAME
    
    # 检查重启是否成功
    if systemctl is-active --quiet $SERVICE_NAME; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') - $SERVICE_NAME 重启成功"
    else
        echo "$(date '+%Y-%m-%d %H:%M:%S') - $SERVICE_NAME 重启失败,请手动检查"
    fi
fi

这个脚本做了以下几件事:

  1. 定义了要监控的服务名称
  2. 检查服务是否在运行
  3. 如果服务停止,尝试重启
  4. 记录操作日志

三、进阶功能实现

基础脚本虽然能用,但还不够完善。让我们给它添加一些更实用的功能:

1. 添加邮件报警功能

#!/bin/bash

# 配置信息
SERVICE_NAME="nginx"
ADMIN_EMAIL="admin@example.com"
MAX_RETRY=3

# 检查服务状态
if ! systemctl is-active --quiet $SERVICE_NAME; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $SERVICE_NAME 已停止,尝试重启..."
    
    # 尝试重启,最多重试MAX_RETRY次
    for ((i=1; i<=$MAX_RETRY; i++)); do
        systemctl restart $SERVICE_NAME
        sleep 5  # 等待5秒让服务启动
        
        if systemctl is-active --quiet $SERVICE_NAME; then
            echo "$(date '+%Y-%m-%d %H:%M:%S') - 第$i次尝试: $SERVICE_NAME 重启成功" | mail -s "服务恢复通知" $ADMIN_EMAIL
            exit 0
        else
            echo "$(date '+%Y-%m-%d %H:%M:%S') - 第$i次尝试: $SERVICE_NAME 重启失败"
        fi
    done
    
    # 所有尝试都失败后发送报警邮件
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $SERVICE_NAME 重启失败,请手动检查" | mail -s "紧急: 服务恢复失败" $ADMIN_EMAIL
    exit 1
fi

echo "$(date '+%Y-%m-%d %H:%M:%S') - $SERVICE_NAME 运行正常"
exit 0

2. 添加日志记录功能

#!/bin/bash

# 配置信息
SERVICE_NAME="nginx"
LOG_FILE="/var/log/service_monitor.log"
MAX_LOG_SIZE=1048576  # 1MB

# 日志文件维护
if [ -f "$LOG_FILE" ] && [ $(stat -c%s "$LOG_FILE") -gt $MAX_LOG_SIZE ]; then
    mv "$LOG_FILE" "${LOG_FILE}.old"
fi

# 记录日志函数
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}

# 主监控逻辑
if ! systemctl is-active --quiet $SERVICE_NAME; then
    log "$SERVICE_NAME 已停止,尝试重启..."
    systemctl restart $SERVICE_NAME
    
    if systemctl is-active --quiet $SERVICE_NAME; then
        log "$SERVICE_NAME 重启成功"
    else
        log "$SERVICE_NAME 重启失败"
    fi
else
    log "$SERVICE_NAME 运行正常"
fi

四、完整的企业级实现

结合前面的功能,下面是一个更完整的企业级实现:

#!/bin/bash

# 配置区
SERVICE_NAME="nginx"                # 监控的服务名称
ADMIN_EMAIL="admin@example.com"     # 管理员邮箱
MAX_RETRY=3                         # 最大重试次数
RETRY_INTERVAL=10                   # 重试间隔(秒)
LOG_FILE="/var/log/service_monitor_${SERVICE_NAME}.log"  # 日志文件路径
MAX_LOG_SIZE=1048576                # 日志文件最大大小(1MB)

# 初始化日志
init_log() {
    # 如果日志文件超过最大大小,则轮转
    if [ -f "$LOG_FILE" ] && [ $(stat -c%s "$LOG_FILE") -gt $MAX_LOG_SIZE ]; then
        mv "$LOG_FILE" "${LOG_FILE}.$(date +%Y%m%d%H%M%S)"
    fi
    # 确保日志目录存在
    mkdir -p "$(dirname "$LOG_FILE")"
}

# 记录日志
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}

# 发送邮件通知
send_alert() {
    local subject=$1
    local message=$2
    echo "$message" | mail -s "$subject" "$ADMIN_EMAIL"
}

# 主监控函数
monitor_service() {
    # 检查服务状态
    if systemctl is-active --quiet "$SERVICE_NAME"; then
        log "服务 $SERVICE_NAME 运行正常"
        return 0
    fi
    
    log "检测到服务 $SERVICE_NAME 已停止"
    
    # 尝试重启
    local retry_count=0
    while [ $retry_count -lt $MAX_RETRY ]; do
        retry_count=$((retry_count + 1))
        
        log "尝试第 $retry_count 次重启 $SERVICE_NAME..."
        systemctl restart "$SERVICE_NAME"
        sleep $RETRY_INTERVAL
        
        if systemctl is-active --quiet "$SERVICE_NAME"; then
            local msg="服务 $SERVICE_NAME 在第 $retry_count 次尝试后成功重启"
            log "$msg"
            send_alert "服务恢复通知: $SERVICE_NAME" "$msg"
            return 0
        else
            log "第 $retry_count 次重启 $SERVICE_NAME 失败"
        fi
    done
    
    # 所有尝试都失败
    local alert_msg="无法恢复服务 $SERVICE_NAME,已尝试 $MAX_RETRY 次重启"
    log "$alert_msg"
    send_alert "紧急: 服务恢复失败 - $SERVICE_NAME" "$alert_msg"
    return 1
}

# 主程序
main() {
    init_log
    monitor_service
}

main "$@"

这个完整版脚本包含了:

  1. 可配置的参数
  2. 日志轮转功能
  3. 邮件报警功能
  4. 多次重试机制
  5. 详细的日志记录

五、部署与定时执行

写好了脚本,我们需要把它部署到服务器上并设置定时执行:

  1. 将脚本保存为 /usr/local/bin/service_monitor.sh
  2. 添加执行权限:chmod +x /usr/local/bin/service_monitor.sh
  3. 设置cron定时任务,每分钟检查一次:
# 编辑crontab
crontab -e

# 添加以下行
* * * * * /usr/local/bin/service_monitor.sh

六、技术优缺点分析

优点:

  1. 轻量级:不需要安装额外软件,直接使用系统自带工具
  2. 灵活:可以轻松修改以适应不同服务的监控需求
  3. 低资源消耗:相比专门的监控系统,Shell脚本几乎不占用系统资源
  4. 快速响应:问题发现和恢复都在秒级完成

缺点:

  1. 功能有限:相比专业监控系统,缺少可视化、历史数据分析等功能
  2. 扩展性差:监控大量服务时需要维护多个脚本
  3. 依赖系统工具:如mail命令需要事先配置好邮件发送功能

七、注意事项

  1. 权限问题:确保脚本有足够的权限执行服务重启操作,通常需要root权限
  2. 邮件配置:服务器需要正确配置邮件发送功能,否则报警无法送达
  3. 日志轮转:长期运行的脚本要注意日志文件大小,避免撑满磁盘
  4. 避免过度重启:设置合理的重试次数和间隔,避免因短暂故障导致频繁重启
  5. 测试验证:部署前要充分测试,确保脚本在各种异常情况下行为符合预期

八、应用场景

这种监控脚本特别适合以下场景:

  1. 小型项目或初创公司,没有预算部署专业监控系统
  2. 对特定关键服务的额外监控,作为现有监控系统的补充
  3. 临时性监控需求,快速实现而不想搭建复杂系统
  4. 资源受限的环境,无法运行重量级监控代理

九、总结

通过Shell脚本实现服务监控与自动重启是一个简单而有效的解决方案。虽然它不能替代专业的监控系统,但在很多场景下已经足够好用。本文从基础到进阶,逐步构建了一个功能完善的监控脚本,你可以根据自己的需求进行调整和扩展。

记住,自动化运维的核心目标是让我们从重复性工作中解放出来,把精力集中在更有价值的事情上。一个好的监控脚本就像是一个可靠的助手,帮你守护着系统的稳定运行。