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。这样在设备响应慢时不会丢数据,响应快时也不会浪费时间。

Paramiko 批量巡检脚本数据流 本地巡检脚本 设备清单 devices.csv IP / 用户名 / 密码 SSHClient.connect() 认证 + 密钥协商 invoke_shell() 交互式通道 send / recv 循环 匹配提示符 解析 + 写入结果 CSV / JSON 巡检报告输出 SSH 加密通道(TCP 22) 传输层:密钥交换 / 加密 AES / ChaCha20 · HMAC 校验 用户认证层 password / publickey 连接层:多路复用 session / sftp 子系统 通道层:Channel exec / shell / sftp 命令回显 / 文件字节流 网络设备 SSH Server 端口 22 CLI 解析器 display / show 配置 / 状态库 接口 / 路由 / 版本 日志文件系统 /var/log SFTP 子系统 上传 / 下载 设备响应 回传:命令回显 / 文件内容 → 解析 → 报告

四、第一个批量巡检脚本

把上述能力组合起来,就得到一个可用的批量巡检脚本。设计要点:设备清单从 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),把脚本从“能跑”提升到“好用”。