跳到主要内容

SSH 远程自动化(paramiko)

批量重启、分发配置、远程执行巡检,离不开 SSH。Python 常用 paramiko 建立会话、执行命令、SFTP 传文件。密钥认证优于密码;生产建议配合堡垒机与 sudo 审计。


安装

cd ~/python-for-ops
uv add paramiko

连接与执行命令

#!/usr/bin/env python3
"""使用密钥连接远程主机并执行只读巡检命令。"""
from __future__ import annotations

import os
import socket
from dataclasses import dataclass

import paramiko

@dataclass
class RemoteResult:
host: str
exit_code: int
stdout: str
stderr: str

def connect(
host: str,
username: str,
key_path: str | None = None,
port: int = 22,
timeout: float = 10.0,
) -> paramiko.SSHClient:
client = paramiko.SSHClient()
known_hosts = os.environ.get("OPS_KNOWN_HOSTS", os.path.expanduser("~/.ssh/known_hosts"))
if not os.path.exists(known_hosts):
raise FileNotFoundError(f"known_hosts 不存在: {known_hosts}")
# 先加载受管的主机公钥,再拒绝未知主机;不能用 AutoAddPolicy 绕过校验。
client.load_host_keys(known_hosts)
client.set_missing_host_key_policy(paramiko.RejectPolicy())
key_path = key_path or os.path.expanduser("~/.ssh/id_ed25519")
client.connect(
hostname=host,
port=port,
username=username,
# key_filename 支持 Ed25519、RSA 等多种密钥类型;不把私钥内容加载进业务代码。
key_filename=key_path,
timeout=timeout,
banner_timeout=timeout,
auth_timeout=timeout,
)
return client

def run_remote(client: paramiko.SSHClient, host: str, command: str, timeout: int = 60) -> RemoteResult:
stdin, stdout, stderr = client.exec_command(command, timeout=timeout)
exit_code = stdout.channel.recv_exit_status()
return RemoteResult(
host=host,
exit_code=exit_code,
stdout=stdout.read().decode(errors="replace"),
stderr=stderr.read().decode(errors="replace"),
)

def inspect_host(host: str, user: str = "ops") -> RemoteResult:
cmd = "df -h / && free -m | head -2 && uptime"
client = connect(host, user)
try:
return run_remote(client, host, cmd)
finally:
client.close()

if __name__ == "__main__":
target = os.environ.get("TARGET_HOST", "192.168.1.10")
try:
res = inspect_host(target)
print(res.stdout)
if res.exit_code != 0:
print("stderr:", res.stderr)
except (paramiko.SSHException, socket.error) as e:
raise SystemExit(f"SSH 失败 {target}: {e}") from e

SFTP 上传文件

def upload_file(host: str, user: str, local_path: str, remote_path: str) -> None:
client = connect(host, user)
try:
sftp = client.open_sftp()
sftp.put(local_path, remote_path)
sftp.close()
finally:
client.close()

批量主机(线程池)

from concurrent.futures import ThreadPoolExecutor, as_completed

HOSTS = ["10.0.0.11", "10.0.0.12", "10.0.0.13"]

def batch_uptime(hosts: list[str], user: str = "ops") -> list[RemoteResult]:
results = []
with ThreadPoolExecutor(max_workers=10) as pool:
futures = {pool.submit(inspect_host, h, user): h for h in hosts}
for fut in as_completed(futures):
try:
results.append(fut.result())
except Exception as e:
h = futures[fut]
results.append(RemoteResult(h, -1, "", str(e)))
return results

安全与实践

说明
Host Key首次可用 KnownHostsFile,勿长期 AutoAddPolicy
命令远程命令避免 shell 拼接用户输入
权限最小账号 + sudo 限定命令
超时exec_command(..., timeout=) 防 hang
密钥chmod 600,不入库

首次接入主机前,应通过 CMDB、控制台或带外渠道核验指纹后再写入受管 known_hosts;不要在自动化脚本执行时自动接受新指纹。可通过 OPS_KNOWN_HOSTS 指定项目专用文件,方便审计和轮换。

提示

大规模编排可交给 Ansible;paramiko 适合定制逻辑(交互式步骤、特殊 SFTP、与 Python 数据处理无缝衔接)。