
本教程详细介绍了如何使用python的netmiko库实现cisco路由器的ssh连接与自动化配置。文章核心在于纠正配置命令中常见的`enable`和`config t`冗余问题,并演示了如何正确配置loopback接口、物理接口以及ospf协议。同时,教程还涵盖了配置保存、差异比较、错误处理和日志记录等专业实践,旨在帮助网络工程师高效、可靠地管理cisco设备。
在现代网络管理中,自动化是提升效率和减少人为错误的关键。Netmiko是一个强大的Python库,它基于Paramiko构建,专门用于简化与网络设备的SSH/Telnet连接和配置管理。它抽象了底层交互细节,使得工程师可以通过编写Python脚本轻松地向路由器、交换机等设备发送命令、获取输出、并进行配置更改。本教程将聚焦于如何利用Netmiko正确且高效地配置Cisco路由器,包括建立SSH连接、配置接口、启用路由协议以及进行配置管理。
Netmiko支持通过SSH或Telnet连接到网络设备。为了确保安全和效率,推荐使用SSH。连接时,需要提供设备的类型、主机地址、认证凭据以及可选的端口和超时时间。
一个典型的设备字典包含以下关键参数:
import getpass
import logging
from netmiko import ConnectHandler
# 配置日志记录
logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(name)s:%(message)s')
def get_device_info():
"""获取用户输入的设备连接信息"""
host = input('请输入路由器IP地址: ')
username = input('请输入用户名: ')
password = getpass.getpass('请输入密码: ')
secret = getpass.getpass('请输入特权模式密码 (secret): ')
while True:
choice = input('请选择连接方式 (telnet/ssh): ').lower()
if choice == 'telnet':
device_type = 'cisco_ios_telnet'
port = 23
break
elif choice == 'ssh':
device_type = 'cisco_ios'
port = 22
break
else:
logging.error('无效选择!请选择 telnet 或 ssh。')
return {
'device_type': device_type,
'host': host,
'username': username,
'password': password,
'secret': secret,
'port': port,
'timeout': 60, # 默认超时时间设为60秒
}
使用ConnectHandler建立连接,推荐使用with语句,它能确保连接在代码块执行完毕后自动关闭,即使发生异常也能妥善处理。
def main():
device = get_device_info()
net_connect = None # 初始化net_connect,防止在finally块中引用未定义变量
try:
logging.info(f"尝试连接设备 {device['host']}...")
with ConnectHandler(**device) as net_connect:
logging.info('连接成功建立!')
# 在这里调用配置函数
configure_device(net_connect)
# 执行配置管理和验证
manage_and_verify_config(net_connect)
except Exception as e:
logging.error(f'发生错误: {e}')
finally:
logging.info('连接已结束。')
# 使用with语句时,Netmiko会自动断开连接,无需显式调用net_connect.disconnect()
# 如果不使用with语句,则需要在finally块中调用net_connect.disconnect()
核心要点:Netmiko的send_config_set()方法会自动处理进入和退出配置模式的命令(enable和config t)。
许多初学者在使用Netmiko进行配置时,会错误地在配置命令列表中包含en(或enable)和conf t(或configure terminal)。这会导致命令发送失败或连接超时,因为Netmiko已经默认处理了这些步骤。
例如,原始代码中包含:
loopback_config = [
'en\n'
'conf t\n'
'interface Loopback0\n',
'ip address 192.168.57.101 255.255.255.0\n',
'exit\n'
]这里的'en\n'和'conf t\n'是冗余且错误的。send_config_set()方法在发送配置命令前,会自动将设备切换到全局配置模式,并在所有命令发送完毕后返回特权模式。因此,正确的做法是直接提供配置命令。
下面我们将演示如何使用Netmiko配置Loopback接口、物理接口、访问控制列表(ACL)以及OSPF路由协议。
将所有配置命令组织成一个列表。Netmiko的send_config_set()方法会逐行发送这些命令。
def configure_device(net_connect):
"""
向设备发送配置命令。
Netmiko会自动处理进入和退出配置模式。
"""
logging.info('开始配置设备...')
# Loopback接口配置
loopback_config = [
'interface Loopback0',
'ip address 192.168.57.101 255.255.255.0',
'no shutdown', # 确保接口启用
]
# 物理接口配置
interface0_config = [
'interface GigabitEthernet0/0',
'ip address 192.168.58.101 255.255.255.0',
'no shutdown',
]
interface1_config = [
'interface GigabitEthernet0/1',
'ip address 192.168.59.101 255.255.255.0',
'no shutdown',
]
# OSPF协议配置 (根据问题要求添加)
# 假设Router ID为192.168.57.101,并宣告Loopback0和GigabitEthernet0/0/1接口所在的网络
ospf_config = [
'router ospf 1', # 进程ID为1
'router-id 192.168.57.101',
'network 192.168.57.0 0.0.0.255 area 0', # 宣告Loopback0所在的网络
'network 192.168.58.0 0.0.0.255 area 0', # 宣告GigabitEthernet0/0所在的网络
'network 192.168.59.0 0.0.0.255 area 0', # 宣告GigabitEthernet0/1所在的网络
]
# 访问控制列表 (ACL) 配置
acl_config = [
'ip access-list extended MY_ACL',
'permit ip 192.168.56.130 0.0.0.255 any', # 注意这里子网掩码应为反向掩码
'deny ip any any',
]
# 合并所有配置命令
configs_to_send = loopback_config + interface0_config + interface1_config + ospf_config + acl_config
try:
output = net_connect.send_config_set(configs_to_send)
logging.info('配置命令发送完成,输出如下:\n%s', output)
except Exception as e:
logging.error(f'发送配置命令时发生错误: {e}')
注意事项:
自动化配置不仅包括发送命令,还包括获取设备当前状态、保存配置以及比较配置差异,以确保配置的正确性。
通过send_command()方法可以执行设备的特权模式命令,例如show running-config。
def save_config_to_file(config_content, filename):
"""将配置内容保存到本地文件"""
try:
with open(filename, 'w') as config_file:
config_file.write(config_content)
logging.info(f'配置已保存到文件: {filename}')
except IOError as e:
logging.error(f'保存配置到文件 {filename} 失败: {e}')
difflib模块可以用来比较两个字符串或文件内容的差异,这对于验证配置更改是否按预期应用非常有用。
import difflib
def show_differences(config1, config2, label1="旧配置", label2="新配置"):
"""显示两个配置字符串之间的差异"""
difference = difflib.Differ()
diff = list(difference.compare(config1.splitlines(), config2.splitlines()))
logging.warning(f'以下是 {label1} 与 {label2} 的差异:')
for line in diff:
if line.startswith('- '):
logging.warning(f'仅在 {label1} 中: {line[2:]}')
elif line.startswith('+ '):
logging.warning(f'仅在 {label2} 中: {line[2:]}')
elif line.startswith('? '):
# 差异行通常伴随'?'行,可以忽略或用于更详细分析
pass
else:
# logging.info(f'相同: {line[2:]}') # 如果需要显示相同行
pass
def manage_and_verify_config(net_connect):
"""管理和验证设备配置"""
logging.info('正在获取设备运行配置...')
running_configuration = net_connect.send_command('show running-config')
if running_configuration:
local_config_file_name = f"{net_connect.host}_running_config.txt"
save_config_to_file(running_configuration, local_config_file_name)
# 假设我们有一个基线配置文件用于比较
# 这里为了演示,我们假设刚保存的配置就是“旧配置”
# 实际应用中,你可能从版本控制系统或预定义文件中加载
# 模拟一个“新的”或“修改后”的配置,这里简化为再次获取运行配置
# 实际应用中,可能是在进行其他配置后再次获取或加载一个预期配置
logging.info('模拟获取第二次配置进行比较...')
second_running_config = net_connect.send_command('show running-config') # 再次获取,理论上应该相同
if running_configuration and second_running_config:
if running_configuration == second_running_config:
logging.info('运行配置与第二次获取的配置一致。')
else:
logging.warning('运行配置与第二次获取的配置不一致!')
show_differences(running_configuration, second_running_config,
label1="首次获取配置", label2="第二次获取配置")
else:
logging.error('未能获取到有效的配置进行比较。')
else:
logging.error('未能从设备获取运行配置。')
健壮的自动化脚本需要有效的错误处理和遵循最佳实践。
使用try-except块捕获可能发生的连接或配置错误,例如连接超时、认证失败等,并提供有用的错误信息。
timeout参数在device字典中非常重要。如果设备响应缓慢或网络延迟高,增加超时时间可以防止连接过早断开。
使用Python的logging模块而非简单的print语句来记录脚本的执行过程、警告和错误信息。这有助于调试和跟踪自动化任务。
当使用with ConnectHandler(...) as net_connect:语句时,Netmiko会在代码块结束后自动断开连接,无需手动调用net_connect.disconnect()。如果未使用with语句,则务必在finally块中显式调用net_connect.disconnect(),以确保连接被正确关闭。
import getpass
import logging
import difflib
from netmiko import ConnectHandler
# 配置日志记录
logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(name)s:%(message)s')
def save_config_to_file(config_content, filename):
"""将配置内容保存到本地文件"""
try:
with open(filename, 'w') as config_file:
config_file.write(config_content)
logging.info(f'配置已保存到文件: {filename}')
except IOError as e:
logging.error(f'保存配置到文件 {filename} 失败: {e}')
def show_differences(config1, config2, label1="配置A", label2="配置B"):
"""显示两个配置字符串之间的差异"""
difference = difflib.Differ()
diff = list(difference.compare(config1.splitlines(), config2.splitlines()))
has_diff = False
for line in diff:
if line.startswith('- ') or line.startswith('+ '):
has_diff = True
break
if has_diff:
logging.warning(f'以下是 {label1} 与 {label2} 的差异:')
for line in diff:
if line.startswith('- '):
logging.warning(f'仅在 {label1} 中: {line[2:]}')
elif line.startswith('+ '):
logging.warning(f'仅在 {label2} 中: {line[2:]}')
else:
logging.info(f'{label1} 与 {label2} 没有差异。')
def configure_device(net_connect):
"""
向设备发送配置命令。
Netmiko会自动处理进入和退出配置模式。
"""
logging.info('开始配置设备...')
# Loopback接口配置
loopback_config = [
'interface Loopback0',
'ip address 192.168.57.101 255.255.255.0',
'no shutdown',
]
# 物理接口配置
interface0_config = [
'interface GigabitEthernet0/0',
'ip address 192.168.58.101 255.255.255.0',
'no shutdown',
]
interface1_config = [
'interface GigabitEthernet0/1',
'ip address 192.168.59.101 255.255.255.0',
'no shutdown',
]
# OSPF协议配置
ospf_config = [
'router ospf 1',
'router-id 192.168.57.101',
'network 192.168.57.0 0.0.0.255 area 0',
'network 192.168.58.0 0.0.0.255 area 0',
'network 192.168.59.0 0.0.0.255 area 0',
]
# 访问控制列表 (ACL) 配置
acl_config = [
'ip access-list extended MY_ACL',
'permit ip 192.168.56.130 0.0.0.255 any', # 注意反向掩码
'deny ip any any',
]
# 合并所有配置命令
configs_to_send = loopback_config + interface0_config + interface1_config + ospf_config + acl_config
try:
output = net_connect.send_config_set(configs_to_send)
logging.info('配置命令发送完成,输出如下:\n%s', output)
except Exception as e:
logging.error(f'发送配置命令时发生错误: {e}')
raise # 重新抛出异常,以便主函数捕获
def manage_and_verify_config(net_connect):
"""管理和验证设备配置"""
logging.info('正在获取设备运行配置...')
running_configuration = net_connect.send_command('show running-config')
if running_configuration:
local_config_file_name = f"{net_connect.host}_running_config.txt"
save_config_to_file(running_configuration, local_config_file_name)
# 假设我们有一个基线配置文件用于比较,这里为了演示,我们再次获取
# 实际应用中,可以从文件加载一个期望的配置
logging.info('再次获取运行配置进行比较...')
second_running_config = net_connect.send_command('show running-config')
if running_configuration and second_running_config:
show_differences(running_configuration, second_running_config,
label1="首次获取配置", label2="第二次获取配置")
else:
logging.error('未能获取到有效的配置进行比较。')
else:
logging.error('未能从设备获取运行配置。')
def get_device_info():
"""获取用户输入的设备连接信息"""
host = input('请输入路由器IP地址: ')
username = input('请输入用户名: ')
password = getpass.getpass('请输入密码: ')
secret = getpass.getpass('请输入特权模式密码 (secret): ')
while True:
choice = input('请选择连接方式 (telnet/ssh): ').lower()
if choice == 'telnet':
device_type = 'cisco_ios_telnet'
port = 23
break
elif choice == 'ssh':
device_type = 'cisco_ios'
port = 22
break
else:
logging.error('无效选择!请选择 telnet 或 ssh。')
return {
'device_type': device_type,
'host': host,
'username': username,
'password': password,
'secret': secret,
'port': port,
'timeout': 100, # 增加超时时间以适应Netlab环境
}
def main():
device = get_device_info()
try:
logging.info(f"尝试连接设备 {device['host']}...")
with ConnectHandler(**device) as net_connect:
logging.info('连接成功建立!')
# 发送配置命令
configure_device(net_connect)
# 执行配置管理和验证
manage_and_verify_config(net_connect)
except Exception as e:
logging.error(f'发生错误: {e}')
finally:
logging.info('连接已结束。')
if __name__ == "__以上就是Netmiko自动化Cisco路由器配置:SSH连接、接口与OSPF协议实践的详细内容,更多请关注php中文网其它相关文章!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号