Python Paramiko实战:多线程服务器批量巡检与Excel报表
简介这是一套面向运维工程师的服务器巡检工具包基于 Python 与 Paramiko 实现 SSH 远程连接支持多线程并发向多台服务器批量下发巡检命令并能自动汇总结果生成 Excel 报表与 Pyecharts 可视化图表有效解决日常巡检中重复操作多、效率低的问题。压缩包共 10 个文件容量约 40KB内含 py 主脚本、cmd 巡检命令示例、xls 示例报表、log 运行日志、host/info 主机配置模板以及 README、说明文件和附赠 docx 文档结构清晰便于按用途取用与学习。工具还提供简单界面用户可配置巡检任务、运行参数和服务器列表也附有主机清单模板和使用说明初中级运维人员按文档即可快速部署。已有 147 人浏览学习。对于需要批量执行远程命令、自动生成运维报表的团队以及想参考 Paramiko、多线程和 Pyecharts 结合思路的 Python 学习者这套小工具都很具参考价值。1. 项目整体设计与思路拆解1.1 为什么选Paramiko而不是其他方案做运维的人都有这种经历月底要巡检几十台服务器手动一台台SSH登录上去敲命令运气好两个小时搞定运气不好遇到几台密码过期、主机名对不上号的一上午就搭进去了。我之前试过用Shell脚本批量搞但密码分发和结果收集太痛苦也试过直接上Ansible反馈很直观但对于只需要“跑几条命令然后把结果整理成表格”这个轻量场景来说Ansible的学习成本和环境要求有点重了。后来换成Python加Paramiko思路一下就顺了。Paramiko是Python生态里最成熟的SSH协议客户端库底层走的是SSH2协议和你的服务器是否装了Agent完全无关。这意味着只要目标机器开放22端口、有账号密码或者密钥你就能直接连上去执行命令。实际测试下来由Paramiko拉起远程命令的延迟在几十毫秒级别特别适合巡检这种“短命令、多机器、重收集”的场景——每条命令执行时间短大量耗时其实在网络往返和命令排队上。项目中引入Paramiko还有一个关键优势不需要在被管机器上部署任何Agent。对于跨网络区域的服务器巡检这一点很实用省去了Agent分发和权限审批的麻烦。选择多线程并发而不是顺序执行原因也很直观如果一台一台依次跑命令20台机器每台需要5秒一轮巡检下来就是100秒加上逐个连接、等待返回的耗时最终可能要10到15分钟。这个时间看似不长但一旦机器规模增长到50台甚至100台耗时会线性增加到十几分钟巡检窗口根本打不住。而用多线程把连接和命令执行并发起来5到8个线程同时推进20台机器的巡检时间可以压到20到40秒。这个项目里我用的是concurrent.futures.ThreadPoolExecutor来做线程池管理而不是自己手工创建线程——原因是线程池能统一处理任务提交、结果获取、超时控制以及最重要的“并发数限制”。1.2 整体功能架构这个巡检工具的完整流程可以概括成四个环节读取服务器清单支持IP、端口、用户名、密码或密钥路径、分组、巡检命令等字段通过线程池并发执行paramiko.SSHClient远程连接批量执行预定义命令脚本收集每条命令在每台机器上的标准输出、执行状态和耗时统一封装成结构化数据用openpyxl生成Excel巡检报表用pyecharts生成可视化图表并自动归档到按日期命名的报表目录。把架构想清楚之后再动手写代码会发现这个工具本质上只做两件事并发执行远程命令把结果整理成报表。但把这两件事做扎实需要处理很多细节。比如一个线程在执行命令时另一个线程的连接超时了怎么处理一台机器命令执行出错是直接终止整个巡检还是记录错误继续跑后面的机器生成的Excel报表是否需要按分组分Sheet这些看起来不是核心逻辑的细节恰恰是决定工具能否真正落地使用的关键。我在实际开发中把服务器清单做成了Excel用openpyxl读取。因为现网运维工程师对Excel的熟悉程度远高于JSON或YAML用Excel当配置文件的优势是任何人拿到模板都能直接改不需要打开文本编辑器去和括号逗号做斗争。每个Sheet代表一个分组Web组、DB组、缓存组等每一行是一台服务器列分别是主机名、IP、SSH端口、认证方式、巡检命令列表。这样的配置方式在交付给别人用的时候几乎没有学习成本。2. 核心细节解析与实操要点2.1 远程连接与命令执行几个容易踩坑的地方Paramiko的使用模式非常固定创建SSHClient对象设置自动添加策略AutoAddPolicy然后connect建立连接用exec_command执行命令最后读取stdout和stderr。但这几个基础操作里隐藏着不少坑。第一个坑是exec_command的默认行为是非阻塞的。什么意思就是exec_command返回后命令不一定已经执行完了。如果你立刻去读stdout可能只读到空字符串。正确做法是调用stdout.read()或stdout.channel.recv_exit_status()来等待命令执行完成。recv_exit_status()会阻塞直到命令结束并返回命令的退出码这比简单调用read()更可靠因为它能明确告诉你命令到底是成功还是失败。第二个坑是超时。SSH连接的timeout参数影响的是TCP连接建立阶段的超时时间但命令执行阶段如果服务器响应很慢或者有命令卡住了连接层的超时是管不到的。我最初写完代码测试时碰到过好几台机器在某条命令上一直卡住整个线程池的资源全被卡死的连接占用掉。后来在exec_command之后增加了sock.settimeout()的设置给命令执行阶段也加上了超时控制。实践下来单条命令的合理超时上限是30秒超过这个时间基本可以判定命令异常或主机负载过高。第三个坑是命令退出码。很多人判断命令是否成功只看stdout里有没有内容这是在给自己埋雷。有的命令出错时也会往stdout里输出错误信息有的命令执行成功但stdout为空。正确的判断逻辑是先读recv_exit_status()再根据退出码决定取stdout还是stderr。非零退出码表示命令执行失败这时应该记录stderr内容只有退出码为零时才记录stdout并解析正常输出。这个顺序搞反了报表里就会出现大量误导信息。2.2 多线程并发不要一股脑全开很多人一听说要“多线程并发”第一反应是开几十个线程把所有机器一次性并发跑起来。这种思路在小规模场景下问题不大但一旦机器数量超过20台问题就来了服务器侧的MaxStartups默认配置会限制并发SSH连接数超过限制后新连接会被直接丢弃被管服务器如果配置了MaxSessions限制也会拒绝过多的会话。还有更常见的就是你本机的文件描述符数量限制线程一多连接一多程序直接报Too many open files崩掉。所以并发数的控制思路应该是线程数不是越多越好而是够用就好。我实测的情况是对于巡检这种轻量命令场景并发数取5到8比较合适。低于5个50台机器的巡检耗时在60到80秒左右高于10个耗时的下降幅度不明显但报错的概率明显上升。20台机器用5个线程跑基本可以把一轮远程命令执行的总耗时控制在25秒以内。这个并发数的选择可以根据被管机器的硬件配置做微调如果服务器都是固态硬盘加高配CPU可以适当调到10如果是老旧的虚拟机环境建议还是保守一点。线程池用ThreadPoolExecutor还有一个隐藏的好处配合as_completed()可以支持“结果完成一个就收集一个”的流式处理模式。这意味着不用等所有机器都跑完才开始生成报表而是可以边执行边填充结果列表。对于长时间巡检场景这种模式能让你实时看到哪些机器已经跑完了哪些机器还在执行中。我在项目里加了一个简单的进度打印功能每收集完一台机器的结果就打印一条包含主机名和耗时的日志方便在命令行窗口直观监控整个巡检任务的推进状态。2.3 配置文件与动态命令扩展这个巡检工具的另外一个设计思路是把“巡检项目”和“执行逻辑”解耦。也就是说每台机器跑哪些命令不是写死在代码里的而是根据服务器分组动态决定的。比如Web组默认跑uptime、df -h、free -m、netstat -tlnp这几条DB组额外增加mysqladmin status或redis-cli info memory之类的专用命令。在Excel配置里加一列“命令标识”代码里维护一个命令标识到实际命令的映射表这样新增一种巡检项时不用改动代码逻辑只需要在映射表里加一行。这里有一个细节值得补充命令脚本应该做成一个可配置的列表而不是拼接成一个超长字符串用连起来。这样做的好处有三个。第一单条命令失败不影响其他命令的执行方便单独判断每条命令的状态第二命令执行耗时的统计粒度更细报表里能看到是哪条命令慢第三后续如果想对某个特定命令做定向重试按列表索引操作会非常方便。3. 实操过程与核心环节实现3.1 环境准备与依赖安装先把环境搭起来。Python版本推荐3.8及以上我用的是3.10整个项目只依赖三个第三方库paramiko、openpyxl、pyecharts。安装命令如下pip install paramiko openpyxl pyecharts如果你有多个Python环境建议用pip install前先确认一下当前命令行环境下python --version输出的是不是你要用的那个版本。之前有人踩过坑pip装到了系统Python上代码却又在虚拟环境里跑结果一直报ModuleNotFoundError。项目目录结构上我习惯这样组织inspection_tool/ ├── main.py # 主程序入口 ├── config/ │ └── servers.xlsx # 服务器清单Excel ├── core/ │ ├── ssh_executor.py # 远程命令执行模块 │ ├── report_generator.py # 报表生成模块 │ └── models.py # 数据模型定义 ├── output/ # 巡检结果输出目录 │ └── 20240615/ │ ├── report.xlsx │ └── charts.html └── requirements.txt3.2 远程命令执行的核心实现ssh_executor.py里最核心的类大概是这样的逻辑import paramiko from concurrent.futures import ThreadPoolExecutor, as_completed class SSHExecutor: def __init__(self, max_workers5, timeout30): self.max_workers max_workers self.timeout timeout self.results [] def _run_single(self, server, commands): 在单台服务器上执行命令列表返回结构化结果 host, port, user, pwd server result { hostname: host, ip: server[ip], group: server[group], status: success, error: , exec_time: 0, cmd_details: [] } try: client paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect( hostnameserver[ip], portport, usernameuser, passwordpwd, timeout10, banner_timeout10, auth_timeout10 ) for cmd in commands: start time.time() stdin, stdout, stderr client.exec_command(cmd, timeoutself.timeout) exit_code stdout.channel.recv_exit_status() output stdout.read().decode(utf-8, errorsreplace).strip() err_output stderr.read().decode(utf-8, errorsreplace).strip() result[cmd_details].append({ command: cmd, exit_code: exit_code, output: output if exit_code 0 else err_output, cost_sec: round(time.time() - start, 2) }) client.close() result[exec_time] sum(item[cost_sec] for item in result[cmd_details]) except Exception as e: result[status] failed result[error] str(e) return result def run_batch(self, servers, commands_by_group): 并发执行多台服务器的巡检 tasks [] with ThreadPoolExecutor(max_workersself.max_workers) as executor: for server in servers: cmds commands_by_group.get(server[group], []) tasks.append(executor.submit(self._run_single, server, cmds)) for future in as_completed(tasks): res future.result() self.results.append(res) return self.results有几个细节需要注意。banner_timeout和auth_timeout是我后来补上的——默认情况下Paramiko只有timeout这一个超时参数但它覆盖不了某些网络环境下SSH握手阶段卡住的情况。加上这两个超时之后连接异常时能更快抛错线程不会长时间空转。另外注意解码时用了errorsreplace。为什么因为远程机器上有些命令会输出GBK编码或包含非UTF-8字节的内容直接按UTF-8解码会抛UnicodeDecodeError整个线程就崩了。用errorsreplace可以让异常字节被替换成占位符保证程序能继续往下跑。等到报表阶段再去根据实际情况处理编码问题。3.3 报表生成的实现Excel与可视化图表报表模块我想多花点篇幅说说因为这是整个工具最直观的输出部分也是用户感知最强的一部分。Excel部分用的是openpyxl。我的设计是工作簿里第一个Sheet放“汇总总览”展示整体巡检结果包括总机器数、成功数、失败数、平均耗时等指标后续每个分组一个Sheet详细列出每台机器每条命令的执行结果。这样做的好处是汇报时给领导看总览Sheet就够了自己排查问题时再翻详细Sheet。单元格颜色我用了一套简单的规则退出码为0且输出正常的记录字体是黑色、填充是白色非零退出码的记录填充浅红色连接失败的主机整行标成橙色。另外列宽设置很关键如果执行结果超过160个字符我会做截断处理在Excel里加一个批注保存完整内容否则表格会被长文本撑得没法看。from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment def generate_excel_report(all_results, output_path): wb Workbook() summary_ws wb.active summary_ws.title 巡检总览 # 统计指标 total len(all_results) success sum(1 for r in all_results if r[status] success) failed total - success # 写入总览数据并保存 wb.save(output_path)Pyecharts部分。这个库生成的是HTML可视化网页可以直接在浏览器打开。我做两个核心图表一个是“各分组巡检耗时对比”的柱状图一个是“命令执行异常数量Top10”的横向条形图。前者能帮你看清哪个分组的服务器整体性能差后者能暴露哪些命令高频失败、需要重点排查。from pyecharts.charts import Bar from pyecharts import options as opts bar Bar() bar.add_xaxis(group_names) bar.add_yaxis(平均耗时(秒), avg_times) bar.set_global_opts(title_optsopts.TitleOpts(title分组巡检耗时对比)) bar.render(output/charts_group_cost.html)有个使用上的心得Pyecharts生成的HTML默认是独立的直接把图表和Excel报表放在同一个日期目录里即可不需要塞进Excel里。一份详尽的Excel、一份可视化的HTML网页两者是互补的——Excel适合数据整理和存档HTML适合快速浏览趋势和定位异常。4. 常见问题与排查技巧实录4.1 问题速查表这个问题速查表里的内容基本都是从我自己实际跑这个工具的过程中踩坑踩出来的逐条对应最常见的故障现象和处理方式。现象可能原因排查与解决大批量机器报连接超时服务器侧MaxStartups限制并发连接数减少max_workers从8降到5检查目标服务器/etc/ssh/sshd_config个别机器一直卡住不返回命令执行阶段无超时控制给exec_command加timeout参数检查是否有命令等待交互输入输出内容乱码远程输出编码和本地解码编码不一致解码时指定正确的编码集统一用errorsreplace兜底报表里命令退出码非零但内容看起来正常命令将警告信息输出到了stderr不要只根据stdout判读必须以退出码为准必要时合并stdout和stderr后统一展示调度中心或本地报Too many open files线程数过多文件描述符耗尽增加本机ulimit上限或降低并发数到合理范围Excel打开后显示“文件已损坏”openpyxl保存对象未正确关闭确保wb.close()被调用或改用with上下文管理器4.2 排查实战一次巡检告警抖动说一个我印象很深的实战案例。有一次跑巡检工具报表里显示有3台机器报警提示命令执行超时。单看现象很容易以为是网络问题或服务器负载过高但仔细翻详细Sheet发现超时的命令全都是同一个——df -h。这就很奇怪了df -h理论上执行只要几十毫秒为什么会在多台机器上同时超时后来排查发现这3台机器挂载了一个NFS共享目录而NFS服务端那台机器当时的网络状态异常导致df -h在收集文件系统信息时阻塞在NFS挂载点上。在命令行手动SSH登录上去执行df -h同样会卡住几十秒。这个案例说明了两个点第一巡检命令执行超时未必是SSH问题也可能是命令本身在服务器上依赖了外部资源第二报表里把“单条命令耗时”单独列出来非常有用它能帮你快速定位是哪条命令异常而不是只看到整台机器巡检失败的结果。4.3 进一步优化敏感信息与故障重试工具跑到后期有两个进一步增强的点可以分享。第一是敏感信息的处理。Excel里保存明文密码不是好习惯虽然内网环境下暂时可用但一旦配置表泄露风险很大。一个简单的改进是改用密钥认证把私钥路径配置到Excel里代码里用paramiko.RSAKey.from_private_key_file()加载密钥连接服务器上只需把公钥放到authorized_keys里即可。这样既支持批量部署又不用在文件里保存明文口令。第二是故障自动重试。巡检目标机器偶尔会有瞬时抖动第一次连接超时不代表机器不可用。我给run_batch增加了一个简单的重试逻辑连接或执行失败一次后等待1到2秒再重试一次重试仍然失败才标记为故障并在报表中标注“重试后仍失败”的信息。这个小小的改进把巡检的误报率降了一个量级尤其是对网络状况不太稳定的混合云环境和跨机房场景效果非常明显。最后再分享一点我在实际使用中的体会这种工具不要追求大而全能每天稳定跑、报表能让不同角色的人看懂比功能堆砌更重要。后续可以考虑扩展的方向有把巡检结果推送到企业微信或钉钉机器人的Webhook、配合定时任务Cron做到每天自动巡检、把历史报表归档后做一个简易的指标趋势分析。每一步的扩展都是在现有代码结构上增加模块不需要推翻重来。本文还有配套的精品资源点击获取