Python 学习笔记(第十三期)------运维自动化(下·前篇):远程命令执行------paramiko基础篇
更多Python知识:Python基础入门
我的主页:AOwhisky,这里有更多运维系统性知识整理和其他有趣内容,欢迎与我一起探讨学习~
📖 引言:运维的"远程遥控器"
想象一下这样的场景:你管理着 50 台 Linux 服务器,需要在每台服务器上执行 df -h 检查磁盘空间,然后逐台分析结果。如果手动登录每一台服务器,输入命令,记录结果,等全部检查完,可能已经过去了一个小时,而最早检查的那台服务器可能又有了新的变化。
这就是运维工作中最常见的困境------重复性操作的效率瓶颈。
SSH(Secure Shell)是运维人员最常使用的远程管理协议,而 paramiko 是 Python 生态中最成熟的 SSH 客户端库。它让你能够用 Python 代码替代手工操作:批量执行命令、自动收集结果、集中分析数据,将运维效率提升一个数量级。
本期作为 paramiko 系列的第一篇,我们将从最基础、最常用的功能入手:建立 SSH 连接、执行远程命令、处理命令输出。学完本期,你将具备用 Python 批量管理服务器的基本能力。
--- Compiled and Authored by Whisky --- July 21 st, 2026
📑 本期目录
- paramiko 概述与环境部署
- SSHClient 核心组件
- 主机密钥策略(Host Key Policy)
- 远程命令执行(
exec_command) - 命令执行结果处理
- 批量运维实战:多服务器内存巡检
- 总结与知识点一览表
正文
一、paramiko 概述与环境部署
1.1 什么是 paramiko
paramiko 是一个基于 Python 实现的 SSHv2 协议客户端库,它提供了 SSH 连接、远程命令执行、文件传输(SFTP)、密钥认证等完整的 SSH 功能。
💡 白话理解 :paramiko 就是一个"程序员的 SSH 客户端"------你用 PuTTY 或
ssh命令能做的事情,paramiko 都能用 Python 代码来实现。而且因为它是程序化的,可以批量、自动、定时地执行操作。
paramiko vs 其他远程执行方式:
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| ssh 命令(Shell) | 简单直接 | 无法编程化 | 临时操作 |
| pexpect / expect | 可处理交互式命令 | 脚本复杂,难以维护 | 简单的交互自动化 |
| ansible / saltstack | 功能强大,成熟方案 | 需要额外部署和学习 | 大规模配置管理 |
| paramiko | 轻量、灵活、可编程 | 需处理底层细节 | Python 运维脚本开发 |
1.2 环境部署
paramiko 的安装依赖于一些底层库,下面分别介绍 Linux 和 Windows 下的部署方法。
Linux 环境下部署(CentOS / RHEL 系列):
首先安装编译 Python3 所需的依赖包,然后编译安装 Python3,最后通过 pip 安装 paramiko。
bash
# 1. 安装编译依赖
yum install -y openssl-devel bzip2-devel expat-devel gdbm-devel readline-devel \
sqlite-devel libffi-devel gcc gcc-c++
# 2. 下载并编译安装 Python 3.13(可选,如果系统已有 Python3 可跳过)
tar zxvf Python-3.13.2.tgz
cd Python-3.13.2/
./configure --prefix=/usr/local/python3
make && make install
# 3. 创建软链接
ln -s /usr/local/python3/bin/python3 /usr/bin/python3
ln -s /usr/local/python3/bin/pip3 /usr/bin/pip
# 4. 验证 Python 版本
python3 --version
# 5. 配置 pip 国内镜像源(加速下载)
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple/
# 或手动编辑配置文件
vim /root/.config/pip/pip.conf
# 配置文件内容:
# [global]
# timeout = 40
# index-url = https://pypi.tuna.tsinghua.edu.cn/simple
# 6. 安装 paramiko
pip install paramiko
验证安装:
python
import paramiko
print(paramiko.__version__)
运行结果:
text
3.4.0
1.3 paramiko 的核心组件
paramiko 有两个最常用的核心类:
| 组件 | 用途 | 适用场景 |
|---|---|---|
| SSHClient | SSH 会话的高级封装,用于远程命令执行 | 执行命令、批量巡检、远程部署 |
| SFTPClient | SFTP 会话,用于远程文件操作 | 文件上传、下载、远程文件管理 |
本期我们聚焦 SSHClient ,下一期再深入 SFTPClient。
二、SSHClient 核心组件
2.1 创建 SSHClient 对象
python
import paramiko
# 创建 SSHClient 对象
ssh_client = paramiko.SSHClient()
print(f"SSHClient 对象:{ssh_client}")
2.2 connect() 方法详解
connect() 是 SSHClient 最核心的方法,用于建立 SSH 连接。它支持的参数非常丰富:
| 参数 | 类型 | 说明 | 是否必须 |
|---|---|---|---|
hostname |
str | 目标主机地址 | ✅ 必须 |
port |
int | SSH 端口,默认 22 | ❌ 可选 |
username |
str | 登录用户名 | ❌ 可选(默认当前用户) |
password |
str | 密码认证 | ❌ 可选 |
pkey |
PKey | 私钥对象 | ❌ 可选 |
key_filename |
str/list | 私钥文件路径 | ❌ 可选 |
timeout |
float | 连接超时(秒) | ❌ 可选 |
allow_agent |
bool | 是否允许 SSH 代理 | ❌ 可选 |
look_for_keys |
bool | 是否在 ~/.ssh/ 中查找私钥 |
❌ 可选 |
compress |
bool | 是否启用压缩 | ❌ 可选 |
四种认证方式:
| 认证方式 | 代码示例 | 安全级别 | 推荐度 |
|---|---|---|---|
| 密码认证 | connect(hostname, username, password) |
中 | ⚠️ 测试环境可用 |
| 私钥认证 | connect(hostname, username, key_filename='/root/.ssh/id_rsa') |
高 | ✅ 生产环境推荐 |
| SSH 代理认证 | connect(hostname, username, allow_agent=True) |
高 | ✅ 跳板机场景 |
| 无密码认证(已配置免密) | connect(hostname, username, look_for_keys=True) |
高 | ✅ 已配置互信场景 |
2.3 密码认证连接示例
python
import paramiko
hostname = "192.168.128.128"
username = "root"
password = "123123"
# 创建 SSHClient 对象
ssh_client = paramiko.SSHClient()
# 设置主机密钥策略(后面详细介绍)
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 建立连接
ssh_client.connect(
hostname=hostname,
port=22,
username=username,
password=password,
timeout=10,
allow_agent=False
)
print(f"✅ 成功连接到 {hostname}")
# 关闭连接
ssh_client.close()
运行结果:
text
✅ 成功连接到 192.168.128.128
2.4 私钥认证连接示例
python
import paramiko
hostname = "192.168.128.128"
username = "root"
key_filename = "/root/.ssh/id_rsa"
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 私钥认证
ssh_client.connect(
hostname=hostname,
port=22,
username=username,
key_filename=key_filename,
timeout=10
)
print(f"✅ 使用私钥成功连接到 {hostname}")
ssh_client.close()
三、主机密钥策略(Host Key Policy)
3.1 什么是主机密钥策略
SSH 协议在首次连接时,会提示用户确认目标主机的指纹(fingerprint):
text
The authenticity of host '192.168.128.128 (192.168.128.128)' can't be established.
ECDSA key fingerprint is SHA256:xxxxx.
Are you sure you want to continue connecting (yes/no)?
在自动化的脚本中,无法手动输入 yes,因此 paramiko 提供了三种自动处理策略:
| 策略 | 行为 | 推荐度 |
|---|---|---|
| AutoAddPolicy | 自动接受未知主机的密钥,并保存到本地 known_hosts |
⚠️ 开发/测试环境 |
| WarningPolicy | 接受未知主机密钥,但记录警告日志 | ⚠️ 测试环境 |
| RejectPolicy(默认) | 拒绝未知主机密钥,抛出异常 | ✅ 生产环境(最安全) |
3.2 三种策略的代码对比
python
import paramiko
# 策略1:AutoAddPolicy(自动添加)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 效果:首次连接自动接受密钥,不提示
# 策略2:WarningPolicy(警告后接受)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
# 效果:日志中记录警告,但连接继续
# 策略3:RejectPolicy(拒绝,默认)
ssh = paramiko.SSHClient()
# 不设置策略时,默认使用 RejectPolicy
# 效果:首次连接会抛出 SSHException
⚠️ 生产环境安全建议:
- 在生产环境中,应使用 RejectPolicy (默认策略),并预先在
~/.ssh/known_hosts中配置所有目标主机的密钥- 或者使用 WarningPolicy 并配合日志监控,及时发现异常
- AutoAddPolicy 虽然方便,但存在中间人攻击(MITM)风险,仅建议在隔离的测试环境中使用
3.3 预配置 known_hosts 的方法
如果希望在安全的前提下实现自动化,可以预先配置 known_hosts:
bash
# 方法1:手动连接一次(输入 yes)
ssh root@192.168.128.128
# 方法2:使用 ssh-keyscan 批量添加
ssh-keyscan 192.168.128.128 >> ~/.ssh/known_hosts
# 批量添加多个主机
for ip in 192.168.128.128 192.168.128.129 192.168.128.130; do
ssh-keyscan $ip >> ~/.ssh/known_hosts
done
四、远程命令执行------exec_command()
4.1 基本用法
exec_command() 方法用于在远程主机上执行单条命令,返回三个文件对象:
python
stdin, stdout, stderr = ssh_client.exec_command(command)
| 返回值 | 类型 | 作用 |
|---|---|---|
stdin |
文件对象 | 向命令发送输入(如需要交互时) |
stdout |
文件对象 | 命令的标准输出 |
stderr |
文件对象 | 命令的错误输出 |
4.2 执行简单命令
python
import paramiko
hostname = "192.168.128.128"
username = "root"
password = "123123"
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh_client.connect(hostname=hostname, port=22, username=username, password=password)
# 执行命令
stdin, stdout, stderr = ssh_client.exec_command("free -m")
# 读取输出
print("命令执行结果:")
for line in stdout:
print(line.strip())
# 检查错误
error = stderr.read().decode('utf-8')
if error:
print(f"错误信息:{error}")
finally:
ssh_client.close()
运行结果:
text
命令执行结果:
total used free shared buff/cache available
Mem: 3869 534 587 25 2748 3026
Swap: 8191 0 8191
4.3 执行带参数的命令
python
# 使用列表参数(自动拼接)
stdin, stdout, stderr = ssh_client.exec_command("df -h /")
for line in stdout:
print(line.strip())
运行结果:
text
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 50G 12G 35G 26% /
4.4 执行需要输入的命令
有些命令需要交互式输入(如 sudo、passwd)。这时可以通过 stdin 发送输入:
python
import paramiko
import time
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname="192.168.128.128", username="root", password="123123")
# 执行 sudo 命令(需要输入密码)
stdin, stdout, stderr = ssh_client.exec_command("sudo -S ls /root")
# 通过 stdin 发送密码
stdin.write("123123\n")
stdin.flush() # 确保数据发送
# 读取输出
for line in stdout:
print(line.strip())
ssh_client.close()
运行结果:
text
anaconda-ks.cfg
⚠️ 注意 :
sudo -S会从标准输入读取密码,-S参数是必需的。
4.5 命令执行的状态检查
python
import paramiko
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname="192.168.128.128", username="root", password="123123")
# 执行一个可能失败的命令
stdin, stdout, stderr = ssh_client.exec_command("ls /non_existent_directory")
# 检查返回码(通过 channel)
# 注意:需要先读取输出,否则 channel 可能阻塞
stdout.read()
exit_code = stdout.channel.recv_exit_status()
if exit_code == 0:
print("✅ 命令执行成功")
else:
print(f"❌ 命令执行失败,返回码:{exit_code}")
error = stderr.read().decode('utf-8')
print(f"错误信息:{error}")
ssh_client.close()
运行结果:
text
❌ 命令执行失败,返回码:2
错误信息:ls: cannot access '/non_existent_directory': No such file or directory
五、命令执行结果处理
5.1 读取输出的方式
python
import paramiko
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname="192.168.128.128", username="root", password="123123")
stdin, stdout, stderr = ssh_client.exec_command("df -h")
# 方式1:逐行读取(适合大输出)
print("方式1:逐行读取")
for line in stdout:
print(f" {line.strip()}")
# 方式2:一次性读取全部(适合小输出)
print("方式2:一次性读取")
output = stdout.read().decode('utf-8')
print(output)
# 方式3:读取为列表
print("方式3:读取为列表")
lines = stdout.readlines()
for line in lines:
print(f" {line.strip()}")
ssh_client.close()
5.2 输出解码问题
stdout.read() 返回的是字节流(bytes),需要解码为字符串:
python
# ❌ 不推荐:直接使用字节流
data = stdout.read()
print(data) # b'total 4096\\n'
# ✅ 推荐:解码为字符串
text = stdout.read().decode('utf-8')
print(text) # total 4096
# ✅ 更推荐:使用 text=True 参数(需注意版本支持)
# Python 3.7+ 的 exec_command 支持 text 参数
stdin, stdout, stderr = ssh_client.exec_command("df -h", timeout=30)
# 但默认仍返回 bytes,建议显式解码
5.3 命令执行超时控制
exec_command() 支持 timeout 参数,防止命令卡死:
python
import paramiko
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname="192.168.128.128", username="root", password="123123")
try:
# 设置 5 秒超时
stdin, stdout, stderr = ssh_client.exec_command("sleep 10", timeout=5)
for line in stdout:
print(line.strip())
except paramiko.SSHException as e:
print(f"命令执行超时:{e}")
ssh_client.close()
运行结果:
text
命令执行超时:Timeout exceeded in channel operation
六、批量运维实战:多服务器内存巡检
需求描述:巡检 2 台及以上 Linux 服务器的内存使用情况,获取空闲内存(free)容量,按降序排序后写入文件。
设计思路:
- 将 SSHS 连接和命令执行封装为可复用的函数
- 循环连接所有目标服务器,收集内存空闲数据
- 对收集到的数据进行排序
- 将结果写入文件,每行一个值
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
多服务器内存巡检脚本
功能:批量获取多台服务器的空闲内存容量,排序后写入文件
作者:Whisky
"""
import paramiko
import time
def get_mem_free(host, port=22, user='root', pwd='123123'):
"""
连接目标服务器,获取空闲内存容量(MB)
返回:
success: True/False
mem_free: 空闲内存(MB),失败时返回 None
error: 错误信息
"""
ssh_client = None
try:
# 创建 SSH 客户端
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
ssh_client.connect(
hostname=host,
port=port,
username=user,
password=pwd,
timeout=10,
allow_agent=False
)
# 执行获取内存空闲量的命令
# free -m | grep Mem | awk '{print $4}'
command = "free -m | grep Mem | awk '{print $4}'"
stdin, stdout, stderr = ssh_client.exec_command(command, timeout=10)
# 读取输出
output = stdout.read().decode('utf-8').strip()
error = stderr.read().decode('utf-8').strip()
if error:
return False, None, error
if not output:
return False, None, "命令无输出"
# 尝试转为整数
try:
mem_free = int(output)
return True, mem_free, None
except ValueError:
return False, None, f"无法解析输出:{output}"
except paramiko.AuthenticationException:
return False, None, "认证失败(用户名或密码错误)"
except paramiko.SSHException as e:
return False, None, f"SSH 连接异常:{e}"
except Exception as e:
return False, None, f"连接错误:{e}"
finally:
if ssh_client:
ssh_client.close()
def main():
"""主程序"""
print("=" * 60)
print(" 🔍 多服务器内存巡检工具 v1.0")
print("=" * 60)
# 定义待巡检的服务器列表
hosts = [
{"ip": "192.168.100.138", "user": "root", "pwd": "123123"},
{"ip": "192.168.100.140", "user": "root", "pwd": "123123"},
# 可继续添加更多服务器
]
# 存储结果
results = []
failed_hosts = []
print(f"开始巡检 {len(hosts)} 台服务器...\n")
for idx, host_info in enumerate(hosts, 1):
host = host_info["ip"]
user = host_info["user"]
pwd = host_info["pwd"]
print(f"[{idx}/{len(hosts)}] 正在连接 {host} ...")
success, mem_free, error = get_mem_free(host, user=user, pwd=pwd)
if success:
print(f" ✅ {host} 空闲内存:{mem_free} MB")
results.append({
"host": host,
"mem_free": mem_free
})
else:
print(f" ❌ {host} 采集失败:{error}")
failed_hosts.append({
"host": host,
"error": error
})
# 避免连接过快,稍作间隔
time.sleep(0.5)
# 输出汇总
print("\n" + "=" * 60)
print(" 📊 采集结果汇总")
print("=" * 60)
if results:
# 按空闲内存从大到小排序
results.sort(key=lambda x: x["mem_free"], reverse=True)
print("\n✅ 成功采集(按空闲内存降序):")
print(" 服务器地址 空闲内存(MB)")
print(" " + "-" * 35)
for r in results:
print(f" {r['host']:20} {r['mem_free']:>6} MB")
# 写入文件
output_file = "memory_report.txt"
with open(output_file, "w", encoding="utf-8") as f:
f.write("服务器内存空闲报告\n")
f.write("生成时间:" + time.strftime("%Y-%m-%d %H:%M:%S") + "\n")
f.write("=" * 40 + "\n")
for r in results:
f.write(f"{r['host']}: {r['mem_free']} MB\n")
print(f"\n💾 报告已保存到:{output_file}")
if failed_hosts:
print("\n❌ 采集失败的服务器:")
for f in failed_hosts:
print(f" {f['host']}:{f['error']}")
print("\n" + "=" * 60)
print(" 巡检完成!")
print("=" * 60)
if __name__ == "__main__":
main()
运行结果:
text
============================================================
🔍 多服务器内存巡检工具 v1.0
============================================================
开始巡检 2 台服务器...
[1/2] 正在连接 192.168.100.138 ...
✅ 192.168.100.138 空闲内存:512 MB
[2/2] 正在连接 192.168.100.140 ...
✅ 192.168.100.140 空闲内存:234 MB
============================================================
📊 采集结果汇总
============================================================
✅ 成功采集(按空闲内存降序):
服务器地址 空闲内存(MB)
-----------------------------------
192.168.100.138 512 MB
192.168.100.140 234 MB
💾 报告已保存到:memory_report.txt
============================================================
巡检完成!
============================================================
七、补充知识:日志记录
在生产环境中,运维脚本通常需要记录日志以便于审计和故障排查。paramiko 提供了日志功能:
python
import paramiko
# 记录 paramiko 日志到文件
paramiko.util.log_to_file('paramiko.log')
# 后续所有 paramiko 操作都会记录日志
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname="192.168.128.128", username="root", password="123123")
日志文件示例 (paramiko.log):
text
2026-07-20 10:23:45,123 [INFO] paramiko.transport: Connected to 192.168.128.128
2026-07-20 10:23:45,234 [INFO] paramiko.transport: Authentication successful
2026-07-20 10:23:45,345 [INFO] paramiko.channel: Channel opened
📝 总结与知识点一览表
核心 API 速查表
| 功能 | 核心 API | 说明 |
|---|---|---|
| 创建 SSH 客户端 | ssh = paramiko.SSHClient() |
实例化 SSH 客户端对象 |
| 设置主机密钥策略 | ssh.set_missing_host_key_policy(policy) |
AutoAddPolicy / WarningPolicy / RejectPolicy |
| 建立连接 | ssh.connect(hostname, username, password, port, timeout) |
认证方式:密码/私钥 |
| 执行远程命令 | stdin, stdout, stderr = ssh.exec_command(cmd) |
返回三个文件对象 |
| 读取标准输出 | stdout.read().decode('utf-8') |
读取命令输出并解码 |
| 获取返回码 | stdout.channel.recv_exit_status() |
0 表示成功,非 0 表示失败 |
| 关闭连接 | ssh.close() |
释放 SSH 会话 |
| 日志记录 | paramiko.util.log_to_file(filename) |
将操作日志写入文件 |
主机密钥策略对比
| 策略 | 行为 | 安全性 | 适用场景 |
|---|---|---|---|
AutoAddPolicy() |
自动接受并保存未知主机密钥 | ⚠️ 较低 | 开发/测试环境 |
WarningPolicy() |
接受未知主机密钥,记录警告 | ⚠️ 中等 | 测试环境 |
RejectPolicy() |
拒绝未知主机密钥,抛出异常 | ✅ 高 | 生产环境(强烈推荐) |
认证方式对比
| 认证方式 | 安全性 | 适用场景 |
|---|---|---|
| 密码认证 | ⚠️ 密码可能被拦截 | 测试环境、临时操作 |
| 私钥认证 | ✅ 公钥加密,安全性高 | 生产环境、自动化脚本 |
| SSH 代理认证 | ✅ 密钥不落盘 | 跳板机场景 |
常见错误排查指南
| 错误现象 | 大概率原因 | 解决方案 |
|---|---|---|
NoHostKeyForHost |
主机密钥未在 known_hosts 中 | 设置策略或预先添加密钥 |
AuthenticationException |
用户名/密码错误 | 检查认证信息 |
SSHException: Timeout expired |
网络不通或防火墙拦截 | 检查网络连通性、防火墙规则 |
ChannelException: [Errno 32] Broken pipe |
连接中断(超时或服务重启) | 增加 timeout 设置,添加重试逻辑 |
命令输出为 b''(空) |
命令本身无输出或执行失败 | 检查命令是否正确,读取 stderr |
| 中文输出乱码 | 编码不一致(如 GBK) | 使用 decode('gbk') 或 decode('utf-8') 根据目标系统设置 |
学习/生产最佳实践
- 生产环境优先使用私钥认证:避免在脚本中硬编码密码,私钥认证更安全。如果必须用密码,应将密码存储在环境变量或 Vault 等安全工具中,而不是直接写在代码里。
- 始终关闭 SSH 连接 :使用
finally或在连接后显式调用close(),避免资源泄漏。 - 设置合理的超时 :为
connect()和exec_command()都设置timeout参数,防止网络问题导致脚本卡死。 - 正确处理命令返回码 :不要仅凭
stdout的输出判断命令是否成功,应该检查recv_exit_status()。 - 记录操作日志 :生产环境中开启
paramiko.util.log_to_file(),便于事后审计和故障排查。 - 避免循环中频繁创建连接:在批量操作时,可以考虑复用 SSH 连接(保持连接池),减少握手开销。
🔜 下期预告
本期我们学习了 paramiko 的基础篇------从环境部署、SSHClient 的创建与连接认证、主机密钥策略,到远程命令执行(exec_command)和结果处理,最后通过"多服务器内存巡检"脚本将知识整合为实战工具。
下一期 ,我们将进入 paramiko 的进阶篇------远程文件传输。你将学习 SFTPClient 的使用,掌握如何通过 Python 代码实现文件的上传、下载、远程目录管理等操作,并通过"堡垒机场景下的文件分发"实战案例,将 SSH 命令执行与 SFTP 文件传输结合起来,构建更完整的自动化运维工具链。敬请期待!
本期(paramiko 基础篇)到此结束。 如果您在使用过程中发现任何问题或有改进建议,欢迎随时提出!
--- Compiled and Authored by Whisky --- July 21 st, 2026