共计 11629 个字符,预计需要花费 30 分钟才能阅读完成。
[toc]
NAPALM 概述
官网链接
其余 drive 链接(国人)
NAPALM 全称为 Network Automation and Programmability Abstraction Layer with Multivendor support,翻译起来就是反对 ” 多厂商 ” 网络自动化和可编程的形象层.
NAPALM 是一个 python 开源的第三方模块,截至 2021.11 月,反对的厂商如下所示:
尽管反对厂商不是很多,临时没有反对国内厂商,但性能都是很弱小的。
Cisco IOS | |
Cisco NX-OS | |
Cisco IOS-XR | |
Juniper JUNOS | |
Arista EOS |
NX-API support on the Nexus 5k, 6k and 7k families was introduced in version 7.2
- 依赖 netmiko,本机装置即可.
反对的设施
通用反对模型
_ | EOS | Junos | IOS-XR (NETCONF) | IOS-XR (XML-Agent) | NX-OS | NX-OS SSH | IOS |
---|---|---|---|---|---|---|---|
Driver Name | eos | junos | iosxr_netconf | iosxr | nxos | nxos_ssh | ios |
Structured data | Yes | Yes | Yes | No | Yes | No | No |
Minimum version | 4.15.0F | 12.1 | 7.0 | 5.1.0 | 6.1 [1] | 12.4(20)T | 6.3.2 |
Backend library | pyeapi | junos-eznc | ncclient | pyIOSXR | pynxos | netmiko | netmiko |
Caveats | EOS | IOS-XR (NETCONF) | NXOS | NXOS | IOS |
阐明:
- driver name:前面咱们写代码须要填写的, 如
get_network_driver(ios)
- structured data:反对的结构化数据
- Minimum version:这里还有最低的版本要求;
配置反对模型
反对配置替换、合并、提交、比照、原子配置、回滚等。
_ | EOS | Junos | IOS-XR (NETCONF) | IOS-XR (XML-Agent) | NX-OS | IOS |
---|---|---|---|---|---|---|
Config. replace | Yes | Yes | Yes | Yes | Yes | Yes |
Config. merge | Yes | Yes | Yes | Yes | Yes | Yes |
Commit Confirm | Yes | Yes | No | No | No | No |
Compare config | Yes | Yes | Yes | Yes [2] | Yes [4] | Yes |
Atomic Changes | Yes | Yes | Yes | Yes | Yes/No [5] | Yes/No [5] |
Rollback | Yes [3] | Yes | Yes | Yes | Yes/No [5] | Yes |
[2] Hand-crafted by the API as the device doesn’t support the feature.
[3] Not supported but emulated. Check caveats.
[4] For merges, the diff is very simplistic. See caveats.
[5] (1, 2, 3) No for merges. See caveats.
[6] NAPALM requires Junos OS >= 14.1 for commit-confirm functionality.
Getters 反对的模型
该表官网会不定期自动更新。
EOS | IOS | IOSXR | IOSXR_NETCONF | JUNOS | NXOS | NXOS_SSH | |
---|---|---|---|---|---|---|---|
get_arp_table | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
get_bgp_config | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
get_bgp_neighbors | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
get_bgp_neighbors_detail | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
get_config | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
get_environment | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
get_facts | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
get_firewall_policies | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
get_interfaces | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
get_interfaces_counters | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
get_interfaces_ip | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
get_ipv6_neighbors_table | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
get_lldp_neighbors | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
get_lldp_neighbors_detail | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
get_mac_address_table | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
get_network_instances | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ |
get_ntp_peers | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
get_ntp_servers | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
get_ntp_stats | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
get_optics | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ |
get_probes_config | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
get_probes_results | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
get_route_to | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
get_snmp_information | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
get_users | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
get_vlans | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ |
is_alive | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
ping | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ |
traceroute | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
- ✅ – supported
- ❌ – not supported
- ☠ – broken
通过 Getters
类,比方 get_config
就能够失去设施的配置 (running 和 startup)、get_facts
获取设施的根本信息、get_interfaces
获取接口信息等等,也是十分不便的。
其余办法
_ | EOS | Junos | IOS-XR (NETCONF) | IOS-XR | NX-OS | IOS |
---|---|---|---|---|---|---|
load_template | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
ping | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
traceroute | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
可用的配置模板
set_hostname
(JunOS, IOS-XR, IOS) – Configures the hostname of the device.set_ntp_peers
(JunOS, IOS-XR, EOS, NXOS, IOS) – Configures NTP peers of the device.delete_ntp_peers
(JunOS, IOS-XR, EOS, NXOS, IOS): Removes NTP peers from device’s configuration.set_probes
(JunOS, IOS-XR): Configures RPM/SLA probes.schedule_probes
(IOS-XR): On Cisco devices, after defining the SLA probes, it is mandatory to schedule them. Defined also for JunOS as empty template, for consistency reasons.delete_probes
(JunOS, IOS-XR): Removes RPM/SLA probes.
以上办法,大家能够在试验环境测试下。
如何装置
须要确保 netmiko 曾经被装置,依赖与 netmiko 的。
- 通过 pip3 装置
python -m pip install napalm
备注:从版本 3.0.0 之后,NAPALM 仅反对 Python 3.6+.
如何降级
pip install napalm -U
备注:如果 napalm 有降级了,能够通过该办法进行降级.
实操示例
视频中演示的代码,都在这里了.
根本的设施连贯
from napalm import get_network_driver | |
import pprint | |
pp = pprint.PrettyPrinter(indent=2) | |
# drive 是一个类 class,传入 drive_name | |
drive = get_network_driver('ios') | |
# 类实例化 | |
conn = drive(hostname='192.168.0.20', | |
username='cisco', | |
password='cisco', | |
optional_args={'port': 22}) | |
# 建设连贯会话 | |
conn.open() | |
# 应用其中一个办法,获取接口 ip 地址 | |
ouput = conn.get_interfaces_ip() | |
# 打印后果 | |
pp.pprint(ouput) | |
# 敞开连贯 | |
conn.close() |
执行脚本后,后果如下所示:
备份配置
from napalm import get_network_driver | |
import pprint | |
import os | |
pp = pprint.PrettyPrinter(indent=2) | |
host = "192.168.0.20" | |
# drive 是一个类 class | |
drive = get_network_driver('ios') | |
# 类实例化 | |
conn = drive(hostname= host, | |
username= 'cisco', | |
password= 'cisco', | |
optional_args= {'port': 22}) | |
# 建设连贯会话 | |
conn.open() | |
# 获取设施配置信息 | |
output = conn.get_config() | |
# 打印后果 | |
pp.pprint(output) | |
running_config = output['running'] | |
# 写入文件 | |
with open(os.path.join('LOG', f'{host}-running.conf'), 'w+') as f: | |
f.writelines(running_config) | |
# 敞开连贯 | |
conn.close() |
执行完脚本,后果如下所示:
默认蕴含了 3 个备份配置:
- running 配置:就是以后运行配置.
- startup 配置:就是启动配置了.
- candidate 配置,这个个别为空.
这里,我保留了 running.config
配置文件.
获取设施根底信息
# drive 是一个类 class | |
drive = get_network_driver('ios') | |
# 类实例化 | |
conn = drive(hostname='192.168.0.20', | |
username='cisco', | |
password='cisco', | |
optional_args={'port': 22}) | |
# 建设连贯会话 | |
conn.open() | |
# 获取接口 ip 地址 | |
output = conn.get_facts() | |
# 打印后果 | |
pp.pprint(ouput) | |
# 敞开连贯 | |
conn.close() |
执行脚本后,后果如下所示:
get_facts()
办法采集了如下字段:
- 域名、主机名称、接口列表、型号、版本号、序列号、运行工夫、厂商.
发送命令
# drive 是一个类 class | |
drive = get_network_driver('ios') | |
# 类实例化 | |
conn = drive(hostname='192.168.0.20', | |
username='cisco', | |
password='cisco', | |
optional_args={'port': 22, 'secret': 'cisco'}) | |
# 建设连贯会话 | |
conn.open() | |
# 发送命令 | |
output = conn.cli(['show ip int brief', 'show arp']) | |
pp.pprint(output) | |
# 敞开连贯 | |
conn.close() |
执行脚本后,后果如下所示:
阐明:这里要注意下如果用户权限不够或者须要进入 enable
的,须要 optional_args
减少 secret
参数,表明须要进入特权模式了.
如果执行的命令权限不容许,就会报如下谬误:
ValueError: Failed to enter enable mode. Please ensure you pass the 'secret' argument to ConnectHandler.
配置类
通过 scp 协定下发配置,它不须要 enable 明码.
前提条件:
- 设施要开启
scp
服务,ip scp server enable
. -
开启
archive
服务,备份配置到本地 flash 上;archive path flash:R1.conf write-memory
合并配置
阐明:
- load_merge_candidate():加载配置文件.
- commit_config():提交配置.
-
revert_in=30:如果平台反对,能够设置工夫挂起配置提交,超时没 commit 就复原配置。
根底玩法
模板文件门路:
templates\ios_logging_config.cfg
logging host 10.1.1.2
from napalm import get_network_driver | |
import pprint | |
pp = pprint.PrettyPrinter(indent=2) | |
# drive 是一个类 class | |
drive = get_network_driver('ios') | |
# 类实例化 | |
conn = drive(hostname='192.168.0.20', | |
username='cisco', | |
password='cisco', | |
optional_args={'port': 22}) | |
# 建设连贯会话 | |
conn.open() | |
try: | |
conn.load_merge_candidate(filename='templates/ios_logging_config.cfg') | |
conn.commit_config() | |
except Exception as e: | |
print(e) |
如果设施上没开启 scp
服务就会报错:
SCP file transfers are not enabled. Configure 'ip scp server enable' on the device.
高级一点玩法:
模板文件门路:templates\ios_logging_config.cfg
def merge_config(vendor, devices, template_file): | |
try: | |
# drive 是一个类 class | |
drive = get_network_driver(vendor) | |
# 类实例化 | |
conn = drive(**devices) | |
# 建设连贯会话 | |
conn.open() | |
except Exception as e: | |
print("连贯谬误:{}".format(e)) | |
return | |
try: | |
conn.load_merge_candidate(filename=template_file) | |
new_config = conn.compare_config() | |
if new_config: | |
print('预推送的配置如下:') | |
print('='*80) | |
print(new_config) | |
print('=' * 80) | |
choice = input("你要开始推送这些配置嘛, [Y/N]:") | |
if choice.lower() == 'y': | |
print('开始提交配置,请稍后...') | |
conn.commit_config() | |
rollback = input("是否须要回滚配置,输出 Y:回滚,输出 N,不回滚. [Y/N]") | |
if rollback.lower() == 'y': | |
print('开始回滚配置,请稍后...') | |
conn.rollback() | |
print('配置已回滚,请查看配置.') | |
else: | |
print('配置曾经下发胜利.') | |
else: | |
conn.discard_config() | |
print('本次预配置没有推送.') | |
else: | |
print('无需反复配置.') | |
except Exception as e: | |
print(e) | |
finally: | |
conn.close() | |
if __name__ == '__main__': | |
vendor = 'ios' | |
devices = {'hostname': '192.168.0.20', | |
'username': 'cisco', | |
'password': 'cisco', | |
'optional_args':{'port': 22} | |
} | |
tmp = 'templates/ios_logging_config.cfg' | |
merge_config(vendor, devices, template_file=tmp) |
配置替换
办法:load_replace_candidate()
def replace_config(vendor, devices, template_file): | |
try: | |
# drive 是一个类 class | |
drive = get_network_driver(vendor) | |
# 类实例化 | |
conn = drive(**devices) | |
# 建设连贯会话 | |
conn.open() | |
except Exception as e: | |
print("连贯谬误:{}".format(e)) | |
return | |
try: | |
if not (os.path.exists(template_file) and os.path.isfile(template_file)): | |
msg = '文件不存在或文件类型不可用.' | |
raise ValueError(msg) | |
print("开始加载候选替换配置...") | |
# 这个还未加载到设施上的 | |
conn.load_replace_candidate(template_file) | |
# 配置比拟 | |
print("\n 预览配置比照:") | |
print(">"*80) | |
compare_result = conn.compare_config() | |
print(compare_result) | |
print(">" * 80) | |
# You can commit or discard the candidate changes. | |
if compare_result: | |
try: | |
choice = input("\nWould you like to commit these changes? [yN]:").lower() | |
except NameError: | |
choice = input("\nWould you like to commit these changes? [yN]:").lower() | |
if choice == "y": | |
print("Committing ...") | |
conn.commit_config() | |
else: | |
print("Discarding ...") | |
conn.discard_config() | |
else: | |
print('没有新的配置.') | |
conn.discard_config() | |
conn.close() | |
print("Done...") | |
except Exception as e: | |
print(e) | |
if __name__ == '__main__': | |
vendor = 'ios' | |
devices = {'hostname': '192.168.0.20', | |
'username': 'cisco', | |
'password': 'cisco', | |
'optional_args':{'port': 22} | |
} | |
tmp = 'LOG/192.168.0.20-running.conf' | |
replace_config(vendor, devices, template_file=tmp) |
阐明:这里我用思科的设施,留神的是,如果 running
的配置里应用了banner
,我是都提前把它删除了,不然会报错,这块我临时没测试进去,如果哪位小伙伴试出来了,请告知我,谢谢。
Jinja2 模板
-
装置 jinja2
python -m pip install jinja2
-
jiaja2 模板配置
模板文件门路:templates/ssh-acl.tpl
{% for host in hosts -%} access-list 20 permit host {{host}} {% endfor -%} line vty 0 4 access-class 20 in -
下发配置
from napalm import get_network_driver from jinja2 import FileSystemLoader, Environment import pprint import logging pp = pprint.PrettyPrinter(indent=2) def connect_device(vendor, devices, hosts): try: # drive 是一个类 class drive = get_network_driver(vendor) # 类实例化 conn = drive(**devices) # 建设连贯会话 conn.open() except Exception as e: print("连贯谬误:{}".format(e)) return try: loader = FileSystemLoader('templates') env = Environment(loader=loader) tpl = env.get_template('ssh-acl.tpl') config_tpl = tpl.render({'hosts': hosts}) # print(config_tpl) conn.load_merge_candidate(config=config_tpl) conn.commit_config() except Exception as e: print(e) finally: conn.close() if __name__ == '__main__': vendor = 'ios' devices = {'hostname': '192.168.0.20', 'username': 'cisco', 'password': 'cisco', 'optional_args':{'secret': 'cisco', 'port': 22} # 如果 enable 须要明码,退出 'secret' 参数 } hosts = ['192.168.0.1', '192.168.0.254'] connect_device(vendor, devices, hosts) 这里能够打印下
config_tpl
, 看看下发的配置是啥:# print(config_tpl)的输入后果如下所示:access-list 20 permit host 192.168.0.1 access-list 20 permit host 192.168.0.254 line vty 0 4 access-class 20 in
配置更改回滚
conn.rollback()
备注:见后面章节演示.
验证部署
验证设施的状态是否是你冀望的.
-
定义一个 yaml 文件,定义预期想要的状态
模板文件门路:
templates/napalm_t.yaml
--- - get_facts: 'serial_number': '99CBK1M35C217V5Z7ABWQ' - get_interfaces_ip: GigabitEthernet0/0: ipv4: 192.168.0.20 -
compliance_report 办法
import pprint from napalm import get_network_driver pp = pprint.PrettyPrinter(indent=2) def connect_device(vendor, devices, hosts): try: # drive 是一个类 class drive = get_network_driver(vendor) # 类实例化 conn = drive(**devices) # 建设连贯会话 conn.open() except Exception as e: print("连贯谬误:{}".format(e)) return try: out = conn.compliance_report('templates/napalm_t.yaml') pp.pprint(out) except Exception as e: print(e) finally: conn.close() if __name__ == '__main__': vendor = 'ios' devices = {'hostname': '192.168.0.20', 'username': 'cisco', 'password': 'cisco', 'optional_args':{'port': 22} } hosts = ['192.168.0.1', '192.168.0.254'] connect_device(vendor, devices, hosts) 执行后果如下所示:
{ 'complies': True, 'get_facts': { 'complies': True, 'extra': [], 'missing': [], 'present': { 'serial_number': { 'complies': True, 'nested': False}}}, 'get_interfaces_ip': { 'complies': True, 'extra': [], 'missing': [], 'present': { 'GigabitEthernet0/0': { 'complies': True, 'nested': True}}}, 'skipped': []} 阐明:
complies
的 value 值为True
阐明失常,如果False
示意和预期构想不太统一。
反对上下文治理
通过上下文治理就不须要调用 open()和 close()办法了.
# 类实例化 | |
drive = get_network_driver('ios') | |
# with 上下文治理 | |
with drive(hostname='192.168.0.20',username='cisco',password='cisco',optional_args={'port': 22}) as conn: | |
output = conn.get_facts() | |
# 打印后果 | |
pp.pprint(ouput) |
ping 办法
默认格局:ping x.x.x.x timeout 2 size 100 repeat 5
#!/usr/bin/env python3 | |
#-*- coding:UTF-8 -*- | |
from napalm import get_network_driver | |
import pprint | |
import logging | |
# logging.basicConfig(filename='debug.log', level=logging.DEBUG) | |
# logger = logging.getLogger('napalm') | |
pp = pprint.PrettyPrinter(indent=2) | |
def connect_device(vendor, devices, dst_ip): | |
try: | |
# drive 是一个类 class | |
drive = get_network_driver(vendor) | |
# 类实例化 | |
conn = drive(**devices) | |
# 建设连贯会话 | |
conn.open() | |
except Exception as e: | |
print("连贯谬误:{}".format(e)) | |
return | |
try: | |
for ip in dst_ip: | |
output = conn.ping(ip) | |
pp.pprint(output) | |
except Exception as e: | |
print(e) | |
finally: | |
conn.close() | |
if __name__ == '__main__': | |
vendor = 'ios' | |
devices = {'hostname': '192.168.0.20', | |
'username': 'cisco', | |
'password': 'cisco', | |
'optional_args':{'port': 22} | |
} | |
dst_ip = ['192.168.0.19', '192.168.0.20'] | |
connect_device(vendor, devices, dst_ip) |
回显的后果:
{ 'success': { 'packet_loss': 0, | |
'probes_sent': 5, | |
'results': [{'ip_address': '192.168.0.19', 'rtt': 0.0}, | |
{'ip_address': '192.168.0.19', 'rtt': 0.0}, | |
{'ip_address': '192.168.0.19', 'rtt': 0.0}, | |
{'ip_address': '192.168.0.19', 'rtt': 0.0}, | |
{'ip_address': '192.168.0.19', 'rtt': 0.0}], | |
'rtt_avg': 2.0, | |
'rtt_max': 3.0, | |
'rtt_min': 2.0, | |
'rtt_stddev': 0.0}} | |
{ 'success': { 'packet_loss': 0, | |
'probes_sent': 5, | |
'results': [{'ip_address': '192.168.0.20', 'rtt': 0.0}, | |
{'ip_address': '192.168.0.20', 'rtt': 0.0}, | |
{'ip_address': '192.168.0.20', 'rtt': 0.0}, | |
{'ip_address': '192.168.0.20', 'rtt': 0.0}, | |
{'ip_address': '192.168.0.20', 'rtt': 0.0}], | |
'rtt_avg': 1.0, | |
'rtt_max': 2.0, | |
'rtt_min': 1.0, | |
'rtt_stddev': 0.0}} |
好了,文章的解说就到此结束了,各个性能曾经给大家演示了,各位小伙伴如实操一遍,置信就能把握了并依据需要进行改写了。
如果须要更具体的信息,请挪动官网进行查阅。
别忘了,动动手分享下,点击、珍藏、三连击! 哈哈 …
如果喜爱的我的文章,欢送关注我的公众号:点滴技术,扫码关注,不定期分享