Python自动化脚本实战:批量创建用户、文件备份、日志清理、服务监控

在日常工作中,我们经常需要重复执行批量创建用户、定期备份文件、清理过期日志、监控服务状态等机械性操作。手动处理不仅耗时耗力,还容易出错。Python作为一门简洁高效的脚本语言,能轻松实现这些操作的自动化,帮我们节省大量时间。本文将分享4个企业级常用自动化脚本的完整实现,包含详细注释和使用教程,内容兼顾新手入门与实际生产需求,适合直接落地使用。

一、批量创建Linux用户(服务器运维必备)

场景说明

在管理Linux服务器时,经常需要为团队成员、测试环境批量创建用户(比如新员工入职、搭建多账号测试环境)。手动执行useradd命令效率极低,用脚本可一键完成批量创建、密码设置、权限配置。

实现思路

  1. 定义需要创建的用户名列表(可从文件读取,灵活扩展);
  2. 生成随机强密码(包含大小写字母、数字、特殊字符);
  3. 通过subprocess模块执行Linux命令(useradd创建用户、echo设置密码);
  4. 记录创建结果(用户名+密码)到文件,方便分发;
  5. 加入异常处理,避免重复创建用户报错。

完整代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Linux批量创建用户脚本
功能:批量创建用户、设置随机强密码、记录账号信息
适用环境:Linux系统(CentOS/Ubuntu),需root权限运行
"""
import subprocess
import string
import random
import os

def generate_strong_password(length=12):
    """生成随机强密码:包含大小写字母、数字、特殊字符"""
    # 密码字符集:排除容易混淆的字符(如l、O、0、1)
    lowercase = string.ascii_lowercase.replace('l', '').replace('o', '')
    uppercase = string.ascii_uppercase.replace('O', '').replace('I', '')
    digits = string.digits.replace('0', '').replace('1', '')
    special_chars = '!@#$%^&*()_+-=[]{}|;:,.?~'
    
    # 确保密码包含每种字符类型
    password = [
        random.choice(lowercase),
        random.choice(uppercase),
        random.choice(digits),
        random.choice(special_chars)
    ]
    
    # 填充剩余长度
    all_chars = lowercase + uppercase + digits + special_chars
    password += [random.choice(all_chars) for _ in range(length - 4)]
    
    # 打乱密码顺序
    random.shuffle(password)
    return ''.join(password)

def create_linux_user(username, password):
    """创建Linux用户并设置密码"""
    try:
        # 1. 检查用户是否已存在(id命令返回0表示存在)
        check_cmd = f"id -u {username}"
        subprocess.run(
            check_cmd, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
        )
        print(f"⚠️ 用户 {username} 已存在,跳过创建")
        return False
    except subprocess.CalledProcessError:
        # 2. 用户不存在,执行创建命令
        try:
            # 创建用户(-m自动创建家目录,-s指定bash shell)
            useradd_cmd = f"useradd -m -s /bin/bash {username}"
            subprocess.run(useradd_cmd, shell=True, check=True, sudo=True)
            
            # 设置密码(echo密码通过管道传给passwd,--stdin避免交互)
            passwd_cmd = f"echo '{username}:{password}' | chpasswd"
            subprocess.run(passwd_cmd, shell=True, check=True, sudo=True)
            
            # 可选:添加sudo权限(根据需求注释/启用)
            # sudo_cmd = f"echo '{username} ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers"
            # subprocess.run(sudo_cmd, shell=True, check=True, sudo=True)
            
            print(f"✅ 用户 {username} 创建成功")
            return True
        except subprocess.CalledProcessError as e:
            print(f"❌ 创建用户 {username} 失败:{str(e)}")
            return False

def batch_create_users(user_list, output_file="user_accounts.txt"):
    """批量创建用户并记录账号信息到文件"""
    # 清空历史记录文件(若存在)
    if os.path.exists(output_file):
        os.remove(output_file)
    
    # 遍历用户列表创建
    success_count = 0
    with open(output_file, "w", encoding="utf-8") as f:
        f.write("Linux批量创建用户账号信息\n")
        f.write("="*50 + "\n")
        f.write(f"用户名\t密码\t创建时间\n")
        f.write("="*50 + "\n")
        
        for username in user_list:
            password = generate_strong_password()
            if create_linux_user(username, password):
                # 记录账号信息(包含创建时间,需系统安装date命令)
                create_time = subprocess.getoutput("date '+%Y-%m-%d %H:%M:%S'")
                f.write(f"{username}\t{password}\t{create_time}\n")
                success_count += 1
    
    print(f"\n📊 批量创建完成:成功{success_count}个,失败{len(user_list)-success_count}个")
    print(f"账号信息已保存到:{os.path.abspath(output_file)}")

if __name__ == "__main__":
    # 1. 定义需要创建的用户列表(可从文件读取,示例:user_list = [line.strip() for line in open('users.txt')])
    user_list = ["dev01", "dev02", "test01", "test02", "ops01"]
    
    # 2. 检查是否为root用户(Linux创建用户需root权限)
    if os.geteuid() != 0:
        print("❌ 错误:创建Linux用户需root权限,请用sudo或root用户运行!")
        exit(1)
    
    # 3. 执行批量创建
    batch_create_users(user_list)

使用方法

  1. 环境准备:Linux系统(CentOS/Ubuntu),安装Python3(一般系统自带);
  2. 修改用户列表:将user_list改为你需要创建的用户名(支持从文件读取,见代码注释);
  3. 运行脚本:需root权限,执行命令 sudo python3 batch_create_users.py
  4. 查看结果:脚本会生成user_accounts.txt文件,包含所有成功创建的用户名和密码。

注意事项

  1. 必须root权限运行(Linux创建用户的系统限制);
  2. 密码生成后自动记录到文件,建议创建完成后及时分发给用户并提醒修改密码;
  3. 若需要给用户添加sudo权限,可启用代码中“添加sudo权限”的相关命令(谨慎使用,避免权限泄露);
  4. 支持Ubuntu/CentOS主流Linux发行版,其他发行版可能需要调整useradd命令参数。

二、文件自动备份脚本(支持定时/增量备份)

场景说明

重要文件(如项目代码、数据库备份、配置文件)需要定期备份,防止误删、病毒攻击导致数据丢失。该脚本支持指定备份目录、备份到本地/远程服务器、增量备份(只备份新增/修改的文件),可配合Linux定时任务(crontab)实现自动执行。

实现思路

  1. 配置备份源目录、目标目录(本地或远程SSH目录);
  2. 支持全量备份(每次备份所有文件)和增量备份(基于文件修改时间);
  3. 备份文件压缩为zip包,文件名包含时间戳(便于区分版本);
  4. 自动清理过期备份(按天数保留,避免占用过多磁盘空间);
  5. 支持备份完成后发送邮件通知(可选)。

完整代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
文件自动备份脚本
功能:全量/增量备份、压缩存储、过期清理、邮件通知
适用环境:Windows/Linux/MacOS,支持本地备份和SSH远程备份
"""
import os
import zipfile
import shutil
import time
from datetime import datetime, timedelta
import smtplib
from email.mime.text import MIMEText
from email.header import Header

# ==================== 备份配置(根据需求修改)====================
BACKUP_SOURCE = ["/home/user/project", "/etc/nginx"]  # 要备份的源目录(可多个)
BACKUP_DEST = "/data/backups"  # 备份目标目录(本地)
# BACKUP_DEST = "user@192.168.1.100:/remote/backups"  # 远程SSH目录(需配置免密登录)
BACKUP_TYPE = "incremental"  # 备份类型:full(全量)/ incremental(增量)
RETENTION_DAYS = 7  # 备份保留天数(超过自动删除)
ZIP_PASSWORD = "backup@2024"  # 压缩包密码(为空则不加密)
EMAIL_NOTIFY = False  # 是否启用邮件通知
SMTP_CONFIG = {
    "server": "smtp.qq.com",
    "port": 465,
    "username": "your_email@qq.com",
    "password": "your_smtp_password",
    "recipient": "notify_email@xxx.com"
}

def get_backup_filename():
    """生成备份文件名:包含时间戳和备份类型"""
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    return f"backup_{timestamp}_{BACKUP_TYPE}.zip"

def is_file_modified(file_path, base_time):
    """判断文件是否在基准时间后被修改(用于增量备份)"""
    file_mtime = os.path.getmtime(file_path)  # 文件最后修改时间(时间戳)
    return file_mtime > base_time

def get_last_full_backup_time():
    """获取最近一次全量备份的时间(用于增量备份基准)"""
    last_full_time = 0
    if not os.path.exists(BACKUP_DEST):
        return last_full_time
    
    # 遍历备份目录,查找最近的全量备份文件
    for filename in os.listdir(BACKUP_DEST):
        if filename.startswith("backup_") and "_full.zip" in filename:
            try:
                # 从文件名提取时间戳(如backup_20240520_143000_full.zip)
                timestamp_str = filename.split("_")[1]
                backup_time = datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S").timestamp()
                if backup_time > last_full_time:
                    last_full_time = backup_time
            except Exception as e:
                print(f"⚠️ 解析全量备份文件 {filename} 失败:{str(e)}")
    return last_full_time if last_full_time != 0 else time.time() - 86400  # 默认基准:24小时前

def backup_files(backup_filename):
    """执行文件备份并压缩"""
    backup_path = os.path.join(BACKUP_DEST, backup_filename)
    base_time = get_last_full_backup_time() if BACKUP_TYPE == "incremental" else 0
    
    try:
        # 若目标是远程SSH目录,先备份到本地临时目录
        temp_backup = backup_path
        if "@" in BACKUP_DEST and ":" in BACKUP_DEST:
            temp_dir = "/tmp/local_backup_temp"
            os.makedirs(temp_dir, exist_ok=True)
            temp_backup = os.path.join(temp_dir, backup_filename)
        
        # 创建压缩包(支持密码加密)
        with zipfile.ZipFile(temp_backup, "w", zipfile.ZIP_DEFLATED) as zipf:
            # 若设置密码,使用zipfile的setpassword(仅支持传统ZIP加密)
            if ZIP_PASSWORD:
                zipf.setpassword(ZIP_PASSWORD.encode("utf-8"))
            
            # 遍历所有源目录,添加文件到压缩包
            for source_dir in BACKUP_SOURCE:
                if not os.path.exists(source_dir):
                    print(f"⚠️ 源目录 {source_dir} 不存在,跳过")
                    continue
                
                for root, dirs, files in os.walk(source_dir):
                    for file in files:
                        file_path = os.path.join(root, file)
                        # 增量备份:只添加修改时间在基准时间之后的文件
                        if BACKUP_TYPE == "incremental" and not is_file_modified(file_path, base_time):
                            continue
                        
                        # 保留文件的相对路径(便于恢复)
                        arcname = os.path.relpath(file_path, start=os.path.commonpath(BACKUP_SOURCE))
                        zipf.write(file_path, arcname=arcname)
                        print(f"📤 备份文件:{file_path}")
        
        # 若目标是远程目录,通过scp复制(需配置SSH免密登录)
        if "@" in BACKUP_DEST and ":" in BACKUP_DEST:
            scp_cmd = f"scp {temp_backup} {BACKUP_DEST}"
            os.system(scp_cmd)
            os.remove(temp_backup)  # 删除本地临时文件
            print(f"🌐 已将备份文件上传到远程服务器:{BACKUP_DEST}")
        
        print(f"✅ 备份完成:{backup_path}")
        return True, backup_path
    except Exception as e:
        print(f"❌ 备份失败:{str(e)}")
        # 若备份过程出错,删除不完整的压缩包
        if os.path.exists(temp_backup):
            os.remove(temp_backup)
        return False, ""

def clean_expired_backups():
    """清理过期的备份文件"""
    if not os.path.exists(BACKUP_DEST) or "@" in BACKUP_DEST:
        return  # 远程目录暂不支持自动清理
    
    expired_time = datetime.now() - timedelta(days=RETENTION_DAYS)
    deleted_count = 0
    
    for filename in os.listdir(BACKUP_DEST):
        file_path = os.path.join(BACKUP_DEST, filename)
        if os.path.isfile(file_path) and filename.startswith("backup_"):
            try:
                # 从文件名提取备份时间
                timestamp_str = filename.split("_")[1]
                backup_time = datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S")
                if backup_time < expired_time:
                    os.remove(file_path)
                    print(f"🗑️ 删除过期备份:{filename}")
                    deleted_count += 1
            except Exception as e:
                print(f"⚠️ 清理文件 {filename} 失败:{str(e)}")
    
    print(f"🧹 过期备份清理完成:共删除 {deleted_count} 个文件")

def send_email_notify(success, backup_path):
    """发送备份结果邮件通知"""
    if not EMAIL_NOTIFY:
        return
    
    subject = f"【{'成功' if success else '失败'}】文件备份通知"
    content = f"""
    <h3>文件备份执行结果</h3>
    <p>备份时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
    <p>备份类型:{BACKUP_TYPE}</p>
    <p>源目录:{', '.join(BACKUP_SOURCE)}</p>
    <p>目标位置:{BACKUP_DEST}</p>
    <p>备份状态:{'✅ 成功' if success else '❌ 失败'}</p>
    {f'<p>备份文件:{backup_path}</p>' if success else ''}
    <p>保留天数:{RETENTION_DAYS}天</p>
    """
    
    try:
        # 配置邮件
        msg = MIMEText(content, "html", "utf-8")
        msg["From"] = Header(SMTP_CONFIG["username"], "utf-8")
        msg["To"] = Header(SMTP_CONFIG["recipient"], "utf-8")
        msg["Subject"] = Header(subject, "utf-8")
        
        # 发送邮件(SSL加密)
        server = smtplib.SMTP_SSL(SMTP_CONFIG["server"], SMTP_CONFIG["port"])
        server.login(SMTP_CONFIG["username"], SMTP_CONFIG["password"])
        server.sendmail(
            SMTP_CONFIG["username"],
            SMTP_CONFIG["recipient"],
            msg.as_string()
        )
        server.quit()
        print(f"📧 邮件通知已发送到:{SMTP_CONFIG['recipient']}")
    except Exception as e:
        print(f"❌ 发送邮件通知失败:{str(e)}")

def main():
    print("="*60)
    print(f"📅 开始执行文件备份({datetime.now().strftime('%Y-%m-%d %H:%M:%S')})")
    print(f"🔧 备份配置:类型={BACKUP_TYPE},保留={RETENTION_DAYS}天,加密={'是' if ZIP_PASSWORD else '否'}")
    print("="*60)
    
    # 1. 生成备份文件名
    backup_filename = get_backup_filename()
    
    # 2. 执行备份
    success, backup_path = backup_files(backup_filename)
    
    # 3. 清理过期备份
    clean_expired_backups()
    
    # 4. 发送邮件通知
    send_email_notify(success, backup_path)
    
    print("="*60)
    print(f"📋 备份任务执行结束({datetime.now().strftime('%Y-%m-%d %H:%M:%S')})")
    print("="*60)

if __name__ == "__main__":
    main()

使用方法

  1. 配置修改:根据实际需求修改脚本开头的BACKUP_SOURCE(备份源目录)、BACKUP_DEST(备份目标)等参数;
  2. 依赖安装:若启用邮件通知,需确保系统安装smtplib(Python标准库,一般自带);
  3. 远程备份配置:若备份到远程服务器,需先配置SSH免密登录(ssh-keygen生成密钥,ssh-copy-id复制到远程服务器);
  4. 定时执行(Linux):通过crontab设置定时任务,例如每天凌晨2点执行:
    # 编辑crontab配置
    crontab -e
    # 添加一行(Python路径和脚本路径需替换为实际路径)
    0 2 * * * /usr/bin/python3 /home/user/scripts/file_backup.py >> /var/log/backup.log 2>&1
    

注意事项

  1. 增量备份依赖最近一次全量备份,建议每周执行一次全量备份,每天执行增量备份;
  2. 压缩包密码仅支持传统ZIP加密,安全性一般,重要数据建议结合其他加密方式;
  3. 备份目录需提前创建并确保有写入权限;
  4. 定期检查备份日志和备份文件完整性,避免备份失败未发现。

三、日志清理脚本(自动删除过期/超大日志)

场景说明

服务器日志(如Nginx、Tomcat、应用程序日志)会持续增长,占用大量磁盘空间,甚至导致磁盘满溢影响服务运行。该脚本可按日志文件的时间(过期天数)大小(超过阈值) 自动清理,支持日志切割备份后再清理,避免误删有用日志。

实现思路

  1. 配置需要清理的日志目录、文件后缀(如.log.log.*);
  2. 定义清理规则:过期天数(如7天前的日志)、文件大小阈值(如100MB);
  3. 支持两种清理方式:直接删除、压缩备份后删除;
  4. 遍历日志目录,筛选符合清理规则的文件并执行操作;
  5. 记录清理日志,便于后续审计。

完整代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
日志清理脚本
功能:按时间/大小清理日志、压缩备份、记录清理日志
适用环境:Windows/Linux/MacOS
"""
import os
import time
import zipfile
from datetime import datetime, timedelta

# ==================== 清理配置(根据需求修改)====================
LOG_DIRS = ["/var/log/nginx", "/var/log/tomcat", "/home/user/app/logs"]  # 日志目录
LOG_SUFFIX = [".log", ".log.", ".txt", ".out"]  # 需要清理的日志文件后缀
EXPIRE_DAYS = 7  # 日志过期天数(超过自动清理)
MAX_SIZE_MB = 100  # 日志最大大小(MB),超过自动清理(0表示不按大小清理)
CLEAN_MODE = "delete"  # 清理模式:delete(直接删除)/ compress(压缩备份后删除)
BACKUP_DIR = "/var/log/clean_backup"  # 压缩备份目录(CLEAN_MODE=compress时生效)
LOG_FILE = "/var/log/log_cleaner.log"  # 清理日志记录文件

def init_backup_dir():
    """初始化备份目录(若不存在则创建)"""
    if CLEAN_MODE == "compress" and not os.path.exists(BACKUP_DIR):
        os.makedirs(BACKUP_DIR, exist_ok=True)
        log_info(f"创建备份目录:{BACKUP_DIR}")

def log_info(message):
    """记录清理日志(包含时间戳)"""
    log_msg = f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {message}\n"
    print(log_msg, end="")
    # 写入日志文件
    with open(LOG_FILE, "a", encoding="utf-8") as f:
        f.write(log_msg)

def get_file_size_mb(file_path):
    """获取文件大小(单位:MB)"""
    file_size = os.path.getsize(file_path)
    return round(file_size / 1024 / 1024, 2)

def is_log_file(file_name):
    """判断是否为需要清理的日志文件(根据后缀匹配)"""
    return any(file_name.endswith(suffix) for suffix in LOG_SUFFIX)

def compress_and_delete(file_path):
    """压缩文件后删除原文件"""
    try:
        # 生成备份文件名(原文件名+时间戳)
        file_dir, file_name = os.path.split(file_path)
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_filename = f"{os.path.splitext(file_name)[0]}_{timestamp}.zip"
        backup_path = os.path.join(BACKUP_DIR, backup_filename)
        
        # 压缩文件
        with zipfile.ZipFile(backup_path, "w", zipfile.ZIP_DEFLATED) as zipf:
            zipf.write(file_path, arcname=file_name)
        
        # 删除原文件
        os.remove(file_path)
        log_info(f"📦 压缩备份并删除:{file_path} → {backup_path}")
        return True
    except Exception as e:
        log_info(f"❌ 压缩文件 {file_path} 失败:{str(e)}")
        return False

def clean_log_files():
    """执行日志清理"""
    init_backup_dir()
    total_cleaned = 0
    total_size_freed = 0  # 释放的磁盘空间(MB)
    
    for log_dir in LOG_DIRS:
        if not os.path.exists(log_dir):
            log_info(f"⚠️ 日志目录 {log_dir} 不存在,跳过")
            continue
        
        log_info(f"🔍 开始扫描日志目录:{log_dir}")
        
        for root, dirs, files in os.walk(log_dir):
            # 跳过备份目录(避免清理刚备份的日志)
            if BACKUP_DIR in root:
                continue
            
            for file in files:
                if not is_log_file(file):
                    continue  # 只处理指定后缀的日志文件
                
                file_path = os.path.join(root, file)
                file_size = get_file_size_mb(file_path)
                file_mtime = os.path.getmtime(file_path)  # 文件最后修改时间
                expire_time = time.time() - EXPIRE_DAYS * 86400  # 过期时间戳
                
                # 满足以下任一条件则清理:1. 过期 2. 超过最大大小(且MAX_SIZE_MB>0)
                need_clean = False
                if file_mtime < expire_time:
                    need_clean = True
                    reason = f"过期({EXPIRE_DAYS}天)"
                elif MAX_SIZE_MB > 0 and file_size > MAX_SIZE_MB:
                    need_clean = True
                    reason = f"超过最大大小({file_size}MB > {MAX_SIZE_MB}MB)"
                
                if need_clean:
                    try:
                        if CLEAN_MODE == "compress":
                            if compress_and_delete(file_path):
                                total_cleaned += 1
                                total_size_freed += file_size
                        else:
                            os.remove(file_path)
                            log_info(f"🗑️ 直接删除日志:{file_path}(原因:{reason},大小:{file_size}MB)")
                            total_cleaned += 1
                            total_size_freed += file_size
                    except Exception as e:
                        log_info(f"❌ 删除日志 {file_path} 失败:{str(e)}")
    
    log_info(f"✅ 日志清理完成:共清理 {total_cleaned} 个文件,释放 {total_size_freed:.2f} MB 磁盘空间")
    log_info("-"*80 + "\n")

def main():
    log_info("="*80)
    log_info("🚀 开始执行日志清理任务")
    log_info(f"📌 清理配置:过期天数={EXPIRE_DAYS}天,最大大小={MAX_SIZE_MB}MB,模式={CLEAN_MODE}")
    log_info("="*80)
    
    clean_log_files()

if __name__ == "__main__":
    main()

使用方法

  1. 配置修改:修改LOG_DIRS为需要清理的日志目录,根据需求调整EXPIRE_DAYS(过期天数)、MAX_SIZE_MB(最大大小)等参数;
  2. 清理模式选择:CLEAN_MODE="delete"直接删除日志(高效),CLEAN_MODE="compress"先压缩备份再删除(更安全);
  3. 定时执行:Linux系统通过crontab设置每周日凌晨3点执行:
    crontab -e
    # 添加一行
    0 3 * * 0 /usr/bin/python3 /home/user/scripts/log_cleaner.py
    

注意事项

  1. 首次使用前建议先注释清理逻辑(os.removecompress_and_delete),仅执行扫描日志,确认筛选的文件正确后再启用清理;
  2. 避免将重要日志目录(如系统核心日志)加入LOG_DIRS,防止误删;
  3. 定期检查清理日志(LOG_FILE),确认清理任务正常执行;
  4. 若日志文件正在被程序写入(如实时日志),直接删除可能导致程序报错,建议结合日志切割工具(如logrotate)使用。

四、服务监控脚本(自动检查状态+重启+报警)

场景说明

服务器上的核心服务(如Nginx、MySQL、Redis、应用程序)可能因内存溢出、端口占用、配置错误等原因意外停止,导致业务中断。该脚本可实时监控服务状态,发现服务停止后自动重启,同时通过邮件/短信报警通知运维人员,减少业务中断时间。

实现思路

  1. 配置需要监控的服务列表(服务名、启动命令、监控方式);
  2. 支持多种监控方式:端口监听(如MySQL默认3306端口)、进程名称(如nginx进程)、自定义检测命令;
  3. 服务停止时执行自动重启,重启失败则发送报警通知;
  4. 记录监控日志,包含服务状态、重启记录、报警信息;
  5. 支持邮件报警,可扩展短信、企业微信/钉钉报警。

完整代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
服务监控脚本
功能:监控服务状态、自动重启、邮件报警、日志记录
适用环境:Linux系统(支持systemd服务管理)
"""
import subprocess
import time
import os
from datetime import datetime
import smtplib
from email.mime.text import MIMEText
from email.header import Header

# ==================== 监控配置(根据需求修改)====================
# 服务列表:name(服务名)、type(监控类型:port/process/command)、target(端口/进程名/检测命令)、start_cmd(启动命令)
MONITOR_SERVICES = [
    {
        "name": "Nginx",
        "type": "port",
        "target": 80,
        "start_cmd": "systemctl start nginx"
    },
    {
        "name": "MySQL",
        "type": "port",
        "target": 3306,
        "start_cmd": "systemctl start mysqld"
    },
    {
        "name": "Redis",
        "type": "process",
        "target": "redis-server",
        "start_cmd": "systemctl start redis"
    },
    {
        "name": "AppService",
        "type": "command",
        "target": "curl -s http://localhost:8080/health | grep 'UP'",  # 健康检查命令
        "start_cmd": "systemctl start app-service"
    }
]
CHECK_INTERVAL = 60  # 监控检查间隔(秒)
RESTART_RETRY = 2  # 重启失败重试次数
LOG_FILE = "/var/log/service_monitor.log"
# 邮件报警配置
ALERT_EMAIL = {
    "enable": True,
    "smtp_server": "smtp.163.com",
    "smtp_port": 465,
    "username": "your_alert@163.com",
    "password": "your_smtp_password",
    "recipients": ["admin1@xxx.com", "admin2@xxx.com"]  # 多个收件人
}

def log_info(message):
    """记录监控日志"""
    log_msg = f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {message}\n"
    print(log_msg, end="")
    with open(LOG_FILE, "a", encoding="utf-8") as f:
        f.write(log_msg)

def execute_command(cmd, timeout=30):
    """执行系统命令并返回结果(状态码+输出)"""
    try:
        result = subprocess.run(
            cmd, shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout
        )
        return result.returncode, result.stdout.decode("utf-8", errors="ignore").strip()
    except subprocess.TimeoutExpired:
        return -1, "命令执行超时"
    except Exception as e:
        return -2, f"命令执行异常:{str(e)}"

def check_service_status(service):
    """检查单个服务状态"""
    service_name = service["name"]
    monitor_type = service["type"]
    target = service["target"]
    
    try:
        if monitor_type == "port":
            # 监控端口:使用ss命令检查端口是否监听(比netstat更高效)
            cmd = f"ss -tuln | grep ': {target} '"
            returncode, _ = execute_command(cmd)
            is_running = returncode == 0
        elif monitor_type == "process":
            # 监控进程:使用pgrep检查进程是否存在
            cmd = f"pgrep -x {target}"  # -x 精确匹配进程名
            returncode, _ = execute_command(cmd)
            is_running = returncode == 0
        elif monitor_type == "command":
            # 自定义命令:返回码为0表示服务正常
            returncode, _ = execute_command(target)
            is_running = returncode == 0
        else:
            log_info(f"⚠️ 服务 {service_name} 监控类型 {monitor_type} 不支持")
            return False
        
        status = "运行中" if is_running else "已停止"
        log_info(f"📊 服务 {service_name} 状态:{status}")
        return is_running
    except Exception as e:
        log_info(f"❌ 检查服务 {service_name} 状态失败:{str(e)}")
        return False

def start_service(service):
    """启动服务并返回启动结果"""
    service_name = service["name"]
    start_cmd = service["start_cmd"]
    log_info(f"🚀 开始启动服务 {service_name},命令:{start_cmd}")
    
    # 执行启动命令
    returncode, output = execute_command(start_cmd)
    if returncode == 0:
        # 启动后再次检查状态(确保启动成功)
        time.sleep(5)  # 等待服务启动完成
        if check_service_status(service):
            log_info(f"✅ 服务 {service_name} 启动成功")
            return True
        else:
            log_info(f"❌ 服务 {service_name} 启动命令执行成功,但状态仍为停止")
            return False
    else:
        log_info(f"❌ 服务 {service_name} 启动失败:{output}")
        return False

def send_alert_email(service, status):
    """发送服务状态报警邮件"""
    if not ALERT_EMAIL["enable"]:
        return
    
    service_name = service["name"]
    subject = f"【紧急】服务 {service_name} {'异常停止' if status == 'stop' else '重启失败'}"
    content = f"""
    <h3>服务监控报警通知</h3>
    <p>报警时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
    <p>服务名称:{service_name}</p>
    <p>服务状态:{status == 'stop' ? '异常停止' : '重启失败'}</p>
    <p>监控类型:{service['type']}</p>
    <p>目标信息:{service['target']}</p>
    <p>启动命令:{service['start_cmd']}</p>
    <p>处理建议:请登录服务器检查服务日志,手动排查故障</p>
    """
    
    try:
        msg = MIMEText(content, "html", "utf-8")
        msg["From"] = Header(ALERT_EMAIL["username"], "utf-8")
        msg["To"] = Header(", ".join(ALERT_EMAIL["recipients"]), "utf-8")
        msg["Subject"] = Header(subject, "utf-8")
        
        # 发送邮件
        server = smtplib.SMTP_SSL(ALERT_EMAIL["smtp_server"], ALERT_EMAIL["smtp_port"])
        server.login(ALERT_EMAIL["username"], ALERT_EMAIL["password"])
        server.sendmail(
            ALERT_EMAIL["username"],
            ALERT_EMAIL["recipients"],
            msg.as_string()
        )
        server.quit()
        log_info(f"📧 报警邮件已发送到:{', '.join(ALERT_EMAIL['recipients'])}")
    except Exception as e:
        log_info(f"❌ 发送报警邮件失败:{str(e)}")

def monitor_single_service(service):
    """监控单个服务:检查状态→停止则重启→重启失败则报警"""
    service_name = service["name"]
    is_running = check_service_status(service)
    
    if not is_running:
        # 服务停止,尝试重启
        log_info(f"⚠️ 服务 {service_name} 已停止,开始尝试重启")
        restart_success = False
        
        for retry in range(RESTART_RETRY):
            log_info(f"🔄 重启尝试 {retry+1}/{RESTART_RETRY}")
            if start_service(service):
                restart_success = True
                break
            time.sleep(10)  # 重启失败后等待10秒再重试
        
        if not restart_success:
            # 多次重启失败,发送报警
            log_info(f"🔥 服务 {service_name} 多次重启失败,触发报警")
            send_alert_email(service, "restart_fail")
        else:
            # 重启成功,发送恢复通知
            send_alert_email(service, "restart_success")

def main():
    log_info("="*80)
    log_info("🚀 服务监控脚本启动(持续运行中...)")
    log_info(f"📌 监控配置:检查间隔={CHECK_INTERVAL}秒,重启重试={RESTART_RETRY}次")
    log_info(f"📌 监控服务数:{len(MONITOR_SERVICES)} 个")
    for service in MONITOR_SERVICES:
        log_info(f"   - {service['name']}({service['type']}:{service['target']})")
    log_info("="*80 + "\n")
    
    try:
        # 循环执行监控
        while True:
            for service in MONITOR_SERVICES:
                monitor_single_service(service)
            
            # 等待下一次检查
            time.sleep(CHECK_INTERVAL)
    except KeyboardInterrupt:
        log_info("🛑 监控脚本被手动停止")
    except Exception as e:
        log_info(f"❌ 监控脚本异常退出:{str(e)}")

if __name__ == "__main__":
    # 检查是否为root用户(部分服务启动需要root权限)
    if os.geteuid() != 0:
        log_info("⚠️ 警告:非root用户运行,可能导致部分服务启动失败,请考虑用sudo运行")
    main()

使用方法

  1. 配置修改:在MONITOR_SERVICES中添加需要监控的服务,指定监控类型(port/process/command)、目标值和启动命令;
  2. 报警配置:启用邮件报警需填写ALERT_EMAIL中的SMTP信息(如QQ邮箱需开启SMTP服务并获取授权码);
  3. 运行脚本:
    # 直接运行(前台)
    python3 service_monitor.py
    
    # 后台运行(推荐,避免终端关闭后脚本停止)
    nohup python3 service_monitor.py >> /var/log/service_monitor.log 2>&1 &
    
  4. 开机自启:将脚本添加到Linux开机自启(如/etc/rc.local),确保服务器重启后自动运行监控脚本。

注意事项

  1. 启动命令需确保能正常执行(建议先手动测试start_cmd);
  2. 监控间隔CHECK_INTERVAL不宜过短(避免频繁检查占用系统资源),也不宜过长(导致服务停止后长时间未发现);
  3. 部分服务启动需要时间(如Java应用),可适当调整start_service函数中的sleep时间;
  4. 建议结合服务器监控工具(如Prometheus)使用,实现更全面的监控告警。

总结

本文分享的4个自动化脚本覆盖了服务器运维、数据安全、日志管理、服务保障等核心场景,具备以下特点:

  1. 实用性强:直接贴合工作需求,可快速修改配置落地使用;
  2. 安全性高:包含异常处理、日志记录、备份机制,避免操作风险;
  3. 扩展性好:支持自定义配置、功能扩展(如添加短信报警、远程监控);
  4. 跨平台兼容:大部分脚本支持Windows/Linux/MacOS,Linux系统可通过定时任务实现全自动运行。

掌握这些自动化脚本后,你可以将重复的机械性工作交给代码,专注于更有价值的核心任务。实际使用中,可根据具体需求灵活调整脚本逻辑,比如添加更多监控指标、优化备份策略、集成到运维平台等。

posted @ 2025-12-03 21:03  小宇无敌  阅读(45)  评论(0)    收藏  举报