脚本如何守护监控关键进程

wen 实用脚本 30

从零构建企业级进程守护与自动恢复体系

目录导读

  1. 为什么关键进程需要脚本守护?——运维痛点与业务连续性风险
  2. 核心原理:进程监控的三大基础机制
  3. 经典脚本实现:从Shell到Python的守护进化
  4. 实战案例:守护数据库与Web服务的完整脚本解析
  5. 进阶优化:日志、告警与自愈能力的增强方案
  6. 常见问题问答(FAQ)
  7. 最佳实践总结与避坑指南

为什么关键进程需要脚本守护?——运维痛点与业务连续性风险

场景还原:某电商公司核心支付网关进程因内存泄漏突然崩溃,由于缺乏自动恢复机制,导致线上支付中断12分钟,直接损失超过30万元,事后分析发现:该进程在崩溃前已有异常日志,但人工监控存在15分钟响应延迟。

脚本如何守护监控关键进程

核心痛点

  • 进程崩溃后,人工重启平均耗时3-8分钟(含排查时间)
  • 复杂业务系统中,一个进程宕机可能触发连锁故障
  • 凌晨时段、节假日期间运维人员无法实时响应
  • 容器化与微服务架构下,进程数量激增(单节点可能运行50+进程)

脚本守护的核心价值:通过自动化脚本实现进程的毫秒级监测秒级自愈全链路告警,将MTTR(平均恢复时间)压缩至30秒以内,关键指标:守护覆盖率应从人工监控的60%提升至99.9%。


核心原理:进程监控的三大基础机制

1 高频探测机制

  • 原理:每隔n秒(通常5-30秒)检查进程是否存在
  • 实现方式
    • Linux: pgrep -f <进程名>pidof <进程名>
    • Windows: tasklist /FI "IMAGENAME eq process.exe"
  • 关键参数:探测间隔需平衡CPU占用与响应速度(推荐生产环境间隔10秒)

2 健康状态验证

  • 检查维度
    • 进程存活:PID文件是否存在、进程是否响应信号
    • 端口可达:netstat -tuln | grep :端口curl http://127.0.0.1:端口/health
    • 内存阈值:ps -p <PID> -o %mem --no-headers | cut -d. -f1
  • 黄金法则:建议同时使用“进程存在”+“端口可达”双重验证,避免僵尸进程误判

3 自愈与降级策略

  • 重启策略:连续失败3次则休眠60秒(防止频繁重启导致系统负载飙升)
  • 备份方案:主进程启动失败时,自动拉起备用进程或降级服务
  • 安全机制:对重启次数进行计数,超过阈值(如10次/小时)触发人工介入

经典脚本实现:从Shell到Python的守护进化

1 Shell版基础守护脚本(适用于Linux简单场景)

#!/bin/bash
PROCESS_NAME="nginx"
CHECK_INTERVAL=10
RESTART_THRESHOLD=3
restart_count=0
while true; do
    if ! pgrep -x "$PROCESS_NAME" > /dev/null; then
        echo "$(date) - 进程 $PROCESS_NAME 已停止,正在重启...(第$((restart_count+1))次)"
        systemctl restart nginx
        restart_count=$((restart_count + 1))
        if [ $restart_count -ge $RESTART_THRESHOLD ]; then
            echo "严重告警:$PROCESS_NAME 连续重启$RESTART_THRESHOLD次失败!" | mail -s "进程守护告警" admin@example.com
            break
        fi
    else
        restart_count=0  # 正常运行时重置计数器
    fi
    sleep $CHECK_INTERVAL
done

限制:无日志持久化、无端口健康检查、重启逻辑简单

2 Python版增强型守护(适用于生产环境)

import subprocess
import time
import psutil
import logging
import smtplib
from datetime import datetime
# 配置
PROCESS_NAMES = ["mysql", "redis-server", "nginx"]
CHECK_INTERVAL = 10
MAX_RESTART_PER_HOUR = 5
HEALTH_ENDPOINTS = {"nginx": "http://127.0.0.1:80/health"}
# 日志配置
logging.basicConfig(
    filename='/var/log/process_guard.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
class ProcessGuard:
    def __init__(self):
        self.restart_history = {name: [] for name in PROCESS_NAMES}
    def check_process_running(self, name):
        for proc in psutil.process_iter(['pid', 'name']):
            if name in proc.info['name']:
                return proc.info['pid']
        return None
    def check_health_endpoint(self, name):
        if name in HEALTH_ENDPOINTS:
            try:
                import requests
                response = requests.get(HEALTH_ENDPOINTS[name], timeout=5)
                return response.status_code == 200
            except:
                return False
        return True  # 无健康检查端点视为通过
    def restart_process(self, name):
        try:
            cmd = f"systemctl restart {name}"
            subprocess.run(cmd, shell=True, check=True)
            self.restart_history[name].append(datetime.now())
            logging.info(f"进程 {name} 重启成功")
            return True
        except subprocess.CalledProcessError as e:
            logging.error(f"进程 {name} 重启失败: {e}")
            return False
    def check_restart_rate(self, name):
        # 清除1小时前的记录
        now = datetime.now()
        self.restart_history[name] = [
            t for t in self.restart_history[name] 
            if (now - t).seconds < 3600
        ]
        return len(self.restart_history[name]) >= MAX_RESTART_PER_HOUR
    def send_alert(self, name, message):
        logging.critical(f"需要人工介入: {name} - {message}")
        # 实际可集成短信/钉钉/邮件告警
        print(f"告警: {message} - 时间: {datetime.now()}")
    def run(self):
        while True:
            for name in PROCESS_NAMES:
                pid = self.check_process_running(name)
                if pid is None:
                    logging.warning(f"进程 {name} 未运行")
                    if not self.check_restart_rate(name):
                        if self.restart_process(name):
                            time.sleep(2)  # 等待启动
                            # 二次确认
                            if not self.check_process_running(name) or not self.check_health_endpoint(name):
                                self.send_alert(name, f"进程重启后仍异常")
                        else:
                            self.send_alert(name, "重启失败")
                    else:
                        self.send_alert(name, f"重启频率过高(近1小时>{MAX_RESTART_PER_HOUR}次)")
                else:
                    if not self.check_health_endpoint(name):
                        logging.error(f"进程 {name} (PID:{pid}) 健康检查失败")
                        # 可考虑优雅重启
                        if self.restart_process(name):
                            logging.info(f"进程 {name} 因健康检查失败重启")
            time.sleep(CHECK_INTERVAL)
if __name__ == "__main__":
    guard = ProcessGuard()
    guard.run()

优势

  • 支持多进程配置
  • 内置健康检查(HTTP端点)
  • 内存化重启频率控制
  • 日志分级与自动告警
  • psutil库提供更多进程指标(CPU/内存实时读取)

实战案例:守护数据库与Web服务的完整脚本解析

案例1:守护MySQL数据库(含主从状态检查)

  • 特殊需求:除进程存活外,还需检查SHOW SLAVE STATUS中的Slave_IO_RunningSlave_SQL_Running
  • 实现
    mysql -e "SHOW SLAVE STATUS\G" | grep -E "Slave_IO_Running|Slave_SQL_Running" | grep -c "Yes"
  • 自愈逻辑:若主从异常,先STOP SLAVE; START SLAVE;,仍失败则重启服务

案例2:守护Nginx(含配置文件验证)

  • 前置检查nginx -t验证配置文件语法
  • 重启命令systemctl reload nginx(热加载)而非restart
  • 回滚机制:若配置文件检验失败,自动回滚至上一份备份配置

效果对标:某生产环境部署后,MySQL因磁盘I/O导致的进程挂起从每月15次降至0次,平均恢复时间<15秒。


进阶优化:日志、告警与自愈能力的增强方案

1 日志体系

  • 日志格式[时间] [进程名] [事件类型] [当前PID] [操作描述]
  • 日志轮转:使用logrotate每日切割,保留30天
  • 趋势分析:通过ELK(Elasticsearch, Logstash, Kibana)聚合日志,发现进程崩溃的规律(如每天凌晨3点高峰)

2 告警分级与通道

告警等级 触发条件 通知方式
信息 进程正常恢复 日志记录
警告 第1-2次重启 企业微信/钉钉机器人
严重 连续重启>3次/小时 电话+工单系统

3 自愈能力增强

  • 预启动:提前在备用机器上启动进程副本,主进程故障时DNS切换
  • 资源清理:重启前执行kill -9(若30秒无响应) + 清除共享内存段
  • 依赖检查:启动前验证数据库连接、磁盘空间、内存余量

常见问题问答(FAQ)

Q1:脚本守护与systemd的Restart=always有何区别?
A:systemd仅监控进程退出信号,无法感知“进程僵死”或“正常响应但性能异常”,脚本可通过健康检查、资源阈值、业务逻辑验证实现更精细的守护,建议:基础进程交给systemd,关键业务进程叠加脚本守护。

Q2:如何避免脚本自身变成“进程孤儿”?
A:将脚本注册为systemd服务,设置Restart=always;或在脚本内使用setsid创建新会话组,确保父进程退出后不被影响。

Q3:守护进程自身占用资源如何控制?
A:Python版每个检查周期消耗CPU约5-20ms;建议守护脚本单独运行在低优先级(nice -n 19),监控间隔>5秒。

Q4:容器环境下如何守护进程?
A:使用Docker的restart=always策略,但更推荐在容器内使用supervisor或s6-overlay,并在容器外使用Kubernetes的livenessProbe(存活探针)。


最佳实践总结与避坑指南

核心原则

  1. 最小化干扰:守护脚本的CPU占用应<1%,内存<50MB
  2. 渐进式恢复:优先尝试SIGTERM -> SIGKILL -> 服务重启分层处理
  3. 失败阈值:严格执行“连续N次失败则暂停并告警”,避免无限重启
  4. 日志先行:所有操作必须记录日志(包括成功的恢复)

常见陷阱

  • ❌ 单进程名匹配错误(如python匹配到多个脚本)
  • ❌ 不检查进程Zombie状态(ps输出Z表示僵死)
  • ❌ 重启前不验证依赖服务可用性(如MySQL未准备好时启动应用)
  • ❌ 忽略网络超时导致健康检查误判(建议设置timeout参数)

推荐部署架构

[业务进程] <--守护脚本--> [systemd服务]
     ↑                        ↓
[日志采集]               [告警网关]
     ↓                        ↓
[ELK分析]               [短信/电话/邮件]

脚本守护的关键不在于写得多复杂,而在于“持续可靠地运行”,建议从最简单的shell脚本开始,逐步添加健康检查、告警、频率控制等功能,一个稳定的守护脚本,应当本身就不需要被守护。

抱歉,评论功能暂时关闭!