企业级Python自动化运维实战(三):多服务器并发巡检与架构升级

专栏简介: 本专栏将从单机巡检起步,逐步演进到多机批量巡检、异常告警推送、可视化Dashboard,最终构建一套完整的企业级自动化运维平台。每一篇都是生产环境实战沉淀,拒绝拒绝Hello World式教学。

📌 本篇导读

如果沿用上一篇企业级Python自动化运维实战(二):单机升级到多机巡检系统,我们需要在每台服务器上都执行一遍,进行三次ssh链接,效率极低。本篇我们将迎来V2.1版本的重大升级,将单机脚本演进为多机并发巡检系统。我们将引入SSH密钥认证、多线程并发、结构化数据解析以及完善的日志系统,打造真正符合企业级标准的自动化运维工具。

痛点/问题V2 版本现状V2.1 企业级要求
认证方式密码/账号明文有风险SSH Key 免密认证
执行效率单台服务器逐个检查多线程并发执行
容错能力SSH失败程序直接退出异常容错,单机失败不影响整体
数据格式采集结果是字符串结构化数据(JSON/Dict)
日志系统无日志记录标准化日志系统
报告质量简单文本标准化HTML报告

一、 安全基石:SSH Key 免密认证

在生产环境中,明文管理服务器密码是极大的安全隐患。企业级运维的标准做法是使用 SSH Key(公钥/私钥) 进行认证。

1.1 生成 SSH 秘钥

在我们的巡检服务器(控制机)上执行以下命令,一路回车即可生成密钥对:

作者:饭要加加加
如需代码压缩包请关注作者,私信获取
ssh-keygen -t rsa -b 4096

执行后,~/.ssh/ 目录下会生成 id_rsa(私钥)和 id_rsa.pub(公钥)。

1.2 分发公钥

将公钥复制到目标服务器上,实现免密登录:

ssh-copy-id root@10.207.132.221
1.3 配置管理

修改 config/server.yaml,将认证方式从密码改为密钥文件路径:

servers:
  - name: master
    host: 10.207.132.221
    username: root
    key_file: C:\Users\Administrator\.ssh\id_rsa
1.4 升级 SSH 模块

修改 ssh/client.py,使用 paramiko 加载私钥进行连接。

import paramiko

def ssh_execute(server, command):
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    
    # 加载私钥
    private_key = paramiko.RSAKey.from_private_key_file(server["key_file"])
    
    client.connect(
        hostname=server["host"],
        username=server["username"],
        pkey=private_key,
        timeout=5
    )
    
    stdin, stdout, stderr = client.exec_command(command)
    result = stdout.read().decode()
    client.close()
    return result

二、 健壮性升级:增加 SSH 异常处理

在V1版本中,如果某台服务器IP错误或宕机,程序会直接崩溃退出。在企业环境中,单机故障绝不能影响整体巡检任务的执行

我们需要对 ssh_execute 函数进行“防御性编程”改造,捕获所有异常并返回结构化结果:

import paramiko

def ssh_execute(server, command):
    try:
        client = paramiko.SSHClient()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        key = paramiko.RSAKey.from_private_key_file(server["key_file"])
        
        client.connect(
            hostname=server["host"],
            username=server["username"],
            pkey=key,
            timeout=5
        )
        
        stdin, stdout, stderr = client.exec_command(command)
        result = stdout.read().decode()
        client.close()
        
        # 成功返回 success 状态
        return {
            "status": "success",
            "data": result
        }
    except Exception as e:
        # 失败返回 failed 状态及错误信息,程序不崩溃
        return {
            "status": "failed",
            "error": str(e)
        }

💡 核心知识点:Paramiko 常用功能

功能核心类核心函数/方法说明
SSH登录SSHClientconnect()建立连接,传入IP、端口、用户名、密钥等。
执行命令SSHClientexec_command()执行命令,返回 stdinstdoutstderr 三个通道。
文件上传SFTPClientput()上传文件 (本地路径, 远程路径)
文件下载SFTPClientget()下载文件 (远程路径, 本地路径)
密钥认证RSAKeyfrom_private_key_file()加载私钥文件,配合 connect(pkey=) 使用。

三、 性能飞跃:重构数据采集与多线程并发

3.1 采集策略重构

之前的做法是“采集一个指标建立一次SSH连接”,这在多机巡检中是致命的性能瓶颈。
优化方案:创建一个新的入口 collectors/server.py,一次SSH连接,批量执行所有命令,然后在本地进行解析。

# collectors/server.py
from ssh.client import ssh_execute
from parser.cpu import parse_cpu
from parser.memory import parse_memory
from parser.disk import parse_disk

def inspect_server(server):
    # 一次性执行多个命令
    command = """
    echo CPU
    top -bn1 | grep Cpu
    echo MEMORY
    free -m
    echo DISK
    df -h /
    """
    
    result = ssh_execute(server, command)
    
    if result["status"] != "success":
        return {
            "name": server["name"],
            "status": "failed",
            "error": result["data"]
        }
    
    raw_data = result["data"]
    
    # 本地解析数据
    return {
        "name": server["name"],
        "cpu": parse_cpu(raw_data),
        "memory": parse_memory(raw_data),
        "disk": parse_disk(raw_data),
        "status": "success"
    }
3.2 数据结构化:Parser 模块

SSH 返回的是字符串,程序无法直接判断大小。我们需要使用正则表达式(re 模块)将其转换为数字。

创建 parser/ 目录:

parser/cpu.py

import re
def parse_cpu(data):
    result = re.search(r'(\d+\.\d+)\s*id', data)
    if result:
        idle = float(result.group(1))
        return round(100 - idle, 2)
    return 0

parser/disk.py

import re
def parse_disk(data):
    result = re.search(r'(\d+)%\s+/', data)
    if result:
        return float(result.group(1))
    return 0

parser/memory.py

import re
def parse_memory(data):
    result = re.search(r'Mem:\s+(\d+)\s+(\d+)', data)
    if result:
        total = float(result.group(1))
        used = float(result.group(2))
        if total > 0:
            return round(used / total * 100, 2)
    return 0

💡 核心知识点:Python 正则 re 模块

核心功能对应函数/方法说明
全局搜索(首个)re.search()扫描整个字符串,返回第一个匹配结果(最常用)。
全局搜索(全部)re.findall()返回列表,包含所有匹配结果。
从头匹配re.match()仅从字符串开头匹配,不常用。
字符串替换re.sub()替换匹配的字符串。
编译对象re.compile()预编译正则,提高重复使用时的性能。
3.3 多线程并发执行

当面对 100 台服务器时,串行执行可能需要 500 秒(假设每台5秒),这是不可接受的。我们使用 concurrent.futures 实现多线程并发。

创建 core/task.py

from concurrent.futures import ThreadPoolExecutor, as_completed

def run_parallel(tasks, max_workers=10):
    """
    tasks: [(函数, 参数), ...]
    max_workers: 最大线程数
    """
    results = []
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        future_list = []
        for func, args in tasks:
            future = executor.submit(func, args)
            future_list.append(future)
        
        for future in as_completed(future_list):
            try:
                result = future.result()
                results.append(result)
            except Exception as e:
                results.append({"status": "failed", "error": str(e)})
    return results

💡 核心知识点:ThreadPoolExecutor 

核心功能对应函数/方法说明
创建线程池ThreadPoolExecutor(max_workers=N)创建包含 N 个线程的池子。
提交任务submit(fn, *args)提交任务,立即返回 Future 对象。
关闭线程池shutdown(wait=True)等待任务完成并关闭(通常配合 with 使用)。

关于 Future 对象:

  • future.result(): 阻塞等待任务完成并获取返回值。
  • future.done(): 判断任务是否完成。
  • future.exception(): 获取任务执行时的异常。

四、 可观测性:增加日志系统

企业级项目必须有日志。我们将使用 Python 内置的 logging 模块记录巡检过程。

在 main.py 中配置日志:

import logging

logging.basicConfig(
    filename="logs/app.log",
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s"
)

# 使用示例
logging.info("开始巡检 web01")
logging.error("web01 连接失败")

日志输出示例:
2026-07-29 15:00 INFO 开始巡检web01
2026-07-29 15:01 ERROR web02 timeout


五、 最终整合:Main 入口与报告调整

最后,我们将所有模块串联起来。同时,调整 analyzer/check.py 以适应新的数据结构(包含 namestatus 等字段)。

analyzer/check.py

CPU_THRESHOLD = 80
MEMORY_THRESHOLD = 80
DISK_THRESHOLD = 90

def check(data):
    alarms = []
    # 先判断采集是否成功
    if data.get("status") != "success":
        alarms.append("服务器数据采集失败")
        return alarms
    
    # 指标检查
    if data.get("cpu", 0) > CPU_THRESHOLD:
        alarms.append("CPU使用率过高")
    if data.get("memory", 0) > MEMORY_THRESHOLD:
        alarms.append("内存使用率过高")
    if data.get("disk", 0) > DISK_THRESHOLD:
        alarms.append("磁盘空间不足")
        
    return alarms

main.py

import os
import sys
import logging
from config.ssh_config import load_servers
from report.report import create_report
from core.task import run_parallel
from analyzer.check import check
from collectors.server import inspect_server

def main():
    logging.basicConfig(
        filename="logs/app.log",
        level=logging.INFO,
        format="%(asctime)s %(levelname)s %(message)s"
    )
    
    servers = load_servers()
    tasks = []
    
    # 构建任务列表
    for server in servers:
        tasks.append((inspect_server, server))
    
    # 并发执行
    results = run_parallel(tasks)
    
    # 处理结果
    for data in results:
        alarms = check(data)
        data["alarms"] = alarms
    
    # 生成报告
    create_report(results)

if __name__ == "__main__":
    main()

六.本篇总结与下篇预告

✅ 本篇收获

至此,我们的巡检系统完成了从“单机脚本”到“企业级工具”的华丽转身:

  1. 安全性:引入 SSH Key,告别明文密码。
  2. 健壮性:完善的异常捕获,单点故障不影响全局。
  3. 高性能:多线程并发 + 单次连接批量执行,效率提升数倍。
  4. 标准化:结构化数据解析 + 标准日志系统。

🚀 下篇预告:数据库与Web监控平台改造
系统已经跑起来了,但报告还是太简陋?告警只能看日志?
在下一篇文章中,我们将进行数据持久化到数据库,制作精美的 HTML 可视化报告,并接入 企业微信/钉钉机器人,实现真正的实时告警推送!

🌟 创作不易,如果这篇文章对你有帮助,欢迎点赞 👍、收藏 ⭐、关注 🔔 三连支持!你的支持是我持续更新的最大动力。有任何问题或建议,欢迎在评论区留言交流,我会一一回复!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

饭要加加加

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值