资讯动态

Python+Netmiko构建网络设备自动巡检系统

发布时间:2026/10/20 16:59:58 来源:尧图企业网站定制
简介资源面向网络运维与Python开发者提供一套基于telnetlib的自动巡检网络设备脚本方案解决手工登录交换机、路由器执行巡检命令效率低、易出错的问题。脚本涵盖连接、身份验证、命令下发、输出解析、结果存储与邮件发送等完整流程并支持通过ini文件灵活配置设备地址、登录口令、命令清单等参数便于扩展至配置备份、故障检测等场景。压缩包共7个文件以1个Python主脚本、5个ini配置文件和1个Markdown说明文档为主体积仅6KB结构简洁。目前已有6140人学习下载。读者可参考脚本学习telnet自动化交互、异常处理和定时任务组织方式也可直接修改配置用于自身网络环境快速构建轻量级巡检工具。1. 自动巡检网络设备用 Python 脚本把凌晨两点的闹钟换成一段代码做网络运维的人应该都有过这种经历深夜收到告警爬起来登录设备一条条敲命令CPU 高不高、内存还剩多少、接口有没有 flapping全靠肉眼盯。我最初接触 Python 网络编程就是为了摆脱这种重复劳动。所谓“自动巡检”本质就是写一段脚本替你登录每一台交换机、路由器、防火墙把平时手工敲的 show 命令批量执行一遍再把输出里关键的指标抠出来生成一张带告警标记的巡检表。这套东西很适合网工、运维工程师以及刚接触网络自动化的开发者不需要懂很深的前端知识有 Python 基础就能上手跑通之后每台设备平均只需要两三秒。这篇笔记就按我实际落地这套巡检脚本的顺序来讲从选型到编码再到那些不跑一次绝对发现不了的坑。2. 技术选型与运行环境为什么我选 Netmiko 而不是裸写 Paramiko2.1 三种采集通道的适用边界网络设备巡检首先要解决“怎么连上去”的问题。常见方案有三条路Paramiko、Netmiko、SNMP。Paramiko 是 SSH 协议的 Python 实现灵活性最高但裸写 Paramiko 意味着你要自己处理交互式 shell 的提示符匹配、命令回显、超时控制。比如登录设备后执行display cpu-usage你要等设备输出完再发下一条命令这个“等多久、等到什么字符串出现”的逻辑非常容易写崩。我最早就是裸写 Paramiko结果被不同厂商设备的回显格式折腾得够呛。Netmiko 是在 Paramiko 之上封装的网络设备自动化库它把 SSH 连接、认证、命令发送、输出读取这些操作统一成send_command()这类高层接口。更关键的是Netmiko 内置了思科、华为、H3C、锐捷等几十种设备的交互规则什么设备用什么提示符正则、什么设备命令需要分页处理它内部都处理好了。对我们做巡检的人来说这意味着不用关心底层 SSH 细节写起来更像是在操作一个“设备对象”。SNMP 适合纯指标采集比如 CPU、内存、接口流量轮询效率比 SSH 高很多但有两个短板一是很多网工对 MIB 节点不熟写 OID 像在猜谜二是 SNMP 只能取指标干不了配置备份、接口状态排查这类需要命令交互的活。所以我最终选择 Netmiko 做主力SNMP 留作后续扩展的补充通道。2.2 环境搭建与虚拟环境隔离巡检脚本的运行环境不需要多复杂一台能连管理网的跳板机或者 Linux 服务器就够。但环境隔离这一步我建议不要省因为 Netmiko 依赖的 Paramiko、scp、textfsm 这些库版本之间容易互相影响直接 pip 装到系统 Python 里早晚会翻车。python3 -m venv venv source venv/bin/activate pip install netmiko openpyxl pyyaml创建一个独立的虚拟环境再把三个核心依赖装进去。netmiko负责设备连接与命令采集openpyxl负责生成最后的 Excel 巡检报告pyyaml负责读取设备清单配置。这里有个参数说明python3 -m venv venv是在当前目录创建一个叫venv的虚拟环境目录激活之后pip装的包都会进这个目录不会污染系统环境。如果你的服务器上没有venv模块需要先安装python3-venv系统包。我一般还会顺手pip install paramiko --upgrade因为 Netmiko 在某些版本下对 Paramiko 的版本有下限要求提前升一下能避免后续莫名报错。2.3 资源包目录结构与配置文件设计项目不需要复杂的框架但目录结构最好一开始就定好否则脚本写大了以后改起来很痛苦。我实际使用的布局是这样的network-inspect/ ├── config/ │ └── devices.yaml ├── core/ │ ├── connector.py │ ├── parser.py │ └── reporter.py ├── reports/ └── main.pyconfig/devices.yaml放设备清单和登录凭据core/connector.py负责连接与命令采集core/parser.py负责从命令输出里提取指标core/reporter.py负责生成报告main.py是入口。设备清单用 YAML 而不是硬编码在 Python 里这样加设备、改密码都不用动代码。下面是我常用的配置格式devices: - name: core-sw-01 host: 192.168.10.1 device_type: huawei username: admin password: ****** port: 22 commands: - display cpu-usage - display memory-usage - display interface brief - name: border-fw-01 host: 192.168.10.2 device_type: huawei username: admin password: ****** port: 22 commands: - display cpu-usage - display memory-usage注意device_type这个字段Netmiko 就是靠它来匹配不同厂商的交互规则的。华为设备填huawei思科填cisco_iosH3C 填hp_comware。填错的话 Netmiko 在握手阶段就会抛出AuthenticationException或者直接卡住。port: 22是 SSH 端口如果设备改了端口号就改这里。每条巡检命令放在commands列表里脚本会逐条执行并收集输出。这个设计的好处是新加巡检项不需要改代码在 YAML 里加一行命令就行了。3. 核心采集引擎用 Netmiko 批量登录交换机并执行巡检命令3.1 设备清单与连接工厂从 YAML 到连接对象整个脚本的起点是把 YAML 里的设备清单加载进来变成一个个连接对象。这里我不建议每台设备单独写连接代码而是做一个简单的“连接工厂”接受一个设备字典返回一个 Netmiko 连接实例。from netmiko import ConnectHandler import yaml def load_devices(config_pathconfig/devices.yaml): with open(config_path, r, encodingutf-8) as f: data yaml.safe_load(f) return data[devices] def create_connector(device): conn_params { device_type: device[device_type], host: device[host], username: device[username], password: device[password], port: device.get(port, 22), timeout: 20, conn_timeout: 15, } conn ConnectHandler(**conn_params) conn.enable() # 进入特权模式部分设备需要 return conn逻辑说明load_devices读取 YAML 文件并返回设备列表create_connector从设备字典里取出连接参数构造ConnectHandler实例。conn.enable()是对思科等设备的特权模式华为设备会自动忽略这一步。参数说明timeout是命令执行的超时时间超过 20 秒没返回就报错conn_timeout是 TCP 连接的超时时间设备不可达时不用干等很久。device.get(port, 22)的意思是如果 YAML 里没写port就用默认值 22这样配置可以更精简。3.2 命令采集与分页处理delimiter 和 fast_cli 的重点连上设备之后核心操作就是逐个执行命令并收集输出。看起来就是一行send_command但实际拿到的输出往往要处理两个问题命令分页和输出清洗。import re def collect_outputs(conn, commands): results {} for cmd in commands: raw conn.send_command( cmd, expect_stringr, fast_cliFalse, delay_factor1.0, ) # 去掉终端控制字符和多余空行 cleaned re.sub(r\x1b\[[0-9;]*[A-Za-z], , raw) cleaned re.sub(r\n{3,}, \n\n, cleaned).strip() results[cmd] cleaned return results逻辑说明send_command发送命令后Netmiko 会一直读到匹配expect_string指定的正则才停止。fast_cliFalse是关闭 Netmiko 的快速 CLI 模式这个模式在某些设备上会缩短命令间隔容易导致输出没完全接收就被截断。参数说明expect_stringr表示读到设备提示符就认为命令输出结束了。不同设备提示符不同华为设备常见或]思科是#。delay_factor是时间间隔倍率默认 1.0遇到设备响应慢、输出经常截断的情况可以调到 2.0 或 3.0但代价是每台设备的巡检时间会翻倍。分页问题要特别注意。很多设备的display命令默认分页显示比如华为的display cpu-usage如果输出超过一屏末尾会有一个---- More ----此时 Netmiko 不会自动翻页脚本会卡死在那里。解决办法有两个一是进设备的screen-length 0 temporary临时关掉分页二是在命令末尾加| no-more华为设备支持的管道符过滤。如果巡检的接口数量特别多我一般会在连接后先发一条screen-length 0 temporary然后再执行采集命令。3.3 并发巡检ThreadPoolExecutor 与设备负载控制如果只管几台设备逐台顺序采集完全没问题。但一旦设备数量超过 30 台顺序跑完可能得四五分钟这时就需要并发。Python 里做并发最简单的方式是ThreadPoolExecutor因为 Netmiko 的瓶颈在网络 I/O等待设备回显时线程是空闲的用线程池而不是多进程能最大化利用等待时间。from concurrent.futures import ThreadPoolExecutor, as_completed def run_inspection(devices): all_results {} # 限制并发数避免把设备或跳板机打爆 with ThreadPoolExecutor(max_workers10) as executor: future_map {} for dev in devices: future executor.submit(inspect_one, dev) future_map[future] dev[name] for future in as_completed(future_map): dev_name future_map[future] try: result future.result() all_results[dev_name] result except Exception as e: all_results[dev_name] {error: str(e)} return all_results逻辑说明executor.submit(inspect_one, dev)把每台设备的巡检任务丢进线程池返回一个future对象as_completed迭代器会按任务完成的先后顺序返回结果哪台先跑完哪台先处理不用等全部完成。参数说明max_workers10是我在实际环境里测试出来的折中值。设备多的时候可以调到 20但我不建议再高了。并发数太高会导致跳板机的 SSH 连接数暴涨设备侧也可能因为瞬间接入太多会话而 CPU 飙升。曾经有次我把并发调到 30结果一台核心交换机 CPU 直接到了 80%从那以后这个参数我再也没超过 20。inspect_one是单台设备的完整巡检流程内部依次调用create_connector、collect_outputs、disconnect。注意future.result()会抛出任务内的异常所以一定要包在 try/except 里避免一台设备连接失败导致整个巡检中断。4. 输出侧指标提取、阈值告警与报告生成4.1 用正则从命令输出里抠 CPU、内存与接口状态命令采集回来只是第一步真正体现“巡检”价值的是把输出里关键指标结构化。华为设备的display cpu-usage输出里有一个CPU Usage: 5%这样的行用正则直接抓取import re def parse_huawei_cpu(output): match re.search(rCPU Usage:\s*(\d)%, output) if match: return int(match.group(1)) return None内存解析类似display memory-usage的输出里有Memory Usage: 32%之类的行同样的正则思路抓出来def parse_memory(output): match re.search(rMemory Usage:\s*(\d)%, output) if match: return int(match.group(1)) return None逻辑说明这两段解析函数的逻辑都很直白就是正则匹配加类型转换。但实际中有一个坑不同设备的输出格式差别很大。比如某些设备 CPU 输出是CPU Occupied Rate: 5%如果只写了CPU Usage这个正则解析出来就是None。所以我一般会写一个parse_cpu_generic函数同时匹配多种模式def parse_cpu_generic(output): patterns [rCPU Usage:\s*(\d)%, rCPU Occupied Rate:\s*(\d)%] for p in patterns: match re.search(p, output) if match: return int(match.group(1)) return None接口状态解析是巡检里最有用的部分display interface brief输出是一个表格行格式类似于GigabitEthernet0/0/1 up up 100M。我提取状态为 down 的接口并归集def parse_down_interfaces(output): down_ifs [] for line in output.splitlines(): parts line.split() # 过滤表头和数据行 if len(parts) 2 and parts[0][:1].isalpha() and parts[1] down: down_ifs.append((parts[0], parts[1])) return down_ifs这段代码的要点在于parts[1] down这个条件。接口表里有两个状态位物理状态和管理状态。物理 down 意味着网线断了或对端设备没起来这是真正需要关注的问题。如果只看第一个状态很多管理上手动 shutdown 的接口会误报。4.2 阈值判断与告警分级提取出指标之后下一步是判断“正不正常”。我先定义了一个阈值表按严重程度分级THRESHOLDS { cpu: {warning: 70, critical: 90}, memory: {warning: 80, critical: 95}, } def evaluate_metric(metric_name, value): if value is None: return unknown cfg THRESHOLDS.get(metric_name) if value cfg[critical]: return critical if value cfg[warning]: return warning return normal参数说明CPU 的 warning 阈值是 70%critical 是 90%内存的 warning 是 80%critical 是 95%。这个阈值不是绝对的内存占用高不一定代表设备有故障有些设备的缓存机制会故意占用大量内存。我在实际使用中会把内存的 warning 调高到 85%避免告警轰炸。有了分级之后巡检结果会汇总成一个包含多维度信息的结构体供报告生成模块使用def build_device_report(dev_name, cpu, mem, down_ifs): return { name: dev_name, cpu: cpu, memory: mem, cpu_level: evaluate_metric(cpu, cpu), memory_level: evaluate_metric(memory, mem), down_interfaces: down_ifs, timestamp: datetime.now().strftime(%Y-%m-%d %H:%M:%S), }4.3 生成 Excel 巡检报告格式、列宽与条件着色报告是整个巡检脚本的“脸面”一份好的巡检报告要让运维人员一眼看到哪台设备有问题。我用 openpyxl 生成 Excel把告警级别映射成单元格背景色from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment def generate_excel_report(all_reports, output_pathreports/inspection.xlsx): wb Workbook() ws wb.active ws.title 巡检报告 headers [设备名, 巡检时间, CPU(%), CPU状态, 内存(%), 内存状态, Down接口数] ws.append(headers) level_colors { normal: C6EFCE, # 绿色 warning: FFEB9C, # 黄色 critical: FFC7CE, # 红色 unknown: D9D9D9, # 灰色 } for rpt in all_reports: row [ rpt[name], rpt[timestamp], rpt[cpu], rpt[cpu_level], rpt[memory], rpt[memory_level], len(rpt[down_interfaces]), ] ws.append(row) # 对状态列着色 row_idx ws.max_row for col_idx, level in [(4, rpt[cpu_level]), (6, rpt[memory_level])]: cell ws.cell(rowrow_idx, columncol_idx) cell.fill PatternFill(start_colorlevel_colors.get(level, FFFFFF), end_colorlevel_colors.get(level, FFFFFF), fill_typesolid) # 调整列宽 ws.column_dimensions[A].width 16 ws.column_dimensions[B].width 20 ws.column_dimensions[C].width 10 ws.column_dimensions[D].width 12 ws.column_dimensions[E].width 10 ws.column_dimensions[F].width 12 ws.column_dimensions[G].width 14 wb.save(output_path) print(f报告已生成: {output_path})逻辑说明ws.append(row)是逐行写入数据行号取ws.max_row可以拿到刚写入的行索引便于给状态单元格上色。PatternFill的start_color和end_color都设成同一种颜色形成实心填充。参数说明level_colors里用的是 Excel 条件格式的经典配色正常绿色、警告黄色、严重红色、未知灰色。这种配色方案在运维团队里几乎是默认约定拿过去就能用。column_dimensions的设置是为了防止设备名、时间戳这类长字符串被截断。5. 避坑笔记Netmiko 巡检脚本的七个典型故障与排查方法5.1 连接超时报错AuthenticationException但密码明明是对的现象脚本执行到某一台设备时抛出AuthenticationException但手动用 SSH 客户端登录同一个账号密码完全正常。原因这个问题我排查了很久最后发现是device_type填错了。设备实际是华为的huawei但我在 YAML 里填的是huawei_olt。Netmiko 会根据device_type加载不同的登录交互规则如果交互规则和设备实际行为不匹配握手阶段就可能误判认证失败。解决打开配置文件逐个核对device_type是否与实际设备型号匹配。不确定的话可以在 Python 里执行netmiko.ssh_dispatcher.platforms查看所有支持的平台列表把字段值抄过去用。5.2 命令输出截断CPU 数据只回显了一半现象执行display cpu-usage之后脚本拿到的输出只有开头几行后面的历史统计信息全部丢失。原因命令执行时间超过了timeout参数的设定值。设备响应慢的时候Netmiko 在超时后会把已经收到的内容返回导致输出不完整。另一个常见诱因是把fast_cli打开了这个优化模式在某些设备上会缩短命令与命令之间的等待时间导致前一个命令的输出还没完全到达就发下一条命令。解决把fast_cliFalse固定写上同时把timeout从默认的 20 调大到 40。如果设备确实响应慢再把delay_factor调到 2。注意这三个参数需要配合使用只调一个往往效果不明显。5.3 两台设备并发采集时输出互相混乱现象用 ThreadPoolExecutor 并发巡检两台同型号设备最终结果里 A 设备的接口数据出现在 B 设备名下。原因这是我在代码里犯的一个低级错误——把连接对象定义成了模块级全局变量。两个线程共用同一个连接对象后一个线程发命令时把前一个线程的输出覆盖了。Netmiko 的ConnectHandler不是线程安全的每个线程必须持有独立的连接实例。解决确保连接对象的创建发生在inspect_one函数内部让每个线程各自创建、各自disconnect()。同时要注意不能把连接实例作为参数传递给多个线程共享。5.4 华为设备命令分页卡死脚本挂起超过 5 分钟现象巡检接口较多的核心交换机时脚本卡在display interface brief这一步一直没有返回。原因设备输出超过一屏出现了---- More ----分页提示。Netmiko 没有自动翻页expect_string匹配不到提示符就一直死等。解决连接建立后先发screen-length 0 temporary命令关闭分页然后再执行采集。这个命令只对当前会话有效不会修改设备配置。5.5 报告里中文设备名乱码现象生成的 Excel 报告里设备名的中文全部变成了类似系统的乱码。原因脚本读取 YAML 文件时没有指定encodingutf-8在 Linux 下默认编码可能不是 UTF-8导致设备名字符串在内存里就是错的。解决所有文件读写操作都显式加上encodingutf-8包括open()和yaml.safe_load()。openpyxl 写 Excel 时原生支持 UTF-8不需要额外处理。5.6 内存占用看起来总是 100%现象多台设备的memory-usage解析出来都是接近 100%但手动登录设备看实际占用率只有 40%。原因华为部分设备的内存输出里Memory Usage后面的百分比可能是一个历史峰值或者包含缓存在内的总占用。某些版本的 VR 系统有两个字段一个是Memory Usage一个是Memory Usage Without Cache正则抓错了字段。解决先手动执行一次命令把完整输出打印出来看字段名。有些型号用Memory Using Percentage is开头把这些变体加进正则模式列表里。5.7 定时任务里脚本跑不起来但手动执行正常现象把脚本写进 crontab 后到时间却没有生成报告手动执行python main.py却一切正常。原因crontab 执行时的环境变量和手动执行不同最典型的是 PATH 里没有包含 Python 虚拟环境的目录导致 crontab 用的是系统 Python而系统 Python 里没装 netmiko。解决在 crontab 里写命令时用绝对路径同时显式调用虚拟环境的 Python 解释器。比如/opt/venv/bin/python /opt/network-inspect/main.py。还可以把脚本输出重定向到日志文件方便排查是环境问题还是代码问题。6. 定时任务与告警推送让巡检脚本真正无人值守6.1 部署到 crontab 与日志管理脚本能在本地跑通只是第一步真正让巡检“自动”起来需要把它挂到定时任务里。我常用的 crontab 配置是每天凌晨 2 点执行一次0 2 * * * cd /opt/network-inspect ./venv/bin/python main.py logs/inspect.log 210 2 * * *表示每天凌晨 2 点整执行这个时间段设备负载相对低。cd /opt/network-inspect是为了让脚本的工作目录正确否则相对路径的配置文件会找不到。 logs/inspect.log 21是标准输出和错误输出都追加到日志文件方便第二天早上查看执行情况。日志管理我一般配合logrotate做按天切割避免单个日志文件无限增长。这一步虽然不是必须的但等你跑上几个月日志文件会大到难以打开。6.2 告警推送把异常指标发到企业微信群机器人巡检报告生成之后只有异常需要主动推送。我用的方式是调用企业微信群机器人的 webhook 接口把分级为 warning 或 critical 的设备信息发到群里。import requests def send_alert(rpt): if rpt[cpu_level] not in (warning, critical): return level_text {warning: ⚠️ 警告, critical: 严重}.get(rpt[cpu_level]) msg f{level_text} {rpt[name]} CPU 达到 {rpt[cpu]}% webhook_url https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyYOUR_KEY data {msgtype: text, text: {content: msg}} requests.post(webhook_url, jsondata, timeout10)逻辑说明send_alert根据巡检报告的告警级别决定是否推送正常状态的设备不打搅人。webhook 的 key 放在代码里虽然不优雅但对于内部使用的巡检脚本来说已经足够了。这里要特别强调一个教训我刚挂到 crontab 的第一周第二天早上数据是全的但第三天有一台设备没有输出。排查日志发现是设备侧临时拒绝了 SSH 连接而我的脚本没有做重试直接把这台设备标记为 error 跳过了。从那以后我每次写巡检脚本都会强制在连接层加一次重试机制同时也要求报告里明确显示“巡检失败”与“指标正常”的区别不能混在一起。希望这篇笔记中的设计思路和踩坑记录能帮你少走几步冤枉路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑