如何实现思科IOS更新脚本的多SSH会话并行执行?
嘿,你已经有了能用的单设备IOS更新Expect脚本,要升级成并行多会话处理的话,这些技术方向可以研究看看:
1. 基于Expect/Tcl本身实现多进程并行
其实Expect底层的Tcl语言本身就支持多进程操作,你可以用fork命令创建子进程,让每个子进程独立处理一台设备的IOS更新任务,父进程负责等待所有子进程完成。
核心思路是把你的单设备处理逻辑封装成一个过程(proc),然后遍历设备列表,为每个设备fork一个子进程去执行这个过程。示例代码片段大概是这样:
# 封装单设备处理逻辑 proc update_ios {hostname password} { set timeout 6 spawn ssh $hostname expect "TACACS*:" send "$password\r" # 这里补上你后续的IOS更新命令逻辑... expect eof } # 假设你的设备列表可以从文件读取或者作为参数传入 set device_list {{"switch01" "pass123"} {"router01" "pass456"} {"switch02" "pass789"}} # 遍历设备,创建多进程 foreach device $device_list { set pid [fork] if {$pid == 0} { # 子进程执行设备更新逻辑 update_ios [lindex $device 0] [lindex $device 1] exit } } # 父进程等待所有子进程结束 while {[wait] ne ""} {}
2. 借助Tcl的Thread扩展实现多线程并行
如果觉得多进程的资源开销有点大,你可以试试Tcl官方的Thread扩展。它允许你创建多个线程来并行处理会话,线程间的通信也比多进程更方便。
首先需要安装tcllib中的Thread包,然后就可以在脚本中初始化线程,把设备任务分配到不同线程中执行:
package require Thread proc update_ios {hostname password} { # 你的单设备Expect逻辑... } set device_list {{"switch01" "pass123"} {"router01" "pass456"}} foreach device $device_list { # 创建新线程 set thread_id [thread::create] # 向线程发送任务 thread::send $thread_id [list update_ios [lindex $device 0] [lindex $device 1]] } # 等待所有线程完成任务 thread::wait
3. 用外部工具批量并行执行现有脚本
如果你不想大改现有脚本,这个方案最省心:把你的单设备脚本改成可接收参数的独立脚本,然后用GNU Parallel这类工具来批量并行执行。
比如先把脚本调整成这样(接收hostname和password作为参数):
set timeout 6 set hostname [lindex argv 0] set password [lindex argv 1] spawn ssh $hostname expect "TACACS*:" send "$password\r" # 后续IOS更新逻辑...
然后用GNU Parallel命令批量启动:
# 假设你有一个包含设备和密码的文件devices.txt,每行格式是"hostname password" parallel ./update_ios.sh {} :::: devices.txt
这种方式完全复用你现有的核心逻辑,只需要做简单的参数适配,借助成熟的工具来管理并行会话。
4. 切换到Python生态用异步/多线程方案
如果愿意换个技术栈,Python的pexpect(Expect的Python实现)或者asyncssh(异步SSH库)配合多线程/异步IO会更灵活,生态也更丰富:
- 用
threading模块实现多线程并行,适合IO密集型的SSH会话场景; - 用
asyncio+asyncssh实现异步IO,资源利用率更高,能同时处理更多设备。
比如用asyncssh的异步示例:
import asyncio import asyncssh async def update_ios(hostname, password): async with asyncssh.connect(hostname, password=password) as conn: channel, _ = await conn.create_session(term_type='vt100') # 处理TACACS认证 await channel.expect('TACACS*:') await channel.send(password + '\n') # 执行IOS更新命令 await channel.send('copy tftp flash:\n') # 后续的交互逻辑... async def main(): devices = [("switch01", "pass123"), ("router01", "pass456")] # 创建所有任务并并行执行 tasks = [update_ios(host, pwd) for host, pwd in devices] await asyncio.gather(*tasks) if __name__ == "__main__": asyncio.run(main())
你可以根据自己的技术熟悉程度和需求来选:如果想保留Expect/Tcl技术栈,优先前两种方案;如果想快速实现,第三种最省心;如果追求长期的扩展性,Python的异步方案会是更好的选择。
内容的提问来源于stack exchange,提问作者kuroikenshi

