Python 学习笔记(第十五期)------运维自动化(下·后篇):堡垒机实战------paramiko高阶篇
知识衔接:
Python 学习笔记(第十四期)------运维自动化(下·中篇):远程文件传输------paramiko进阶篇
更多Python知识:Python基础入门
云原生运维技能学习请戳这里:云原生技术精讲与实战
我的主页:AOwhisky,这里有更多运维系统性知识整理和其他有趣内容,欢迎与我一起探讨学习~
📖 引言:穿透堡垒机的自动化之桥
在前两期,我们掌握了 paramiko 的两大核心能力------远程命令执行 (SSHClient)和远程文件传输(SFTPClient)。你已经可以用 Python 脚本代替手工操作,在服务器上执行命令、上传下载文件,运维效率提升了数倍。
但在真实的生产环境中,事情往往没有那么简单。出于安全考虑,大多数企业的业务服务器并不允许直接 SSH 登录,而是要求运维人员先登录一台堡垒机(跳板机/ Bastion Host),再从堡垒机跳转到目标服务器。
这种架构虽然提升了安全级别,但也给自动化运维带来了新的挑战:
- 无法直接用 SSHClient 连接目标服务器(网络不通)
- 需要先登录堡垒机,再通过堡垒机跳转
- 涉及多次认证和交互式提示
- 需要处理命令提示符、密码输入等交互场景
这就像你要进入一栋有门禁的大楼------不能直接推门而入,而是要先在门卫处登记(登录堡垒机),再由门卫引导你到达指定的房间(跳转到目标服务器)。paramiko 的 invoke_shell 机制,就是解决这个问题的"自动化门禁卡"。
本期作为 paramiko 系列的收官篇,将深入讲解 invoke_shell 交互式会话的原理与应用,带你实现堡垒机环境下的命令执行和文件分发自动化。
--- Compiled and Authored by Whisky --- July 23 rd, 2026
📑 本期目录
- 堡垒机架构与挑战
invoke_shell交互式会话概述- 核心方法详解(
invoke_shell/send/recv) - 交互流程解析
- 堡垒机远程命令执行实战
- 堡垒机远程文件分发实战
- 常见问题与排查技巧
- 总结与知识点一览表
正文
一、堡垒机架构与挑战
1.1 什么是堡垒机
堡垒机(Bastion Host)是一种安全审计与访问控制设备,位于运维人员与业务服务器之间,承担着"唯一入口"的角色。
text
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 运维人员 │────▶│ 堡垒机 │────▶│ 目标服务器 │
│ (办公电脑) │ │ (跳板机) │ │ (业务系统) │
└─────────────┘ └─────────────┘ └─────────────┘
│
审计日志
操作录像
权限控制
堡垒机的核心价值:
| 功能 | 说明 |
|---|---|
| 统一入口 | 所有运维操作必须经过堡垒机,便于集中管理 |
| 身份认证 | 支持双因素认证、LDAP/AD 集成 |
| 权限控制 | 精细化控制每个用户能访问哪些服务器 |
| 操作审计 | 记录所有操作命令,支持录像回放 |
| 合规要求 | 满足等保、ISO27001 等安全合规要求 |
1.2 堡垒机场景下的自动化挑战
| 挑战 | 说明 | 对自动化的影响 |
|---|---|---|
| 无法直连目标 | 业务服务器只允许堡垒机访问 | SSHClient 无法直接连接目标服务器 |
| 交互式认证 | 堡垒机跳转时需要输入密码或确认密钥 | 需要处理交互式提示 |
| 命令提示符变化 | 不同服务器的提示符格式不同 | 需要灵活检测命令完成标志 |
| 会话复用 | 一次登录堡垒机,执行多个操作 | 需要复用 SSH 会话 |
💡 白话理解 :
exec_command就像"打电话"------拨号、说话、挂断,每次都是独立的。invoke_shell就像"远程桌面"------登录后建立一个持续的会话窗口,你可以在这个窗口中输入命令、等待输出、继续输入,就像坐在服务器面前操作一样。堡垒机场景必须用invoke_shell,因为需要模拟人工操作------先登录堡垒机,再输入ssh user@target,然后输入密码,最后才能执行命令。
二、invoke_shell 交互式会话概述
2.1 exec_command vs invoke_shell
| 对比维度 | exec_command |
invoke_shell |
|---|---|---|
| 适用场景 | 单条非交互式命令 | 交互式会话(需要多次输入) |
| 会话类型 | 一次性通道 | 持久化 Shell 会话 |
| 状态保持 | ❌ 每次执行独立 | ✅ 保持会话状态(环境变量、工作目录) |
| 交互能力 | ❌ 有限(仅通过 stdin) | ✅ 完全交互(模拟终端) |
| 堡垒机跳转 | ❌ 不支持 | ✅ 支持 |
| 复杂度 | 低 | 高(需处理提示符、缓冲区) |
2.2 核心工作流程
invoke_shell 的核心交互流程如下:
text
1. 创建 SSH 会话并调用 invoke_shell()
2. 通过 send() 发送命令(需追加换行符 \n)
3. 通过 recv() 接收命令输出
4. 循环等待,直到命令执行完成(检测命令提示符)
5. 重复步骤 2-4,执行多条命令
6. 关闭会话
三、核心方法详解
3.1 invoke_shell()------创建交互式会话
python
channel = ssh_client.invoke_shell(
term='vt100', # 终端类型
width=80, # 终端宽度
height=24, # 终端高度
timeout=10 # 超时时间
)
参数说明:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
term |
str | 'vt100' | 终端类型(模拟哪种终端) |
width |
int | 80 | 终端宽度(字符数) |
height |
int | 24 | 终端高度(行数) |
timeout |
float | None | 通道超时时间(秒) |
3.2 send()------发送命令
python
# 发送命令(注意:必须添加换行符)
channel.send("ls -la\n")
channel.send("ssh root@192.168.128.129\n")
⚠️ 关键点 :
send()不会自动添加换行符,必须手动在命令末尾添加\n,否则命令不会执行。
3.3 recv()------接收输出
python
# 接收输出(最多 1024 字节)
data = channel.recv(1024)
# 循环接收所有数据
while channel.recv_ready():
data += channel.recv(1024)
# 解码为字符串
output = data.decode('utf-8')
3.4 其他常用方法
| 方法 | 作用 | 示例 |
|---|---|---|
recv_ready() |
检查是否有数据可读 | if channel.recv_ready(): |
settimeout() |
设置超时时间 | channel.settimeout(10) |
close() |
关闭通道 | channel.close() |
exit_status_ready() |
检查命令是否完成 | if channel.exit_status_ready(): |
四、交互流程解析
4.1 核心交互模式
invoke_shell 的核心是"发送-等待-接收-解析"循环:
python
import paramiko
import time
def interactive_shell_example():
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname="192.168.128.128", username="root", password="123123")
# 1. 创建交互式会话
channel = ssh.invoke_shell()
channel.settimeout(10)
# 2. 发送命令
channel.send("ls -la /root\n")
# 3. 循环接收输出
output = ""
while True:
time.sleep(0.5) # 等待命令执行
if channel.recv_ready():
data = channel.recv(4096).decode('utf-8', errors='ignore')
output += data
print(data, end="")
# 检查命令是否执行完成(检测命令提示符)
if output.endswith("# "):
break
# 4. 发送第二条命令
channel.send("pwd\n")
# ... 继续接收 ...
channel.close()
ssh.close()
4.2 如何判断命令执行完成
这是 invoke_shell 最核心、也最容易出错的地方。常用的判断方法:
| 方法 | 适用场景 | 优缺点 |
|---|---|---|
| 检测命令提示符 | 有明确的 #、$ 或 > 提示符 |
✅ 最常用,适合通用场景 |
| 检测特定字符串 | 命令输出包含特定标志 | ⚠️ 需要提前知道输出内容 |
| 固定延时 | 命令执行时间可预测 | ❌ 不推荐(浪费时间或超时) |
| 检测命令回显 | 发送的命令回显后执行完成 | ⚠️ 需要精确匹配 |
命令提示符检测的改进:
python
import re
def is_command_prompt(text):
"""检测是否为命令提示符(根据常见格式)"""
# 常见提示符模式:
# [root@localhost ~]# (Linux root)
# [user@host ~]$ (Linux user)
# C:\Windows\System32> (Windows cmd)
# > (Windows PowerShell)
patterns = [
r'\]\$ ', # [user@host ~]$
r'\]\# ', # [root@localhost ~]#
r'\$ ', # 简单的 $
r'\# ', # 简单的 #
r'[A-Z]:\\.+>', # Windows C:\>
r'^> ' # >
]
combined = '|'.join(patterns)
return re.search(combined, text) is not None
4.3 处理密码输入
在堡垒机跳转场景中,最常见的就是 SSH 密码提示:
python
PASSWORD_PROMPT = "'s password:"
def handle_password_prompt(buffer, channel, password, max_retries=3):
"""处理密码提示并发送密码"""
retry_count = 0
while retry_count < max_retries:
if PASSWORD_PROMPT in buffer:
# 找到密码提示位置,发送密码
print(f"🔑 检测到密码提示,发送密码(第 {retry_count + 1} 次)")
channel.send(password + "\n")
buffer = "" # 清空缓冲区
return True, buffer
retry_count += 1
time.sleep(0.5)
if channel.recv_ready():
data = channel.recv(4096).decode('utf-8', errors='ignore')
buffer += data
return False, buffer
五、综合实战:堡垒机远程命令执行
需求描述 :通过堡垒机跳转到目标服务器,执行 ifconfig 命令查看网络配置。
流程:
text
1. SSH 登录堡垒机
2. invoke_shell 开启交互式会话
3. 发送 ssh root@目标IP
4. 检测密码提示,发送密码
5. 检测登录成功(提示符变为 #)
6. 发送 ifconfig 命令
7. 检测命令完成
8. 输出结果
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
堡垒机远程命令执行工具
功能:通过堡垒机在目标服务器上执行命令
作者:Whisky
"""
import paramiko
import time
import sys
import re
# ============ 配置 ============
BASTION_HOST = "192.168.128.128"
BASTION_USER = "root"
BASTION_PASSWORD = "123123"
TARGET_HOST = "192.168.128.129"
TARGET_USER = "root"
TARGET_PASSWORD = "123123"
PASSWORD_PROMPT = "'s password:"
# =============================
def is_command_prompt(text):
"""检测是否为命令提示符"""
patterns = [
r'\]# ', # [root@localhost ~]#
r'\]\$ ', # [user@host ~]$
r'# ', # 简单的 #
r'\$ ', # 简单的 $
]
combined = '|'.join(patterns)
return re.search(combined, text) is not None
def is_password_prompt(text):
"""检测是否为密码提示"""
return PASSWORD_PROMPT in text
def build_channel(ssh):
"""创建交互式会话"""
channel = ssh.invoke_shell()
channel.settimeout(10)
time.sleep(0.5) # 等待会话准备就绪
return channel
def send_command_and_get_output(channel, cmd, password=None, timeout=30):
"""
发送命令并获取输出(支持密码交互)
返回:output, success
"""
output = ""
start_time = time.time()
# 发送命令
channel.send(cmd + "\n")
password_sent = False
command_completed = False
while time.time() - start_time < timeout:
time.sleep(0.3)
if not channel.recv_ready():
continue
data = channel.recv(8192).decode('utf-8', errors='ignore')
output += data
# 检测是否需要输入密码
if password and not password_sent and is_password_prompt(output):
print(" 🔑 检测到密码提示,发送密码...")
channel.send(password + "\n")
password_sent = True
output = "" # 清空输出,避免密码回显残留
# 检测命令是否执行完成
if is_command_prompt(output):
# 检查是否是我们期望的提示符(跳转成功)
command_completed = True
break
return output, command_completed
def main():
print("=" * 60)
print(" 🔗 堡垒机远程命令执行工具 v1.0")
print("=" * 60)
print(f" 堡垒机:{BASTION_HOST}")
print(f" 目标服务器:{TARGET_HOST}")
print("=" * 60)
ssh = None
channel = None
try:
# 1. 登录堡垒机
print("\n📤 [1/4] 登录堡垒机...")
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(
hostname=BASTION_HOST,
username=BASTION_USER,
password=BASTION_PASSWORD,
timeout=10
)
print(" ✅ 堡垒机登录成功")
# 2. 创建交互式会话
print("\n📤 [2/4] 创建交互式会话...")
channel = build_channel(ssh)
print(" ✅ 交互式会话已创建")
# 3. SSH 跳转到目标服务器
print(f"\n📤 [3/4] SSH 跳转到目标服务器 {TARGET_HOST}...")
ssh_cmd = f"ssh {TARGET_USER}@{TARGET_HOST}"
print(f" 执行:{ssh_cmd}")
output, success = send_command_and_get_output(
channel,
ssh_cmd,
password=TARGET_PASSWORD,
timeout=30
)
if not success:
print(" ❌ 跳转失败(超时或认证错误)")
print(f" 输出:{output[:500]}")
sys.exit(1)
print(" ✅ 跳转成功")
# 4. 执行目标命令
print("\n📤 [4/4] 执行命令:ifconfig...")
target_cmd = "ifconfig\n"
channel.send(target_cmd)
# 收集输出
cmd_output = ""
timeout_start = time.time()
while time.time() - timeout_start < 15:
time.sleep(0.3)
if channel.recv_ready():
data = channel.recv(8192).decode('utf-8', errors='ignore')
cmd_output += data
if is_command_prompt(cmd_output):
break
print("\n" + "=" * 60)
print(" 📊 命令执行结果")
print("=" * 60)
# 输出结果(去除命令回显)
lines = cmd_output.split('\n')
# 跳过前几行(命令回显)
result_lines = []
skip_header = True
for line in lines:
if skip_header:
if "ifconfig" in line:
skip_header = False
continue
if line.strip():
result_lines.append(line)
print('\n'.join(result_lines))
print("=" * 60)
print(" ✅ 执行完成!")
print("=" * 60)
except paramiko.AuthenticationException:
print("❌ 认证失败,请检查用户名和密码")
except paramiko.SSHException as e:
print(f"❌ SSH 异常:{e}")
except Exception as e:
print(f"❌ 异常:{e}")
finally:
if channel:
channel.close()
if ssh:
ssh.close()
if __name__ == "__main__":
main()
运行结果:
text
============================================================
🔗 堡垒机远程命令执行工具 v1.0
============================================================
堡垒机:192.168.128.128
目标服务器:192.168.128.129
============================================================
📤 [1/4] 登录堡垒机...
✅ 堡垒机登录成功
📤 [2/4] 创建交互式会话...
✅ 交互式会话已创建
📤 [3/4] SSH 跳转到目标服务器 192.168.128.129...
执行:ssh root@192.168.128.129
🔑 检测到密码提示,发送密码...
✅ 跳转成功
📤 [4/4] 执行命令:ifconfig...
============================================================
📊 命令执行结果
============================================================
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.128.129 netmask 255.255.255.0 broadcast 192.168.128.255
inet6 fe80::20c:29ff:fe56:969b prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:56:96:9b txqueuelen 1000 (Ethernet)
RX packets 4004 bytes 370483 (361.7 KiB)
TX packets 2336 bytes 256789 (250.7 KiB)
============================================================
✅ 执行完成!
============================================================
六、综合实战:堡垒机远程文件分发(进阶版)
在上一期的基础上,我们将 invoke_shell 引入文件分发流程,实现完全自动化的堡垒机文件分发工具:
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
堡垒机远程文件分发工具(invoke_shell 版)
功能:通过堡垒机向目标服务器分发文件
流程:本地 → 堡垒机(SFTP上传)→ 目标服务器(SCP分发,使用 invoke_shell)
作者:Whisky
"""
import paramiko
import os
import time
import sys
import re
# ============ 配置 ============
BASTION_HOST = "192.168.128.128"
BASTION_USER = "root"
BASTION_PASSWORD = "123123"
TARGET_HOST = "192.168.128.129"
TARGET_USER = "root"
TARGET_PASSWORD = "123123"
LOCAL_FILE = "G:\\Python运维软件包\\Python-3.13.2.tgz"
TMP_PATH = "/tmp/Python-3.13.2.tgz"
REMOTE_PATH = "/data/Python-3.13.2.tgz"
PASSWORD_PROMPT = "'s password:"
# =============================
def upload_to_bastion():
"""上传文件到堡垒机(SFTP)"""
print("📤 [1/3] 上传文件到堡垒机...")
try:
# 创建 Transport
transport = paramiko.Transport((BASTION_HOST, 22))
transport.connect(username=BASTION_USER, password=BASTION_PASSWORD)
# 通过 from_transport 创建 SFTPClient
sftp = paramiko.SFTPClient.from_transport(transport)
# 获取文件大小
file_size = os.path.getsize(LOCAL_FILE)
print(f" 本地文件:{LOCAL_FILE}({file_size / (1024**2):.2f} MB)")
# 上传文件
sftp.put(LOCAL_FILE, TMP_PATH)
print(f" ✅ 上传成功:{LOCAL_FILE} → {BASTION_HOST}:{TMP_PATH}")
sftp.close()
transport.close()
return True
except Exception as e:
print(f" ❌ 上传失败:{e}")
return False
def is_command_prompt(text):
"""检测是否为命令提示符"""
patterns = [
r'\]# ',
r'\]\$ ',
r'# ',
r'\$ ',
]
combined = '|'.join(patterns)
return re.search(combined, text) is not None
def is_password_prompt(text):
return PASSWORD_PROMPT in text
def send_command_interactive(channel, cmd, password=None, timeout=60):
"""
发送命令并处理交互式提示
返回:output, success
"""
output = ""
start_time = time.time()
password_sent = False
channel.send(cmd + "\n")
while time.time() - start_time < timeout:
time.sleep(0.3)
if not channel.recv_ready():
continue
data = channel.recv(8192).decode('utf-8', errors='ignore')
output += data
# 处理主机密钥确认(yes/no)
if "yes/no" in output:
print(" ⚠️ 检测到主机密钥确认,发送 yes")
channel.send("yes\n")
output = ""
# 处理密码提示
if password and not password_sent and is_password_prompt(output):
print(" 🔑 检测到密码提示,发送密码...")
channel.send(password + "\n")
password_sent = True
output = ""
# 检测命令是否完成
if is_command_prompt(output):
return output, True
return output, False
def distribute_via_scp_invoke_shell():
"""通过 invoke_shell 执行 SCP 命令分发文件"""
print("\n📤 [2/3] 从堡垒机分发文件到目标服务器(invoke_shell)...")
ssh = None
channel = None
try:
# 1. 登录堡垒机
print(" 登录堡垒机...")
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(
hostname=BASTION_HOST,
username=BASTION_USER,
password=BASTION_PASSWORD,
timeout=10
)
print(" ✅ 堡垒机登录成功")
# 2. 创建交互式会话
print(" 创建交互式会话...")
channel = ssh.invoke_shell()
channel.settimeout(10)
time.sleep(0.5)
# 3. 执行 SCP 命令
scp_cmd = f"scp {TMP_PATH} {TARGET_USER}@{TARGET_HOST}:{REMOTE_PATH}"
print(f" 执行:{scp_cmd}")
output, success = send_command_interactive(
channel,
scp_cmd,
password=TARGET_PASSWORD,
timeout=60
)
if success:
print(" ✅ SCP 分发完成")
print("\n 执行结果:")
for line in output.split('\n'):
if line.strip():
print(f" {line}")
return True
else:
print(f" ❌ SCP 分发失败")
print(f" 输出:{output[:500]}")
return False
except Exception as e:
print(f" ❌ 分发异常:{e}")
return False
finally:
if channel:
channel.close()
if ssh:
ssh.close()
def verify_deployment():
"""验证文件部署"""
print("\n📤 [3/3] 验证文件部署...")
ssh = None
try:
# 通过堡垒机验证
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=BASTION_HOST, username=BASTION_USER, password=BASTION_PASSWORD)
cmd = f'ssh {TARGET_USER}@{TARGET_HOST} "ls -lh {REMOTE_PATH}"'
stdin, stdout, stderr = ssh.exec_command(cmd, timeout=10)
output = stdout.read().decode('utf-8')
error = stderr.read().decode('utf-8')
if output:
print(" ✅ 文件部署成功:")
for line in output.strip().split('\n'):
print(f" {line}")
return True
else:
print(f" ❌ 未找到文件:{REMOTE_PATH}")
if error:
print(f" 错误:{error}")
return False
except Exception as e:
print(f" ❌ 验证失败:{e}")
return False
finally:
if ssh:
ssh.close()
def main():
print("=" * 60)
print(" 📦 堡垒机文件分发工具 v2.0(invoke_shell 版)")
print("=" * 60)
print(f" 堡垒机:{BASTION_HOST}")
print(f" 目标服务器:{TARGET_HOST}")
print(f" 分发文件:{os.path.basename(LOCAL_FILE)}")
print("=" * 60)
if not upload_to_bastion():
print("\n❌ 流程终止:上传到堡垒机失败")
sys.exit(1)
if not distribute_via_scp_invoke_shell():
print("\n❌ 流程终止:分发到目标服务器失败")
sys.exit(1)
if verify_deployment():
print("\n" + "=" * 60)
print(" ✅ 文件分发完成!")
print("=" * 60)
else:
print("\n" + "=" * 60)
print(" ⚠️ 文件分发完成,但验证失败")
print("=" * 60)
if __name__ == "__main__":
main()
七、常见问题与排查技巧
7.1 invoke_shell 典型问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 命令无输出 | 未发送 \n 或缓冲区未刷新 |
确认 send("cmd\n") 包含换行符 |
| 输出截断 | recv() 缓冲区不够大 |
循环读取直到 recv_ready() 返回 False |
| 提示符检测失败 | 命令提示符格式不匹配 | 使用正则表达式匹配多种格式 |
| 密码发送后仍提示 | 密码错误或发送时机不对 | 确认密码正确,检测到提示符后再发送 |
| 会话超时 | 命令执行时间过长 | 调整 settimeout() 和循环超时时间 |
7.2 调试技巧
python
# 技巧1:打印所有接收到的数据(调试用)
def debug_recv(channel, max_bytes=4096):
data = channel.recv(max_bytes).decode('utf-8', errors='ignore')
print(f"[DEBUG] 接收:{repr(data)}")
return data
# 技巧2:逐步交互(人工辅助调试)
def step_by_step(channel):
while True:
cmd = input("> ")
if cmd == 'exit':
break
channel.send(cmd + "\n")
time.sleep(1)
while channel.recv_ready():
print(channel.recv(4096).decode('utf-8', errors='ignore'))
7.3 生产环境建议
| 建议 | 说明 |
|---|---|
| 建立连接池 | 复用堡垒机连接,避免频繁创建新连接 |
| 添加重试机制 | 网络波动时自动重试 2-3 次 |
| 设置合理超时 | 根据命令复杂度动态调整超时时间 |
| 记录完整日志 | 记录每次交互的发送和接收内容 |
| 敏感信息脱敏 | 日志中不要明文记录密码 |
📝 总结与知识点一览表
核心 API 速查表
| 功能 | 核心 API | 说明 |
|---|---|---|
| 创建 SSH 客户端 | ssh = paramiko.SSHClient() |
实例化 SSH 客户端 |
| 登录堡垒机 | ssh.connect(hostname, username, password) |
建立 SSH 连接 |
| 创建交互式会话 | channel = ssh.invoke_shell() |
开启模拟终端 |
| 发送命令 | channel.send("cmd\n") |
必须添加换行符 |
| 接收输出 | channel.recv(4096) |
返回字节流,需解码 |
| 检查可读数据 | channel.recv_ready() |
返回 True 表示有数据可读 |
| 设置超时 | channel.settimeout(10) |
通道操作超时 |
| 关闭通道 | channel.close() |
释放通道资源 |
| 关闭连接 | ssh.close() |
关闭 SSH 连接 |
invoke_shell vs exec_command 完整对比
| 对比维度 | exec_command |
invoke_shell |
|---|---|---|
| 适用场景 | 单条非交互式命令 | 交互式会话、堡垒机跳转 |
| 状态保持 | ❌ 不保持 | ✅ 完整保持 |
| 交互式命令 | ❌ 不支持 | ✅ 完全支持 |
| 堡垒机跳转 | ❌ 不支持 | ✅ 支持 |
| 实现复杂度 | 低 | 高 |
| 性能开销 | 低(每次新建通道) | 较高(持久会话) |
| 异常处理 | 简单 | 复杂 |
常见错误排查指南
| 错误现象 | 大概率原因 | 解决方案 |
|---|---|---|
| 命令发送后无响应 | 忘记加 \n |
channel.send("cmd\n") |
| 密码发送失败 | 未检测到密码提示就发送 | 在循环中检测 "s password:" 后再发送 |
| 输出乱码 | 编码不匹配 | 使用 decode('utf-8', errors='ignore') |
| 提示符检测不到 | 提示符格式特殊 | 使用正则表达式匹配多种格式 |
| 会话意外关闭 | 堡垒机超时设置 | 设置 settimeout() 并定期发送 keepalive |
| 主机密钥确认卡住 | 未处理 yes/no 提示 |
在循环中检测并自动发送 "yes\n" |
学习/生产最佳实践
- 永远在命令末尾添加
\n:这是invoke_shell最常见的错误,切记! - 循环读取直到
recv_ready()返回 False:避免输出截断,一次性读取所有可用数据。 - 用正则表达式检测提示符:不要用简单的字符串包含,因为提示符格式可能变化。
- 记录完整的交互日志:便于排查问题,但注意对密码进行脱敏处理。
- 合理设置超时:不同命令的执行时间不同,为每个命令设置适当的超时。
- 复用 SSH 连接:一次登录堡垒机后,可以执行多个操作,避免重复登录。
🔜 系列结语
至此,Python 自动化运维系列(第10-15期)已全部完结!
我们完成了从运维监控到自动化操作的全链路学习:
| 期数 | 主题 | 核心能力 |
|---|---|---|
| 第10期 | 系统基础监控(psutil 上) | CPU/内存/磁盘/网络监控 |
| 第11期 | 进程监控与子进程管理(psutil 下) | 进程管理 + subprocess |
| 第12期 | IP地址规划(IPy) | 网段计算、地址转换、重叠判断 |
| 第13期 | 远程命令执行(paramiko 基础) | SSHClient + 批量巡检 |
| 第14期 | 远程文件传输(paramiko 进阶) | SFTPClient + 文件分发 |
| 第15期 | 堡垒机实战(paramiko 高阶) | invoke_shell + 堡垒机自动化 |
感谢一路的坚持与陪伴!Python 自动化运维系列是你从"会用 Python"到"用 Python 解决实际问题"的重要一步。
🔜 新系列预告:从自动化脚本到 DevOps 工具链
至此,Python 系列(基础篇 + 运维自动化篇)已全部完结。但你我的技术之旅才刚刚开始。
当我们的脚本越写越多、服务器越管越多时,你会发现:单机脚本的自动化已经不够了------我们需要标准化的 CI/CD 流水线,需要代码质量门禁,需要容器化部署,需要 Kubernetes 集群编排。
下一站,DevOps 工具链。
我已经为大家准备好了全新的系列:
在这个新系列中,我们将一起:
| 期数 | 主题 | 核心内容 |
|---|---|---|
| 第1期 | DevOps 概述与 GitLab 部署 | 从瀑布模型到敏捷开发,GitLab 安装与团队协作 |
| 第2期 | Jenkins 安装与基础配置 | CI/CD 核心引擎,插件与全局工具配置 |
| 第3期 | 开发环境搭建与 GitLab 集成 | Windows 开发环境,代码提交到自动构建 |
| 第4期 | Docker 部署与参数化构建 | 容器化部署,多版本参数化构建 |
| 第5期 | SonarQube 代码审计集成 | 代码质量门禁,自动化扫描 |
| 第6期 | Harbor 镜像仓库管理 | 私有镜像仓库,Jenkins 推送集成 |
| 第7期 | Kubernetes 部署与 DevOps 闭环 | K8s 集群部署,完整 CI/CD 闭环验证 |
从 Python 自动化运维到云原生 DevOps,这是一条自然的技术进阶路径。期待在新系列中与你再次相遇!
--- Compiled and Authored by Whisky --- July 23 rd, 2026