在中文技术社区里,网络运维自动化一直是个高频话题。日常做设备巡检、配置备份、批量下发NTP/SNMP/AAA,最容易消耗时间的不是复杂技术,而是反复SSH登录、敲命令、等回显、记录结果。paramiko是Python实现的SSHv2协议客户端库,适合把“登录、执行、读取、保存”这套动作代码化、批量化、可重复化。Ansible、Nornir这类工具更像自动挡,paramiko更像手动挡,连接、认证、执行、读取全在自己控制范围内。本文围绕paramiko批量管理网络设备的实践,梳理单台SSH执行、并发采集、配置下发、多厂商差异、编码与分页等常见坑。
一、练手环境和Python环境准备
不建议直接拿生产环境练手,可以用GNS3或eNSP搭实验环境。GNS3可以跑思科IOS路由器和交换机镜像,给设备分配管理IP,宿主机直接SSH访问。原文示例中管理IP为192.168.56.101到192.168.56.105,掩码24位,网关指向192.168.56.1。eNSP适合练华为VRP命令,但不少版本默认不开SSH,需要先配置VTY用户、认证方式和RSA密钥。真实二手设备如思科2960、华为S5700也能练,而且更容易遇到密钥交换算法不匹配、加密算法太弱、设备时间不对导致认证失败等问题。
Python建议用3.8以上版本,paramiko是纯Python写的,底层依赖cryptography和bcrypt,三大平台通常有预编译wheel包。安装命令:
国内网络可换镜像源:
- pip install paramiko -i https://pypi.tuna.tsinghua.edu.cn/simple
复制代码
验证安装:
- import paramiko
- print(paramiko.__version__)
复制代码
新版本paramiko要求Python 3.6以上。如果还在用Python 2.7,老版本paramiko虽然能跑,但连接新设备时可能因为加密算法支持不足导致握手失败,建议尽早迁移。项目依赖建议用虚拟环境隔离:
- python -m venv netauto_env
- # Windows激活
- netauto_env\Scripts\activate
- # Linux/macOS激活
- source netauto_env/bin/activate
- pip install paramiko
复制代码
二、单台设备SSH执行:先把连接和回显打通
批量操作前,先跑通单台设备。paramiko常用SSHClient,设置AutoAddPolicy自动接受未知主机密钥,生产环境更建议用load_system_host_keys或已知密钥。下面是一个基础执行函数,核心是建立连接、获取交互式shell、发送命令、读取回显、关闭连接:
- import paramiko
- import time
- def exec_command(host, username, password, command, enable_password=None):
- client = paramiko.SSHClient()
- client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
- try:
- client.connect(
- hostname=host,
- port=22,
- username=username,
- password=password,
- timeout=10,
- look_for_keys=False,
- allow_agent=False,
- banner_timeout=15,
- auth_timeout=15
- )
- shell = client.invoke_shell()
- time.sleep(1)
- shell.recv(65535)
- shell.send(command + '\n')
- time.sleep(2)
- output = shell.recv(65535).decode('utf-8', errors='ignore')
- return output
- finally:
- client.close()
复制代码
这里有几个关键参数。timeout控制TCP连接超时,banner_timeout控制SSH协议握手阶段超时,auth_timeout控制认证阶段超时。批量执行时如果某个IP不通或SSH服务异常,没有这些超时控制,脚本可能长时间卡住。invoke_shell()拿到的是交互式shell,能模拟真实终端登录,也能处理需要交互的命令;代价是回显需要自己判断命令是否结束。示例里用sleep等待再recv是一种简化做法,更严谨的方式是循环读取,直到输出不再变化或检测到提示符再停止。
认证方式主要有密码认证和密钥认证。密码认证简单,但密码硬编码在脚本里有泄露风险,频繁连接大量设备还可能触发账号锁定。密钥认证可以这样连接:
- client = paramiko.SSHClient()
- client.connect(
- hostname=host,
- port=22,
- username='admin',
- key_filename='/home/ops/.ssh/id_rsa',
- timeout=10
- )
复制代码
密钥认证在批量操作中更安全,脚本里不用放密码。但网络设备端配置更复杂,思科要指定ip ssh pubkey-chain,华为要用rsa peer-public-key导入公钥,不同厂商、不同型号命令有差异。设备规模不大时,密码认证配合环境变量或配置文件读取密码也够用;大规模自动化平台更建议对接密钥管理系统。
三、并发批量采集:线程池和超时隔离
单台跑通后,批量就是读取设备清单并循环调用。串行执行有性能瓶颈,假设单台连接加执行3秒,30台就是90秒。用concurrent.futures线程池可以并发执行:
- import paramiko
- import time
- from concurrent.futures import ThreadPoolExecutor, as_completed
- def collect_device_info(device):
- host = device['host']
- username = device['username']
- password = device['password']
- command = device.get('command', 'show version')
- try:
- client = paramiko.SSHClient()
- client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
- client.connect(
- hostname=host,
- port=22,
- username=username,
- password=password,
- timeout=10,
- look_for_keys=False,
- allow_agent=False,
- banner_timeout=15,
- auth_timeout=15
- )
- shell = client.invoke_shell()
- time.sleep(1)
- shell.recv(65535)
- shell.send(command + '\n')
- time.sleep(2)
- output = shell.recv(65535).decode('utf-8', errors='ignore')
- client.close()
- return {'host': host, 'status': 'success', 'output': output}
- except Exception as e:
- return {'host': host, 'status': 'failed', 'error': str(e)}
- def batch_collect(devices, max_workers=10):
- results = []
- with ThreadPoolExecutor(max_workers=max_workers) as executor:
- future_map = {executor.submit(collect_device_info, dev): dev for dev in devices}
- for future in as_completed(future_map):
- results.append(future.result())
- return results
复制代码
max_workers=10表示最多同时跑10个线程,也就是同时SSH登录10台设备。线程数不是越大越好,要考虑跳板机资源、网络带宽和设备SSH连接数限制。有些老交换机SSH并发数有限,同时登录太多会拒绝新连接。20台以内设备并发数设5到10比较稳妥,上百台可以提高到20到30,但建议分段执行。as_completed是谁先完成谁先返回,不会因为某一台设备卡住而阻塞其他设备采集。
四、批量配置下发:模板、干跑、保存和验证
批量查询是“读”,批量配置下发是“写”,风险级别完全不同。配置下发至少要满足几个条件。第一,设备分组和配置模板分离,不同角色设备下发不同内容。第二,支持干跑模式dry-run,正式执行前生成变更内容供Review。第三,配置下发后自动保存,思科用write memory,华为用save,H3C用save force。第四,配置变更后自动验证,不能只看到命令执行成功就结束。
多厂商混用是常态,思科是show running-config,华为是display current-configuration;思科进特权模式用enable,华为进系统视图用system-view。可以在设备清单里增加vendor字段,按厂商和动作返回命令列表:
- def get_commands_by_vendor(vendor, action, params=None):
- if vendor == 'cisco':
- if action == 'backup':
- return ['show running-config']
- elif action == 'save':
- return ['write memory']
- elif action == 'config_ntp':
- return ['configure terminal', 'ntp server ' + params['ntp_server'], 'end', 'write memory']
- elif vendor == 'huawei':
- if action == 'backup':
- return ['display current-configuration']
- elif action == 'save':
- return ['save', 'Y']
- elif action == 'config_ntp':
- return ['system-view', 'ntp-service unicast-server ' + params['ntp_server'], 'return', 'save', 'Y']
- elif vendor == 'h3c':
- if action == 'backup':
- return ['display current-configuration']
- elif action == 'save':
- return ['save force']
复制代码
这块看起来简单,实际坑很多。NTP命令思科支持ntp server,华为支持ntp-service unicast-server,H3C也支持ntp-service unicast-server,但不同版本、不同型号还有差异。用Netmiko这类基于paramiko二次封装的库可以省去部分多厂商命令映射,但理解paramiko底层执行过程仍然有价值。
配置下发后一定要验证。比如下发NTP后执行show ntp status,检查输出里是否有Synchronized或clock is synchronized,有就标记成功,没有就标记warning交给人工确认。原文提到一个实际案例:设备上配置的NTP服务器地址可达,但设备始终没同步,脚本给出warning,人工排查发现是设备到NTP服务器之间防火墙拦截了123端口。如果脚本只判断“命令执行成功”,这个故障就会被漏掉。
五、实测高频坑:编码、分页、超时和交互
1. 中文设备名和回显乱码。国内设备名、描述信息、SNMP团体名可能是中文。paramiko默认decode('utf-8')不是万能解药,思科老设备可能用ASCII或Latin-1,华为VRP默认GBK的场景也存在。读取回显时用errors='ignore'可避免UnicodeDecodeError导致脚本崩溃。更通用的做法是多重编码尝试:
- def smart_decode(data: bytes) -> str:
- for encoding in ('utf-8', 'gbk', 'gb2312', 'latin-1'):
- try:
- return data.decode(encoding)
- except UnicodeDecodeError:
- continue
- return data.decode('utf-8', errors='ignore')
复制代码
原文真实案例是:一台华为S5720交换机设备名为“三层-核心-A”,display version输出里的中文用utf-8解码正常;另一台老版本S5700输出同样中文却用GBK编码,utf-8直接报错。多重编码尝试虽然不优雅,但能覆盖常见设备。
2. 分页输出导致命令“卡死”。很多show命令默认分页显示,比如思科show running-config输出较长时会出现“--More--”,等待用户按空格或回车继续。paramiko的shell.recv()只收了第一屏,你以为命令执行完了,设备其实还在等翻页,后续命令无法正常执行。解法是执行命令前关闭分页。思科在特权模式执行terminal length 0,华为执行screen-length 0 temporary,锐捷执行terminal length 0。推荐在脚本开头统一执行:
- # 思科
- terminal length 0
- # 华为
- screen-length 0 temporary
- # 锐捷
- terminal length 0
复制代码
3. 超时与设备无响应。connect的timeout、banner_timeout、auth_timeout要设置,异常要隔离,单台失败不能影响整批任务。as_completed可以让先完成的设备先返回。原文提到某台设备SSH握手特别慢,如果不加超时控制,串行执行时那一台就能拖慢整个批次。
4. 敏感操作的安全网。配置下发前先备份,先dry-run,再执行;执行后自动保存并验证;验证结果不确定时标记warning交人工判断,而不是强行判定成功或失败。涉及批量改口令、重启端口、清理MAC表项等操作时,更要有回滚和确认机制。
六、项目组织和进阶方向
实际项目中,建议把设备清单、命令模板、采集逻辑、下发逻辑、验证逻辑拆成独立模块。设备清单可以来自Excel、CSV或CMDB接口。采集脚本负责批量读取状态,配置脚本负责按vendor和action生成命令,验证脚本负责检查关键字并汇总结果。
进阶方向上,Netmiko基于paramiko二次封装,自带多厂商命令映射和交互式命令处理;Nornir适合更复杂的并发和资产组织;运维平台则可以把设备操作封装成API。不过给新人的建议仍然是:先会手动登录设备、敲命令、看回显、排查问题,再谈自动化。paramiko适合批量巡检CPU、内存、温度、光功率,批量备份配置文件,批量下发NTP、SNMP、AAA、日志服务器,批量修改口令、重启端口、清理MAC表项等场景。设备数量上到几十上百台,或者操作需要标准化执行时,paramiko脚本的价值就会明显体现出来。 |