3. Paramiko实战
Paramiko 是 Python 中实现 SSHv2 协议的纯 Python 库,它把底层加密协商、密钥交换、认证和通道复用封装成几个直观的类。对网络自动化来说,最常用的三个对象是:SSHClient(面向会话的高层封装)、SFTPClient(基于 SSH 通道的文件传输)、Channel(交互式会话与实时输出)。本节围绕这三者展开,最终落地为一个可连接多台设备、收集基础信息的批量巡检脚本。
一、SSHClient:登录与执行命令
使用 SSHClient 的典型流程是:创建客户端 → 设置缺失主机密钥策略 → 连接 → 执行命令 → 关闭。其中 set_missing_host_key_policy 决定首次遇到未知主机密钥时的行为,AutoAddPolicy 会自动接受并写入 known_hosts,适合实验环境;生产环境更推荐 RejectPolicy 配合预置密钥,避免中间人风险。
import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(
hostname="192.168.1.10",
port=22,
username="admin",
password="Admin@123",
timeout=8,
look_for_keys=False,
allow_agent=False,
)
stdin, stdout, stderr = client.exec_command("display version", timeout=10)
out = stdout.read().decode("utf-8", errors="ignore")
err = stderr.read().decode("utf-8", errors="ignore")
code = stdout.channel.recv_exit_status()
client.close()
这里有几个容易踩坑的点。第一,exec_command 返回的三个流是文件类对象,必须显式 read() 才能拿到内容;第二,recv_exit_status() 要在读取完输出后调用,否则可能阻塞;第三,网络设备(如华为、H3C、Cisco)的 CLI 往往不支持非交互式 exec,直接执行会报错或返回空,此时应改用 invoke_shell() 交互式会话。
二、SFTP:上传与下载文件
文件传输通过 open_sftp() 在已有 SSH 连接上开启 SFTP 子系统,无需额外端口。常用方法有 put、get、listdir、stat、mkdir、remove。上传配置备份、下载日志文件是最典型的两个场景。
sftp = client.open_sftp()
sftp.put("local_config.cfg", "/tmp/remote_config.cfg")
sftp.get("/var/log/messages", "messages.log")
print(sftp.listdir("/tmp"))
sftp.close()
注意 put/get 的 confirm 参数默认开启,会校验文件大小;大文件传输时可设置 callback 回调函数显示进度。若目标目录不存在,需先 mkdir,否则会抛出 IOError。
三、invoke_shell:处理交互式会话
网络设备登录后通常需要进入系统视图、关闭分屏、执行多条命令。这时用 invoke_shell 打开一个交互式通道,通过 send 发送命令、recv 读取回显。关键技巧是:发送命令后等待提示符出现再发下一条,避免命令被截断。
shell = client.invoke_shell()
shell.settimeout(5)
shell.send("screen-length 0 temporary\n")
time.sleep(1)
shell.send("display interface brief\n")
time.sleep(2)
buf = ""
while shell.recv_ready():
buf += shell.recv(65535).decode("utf-8", errors="ignore")
shell.close()
更稳健的做法是循环 recv 直到匹配到设备提示符(如 <Huawei> 或 #),而不是固定 sleep。这样在设备响应慢时不会丢数据,响应快时也不会浪费时间。
四、第一个批量巡检脚本
把上述能力组合起来,就得到一个可用的批量巡检脚本。设计要点:设备清单从 CSV 读取,逐台连接并采集版本、接口状态、CPU 利用率;每台设备独立 try/except,单台失败不影响整体;结果写入 CSV 便于后续分析。
import csv, time, paramiko
def collect(host, user, pwd, cmds):
result = {"host": host, "status": "ok", "data": {}}
cli = paramiko.SSHClient()
cli.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
cli.connect(host, 22, user, pwd, timeout=8,
look_for_keys=False, allow_agent=False)
sh = cli.invoke_shell()
sh.settimeout(6)
time.sleep(1)
sh.recv(65535)
for name, cmd in cmds.items():
sh.send(cmd + "\n")
time.sleep(1.5)
buf = ""
while sh.recv_ready():
buf += sh.recv(65535).decode("utf-8", "ignore")
result["data"][name] = buf.strip()
sh.close()
except Exception as e:
result["status"] = f"fail: {e}"
finally:
cli.close()
return result
CMDS = {
"version": "display version",
"interface": "display interface brief",
"cpu": "display cpu-usage",
}
with open("devices.csv", encoding="utf-8") as f, \
open("inspect_result.csv", "w", newline="", encoding="utf-8") as out:
writer = csv.writer(out)
writer.writerow(["host", "status", "version", "interface", "cpu"])
for row in csv.DictReader(f):
r = collect(row["host"], row["user"], row["pwd"], CMDS)
d = r["data"]
writer.writerow([r["host"], r["status"],
d.get("version", ""), d.get("interface", ""), d.get("cpu", "")])
print(r["host"], r["status"])
运行后,inspect_result.csv 中每行对应一台设备,status 为 ok 表示采集成功,fail: ... 则记录异常原因(如认证失败、超时、连接被拒)。通过对比 version 字段可快速发现版本不一致的设备,通过 interface 字段可筛查 down 的端口,通过 cpu 字段可定位高负载节点。
AutoAddPolicy 加明文密码。推荐改用 SSH 密钥认证,并配合 RejectPolicy 与预置 known_hosts;密码可放入环境变量或密钥管理服务,避免硬编码在脚本中。五、常见问题与排查
连接超时通常由网络不通、ACL 拦截或设备 SSH 未开启导致,可先用 ssh -v 手动验证。认证失败要区分密码错误与算法不兼容,老设备可能只支持 ssh-rsa,需要在 Paramiko 中显式启用。执行命令返回空,多半是设备 CLI 不支持 exec,改用 invoke_shell 即可。回显不完整则往往是 sleep 太短或未循环 recv,应改为按提示符判断结束。SFTP 报 Permission denied 时检查目标目录权限与磁盘空间。
掌握这些要点后,你已经具备用 Paramiko 完成日常巡检、配置备份和日志收集的能力。下一阶段可以在此基础上引入并发(ThreadPoolExecutor)和结构化解析(正则或 TextFSM),把脚本从“能跑”提升到“好用”。