NAPALM(1):入門

1. 背景介紹

曾經調試過千臺設備,也敲過萬行代碼,當年無論大小割接,絕不提前準備腳本,都是現場憑藉比跳egg頻率還高的手速敲擊命令;最近發現隨着年紀的增加越來越不想幹活,哪怕修改一臺設備接口描述也會將配置在txt中寫好,再粘貼下發。遂在閒暇之餘研究起了網絡自動化。網絡自動化沒有一個具體的概念,他是一個體系框架,在這框架內可以極高的解放生產力,淘汰CCNA Level Engineer。換句話說可以實現高頻率重複性無腦操作的自動化。但正正意義上的網絡自動化內容遠不止於此;本系列文章所涉及的網絡自動化,均是將高頻率重複性無腦操作的自動化視爲網絡自動化。本人學藝不精,不能通過一篇文章全面的描述NAPALM的使用,需要不斷學習、吸收再分享,故本文爲系列性文章,不定期更新。
下面我先簡單介紹下NAPALM應用場景:

首先,讓我們假想一個場景:

由於業務發生變更,需要爲一個 POD 裏面的幾十臺交換機修改 QoS 配置。作爲網絡運維人員,應該怎樣處理這項工作呢?

如果需要變更的對象是整個數據中心幾百臺甚至幾千臺交換機,又該怎樣處理這項工作呢?

當下,互聯網行業已經普遍採用 DevOps 的體系流程。靠人力去一臺設備一臺設備的更改配置,已經不再是正確的思維方式。原因不僅僅是浪費時間 —— 要知道,人如果要長時間保持注意力集中,大腦需要耗費大量的能量,很難保證不出現遺漏或者錯誤。而機器卻不會。

因此,正確的方法是利用 DevOps 的流程,讓機器來完成這項工作。例如採用基於 Python 的 SSH 庫 Paramiko 或 Netmiko,以及 Ansible 或 SaltStack 等自動化工具編寫運維腳本。

Netmiko 庫和 Ansible 等運維工具雖然可以通過程序化的腳本對網絡設備實現批量管理,但仍然需要運維工程師對網絡設備的 CLI 很熟悉,預先在腳本中建立需要被執行的 Command 列表。因此運維人員離不開以下兩個體系分別是CLI和SNMP協議:

  1. CLI:命令行工具,不同廠商之間、同廠商不同版本、不同設備之間命令語法均可能不一樣;這造成運維工程師學習成本的增加。同時設備版本升級後,配置也有不兼容的情況。
  2. SNMP:SNMP Agent 會維護一個 MIB(管理信息庫),裏面保存着大量的 OID (對象標識符)。一個 OID 是一對唯一的 Key-Value,SNMP Manager 向 SNMP Agent 查詢或修改若干 Key 所對應的 Value,就可以實現信息採集或者網絡設備的配置修改。SNMP也存在以下問題:
    1. 太古老,性能不好。基於 UDP 協議傳輸,比較不可靠。雖然在應用層有 Response 機制保證丟包之後的重複 get/ set,但代價就是性能和運行時間都受到影響
    2. 致命的問題是,各廠商都大量的使用私有 MIB,卻不存在一個可以自動發現網絡設備當前所採用的 MIB 的機制。網絡運維人員必須分別向設備廠商索取網絡設備的 MIB,耗費大量的時間整理自己需要的 OID,再手工導入到自動化運維平臺或者腳本當中。

所以 SNMP 和CLI只適合用來做信息採集,提供告警和可視化報表,但自動化運維的 API 則需要考慮其他的選項。站在網絡運維人員的角度,這個 API 到底什麼樣的?其應該具有以下特點:

  1. 容易使用 —— Usability 是所有產品的核心價值

  2. 需要能夠清晰地區分“配置數據”,“設備運行狀態數據”和“統計數據”

  3. 需要能夠分別從各個網絡設備獲取上述 3 種數據,並且可以方便地對比不同設備的數據

  4. 可以讓網絡運維人員統一地管理整個網絡的所有設備,而不是一臺一臺的單獨管理

  5. 對不同廠商的設備都能夠使用同一種配置方法

  6. 配置變更對網絡業務的影響要儘可能的小

  7. 能夠提供一個標準化的,對設備 Pulling 和 Pushing 配置文件的流程,以滿足對設備配置的備份和恢復的業務需求

  8. 能夠很方便地,持續地,檢查設備配置文件的一致性

  9. 能夠提供基於文本的配置方式,並且不會導致配置的亂序,例如不能攪亂 ACL 規則的順序

目前能夠滿足這些要求的網絡設備的北向 API 接口就是 Netconf,但是於很多廠商雖然支持 Netconf,但有一些 Key-Value 卻存在差異。比如爲了表達“端口”,有些廠商用 intf 作爲 Key,但另外一些廠商卻用 interface 作爲 Key。另一個例子就是 Uptime,設備運行時間,各家廠商的設備返回的時間格式更是五花八門。這爲網絡運維人員處理數據的工作造成了很大的麻煩,不得不耗費大量的時間和精力去閱讀設備廠商的 Netconf 文檔,去編寫大量的正則表達式。

還有,雖然主流的 SDN Controller 的南向接口都支持 Netconf,但是在實際部署時,卻無法用單一的 Controller 去控制多廠商的網絡設備。通常都是各個廠商使用自己的 SDN Controller 控制自己的設備,然後再用 REST API 與用戶的 SDN Controller 對接。故NAPALM出現在我們面前:

NAPALM 是一個 Python 庫,它的全稱是 Network Automation and Programmability Abstraction Layer with Multivendor support,多廠商支持的網絡自動化和可編程抽象層。

目前 Ansible 集成了 3 個 NAPALM 模塊,分別是:

  • napalm_parse_yang:用於從設備或文件中解析配置/狀態數據

  • napalm_diff_yang:用於比較 2 個 YANG 對象的差異

  • napalm_translate_yang:用於將 YANG 對象轉譯成設備原始的配置

從設備取出原始配置數據/狀態數據之後,可以使用 NAPALM 將其翻譯成標準格式的 NAPALM 數據。反之,也可以將標準格式的 NAPALM 數據翻譯成設備原始配置數據,並 Push 到網絡設備裏面,以修改設備的配置文件。

Netconf&NAPALM
是的,NAPALM 還是不能徹底解決網絡自動化所面臨的問題。

因爲各廠商 Netconf 的數據表達存在很多差異,所以 NAPALM 必須要依賴第三方的 Module 來完成原始數據的解析和翻譯。如果要解析廠商 A 的某個 OS 系統的配置,就需要一個 OSA_Module;如果要解析廠商 B 的某個 OS 系統的配置,則需要 OSB_Module。所以目前 NAPALM 支持的 OS 類型還比較少,僅限於某幾個國外品牌廠商的 OS 系統。

但是NAPALM是目前被集成最廣泛的網絡自動化庫,對思科和juniper支持很好。下面我們將進入是實驗部分。

2. 實驗目的

  1. 安裝NAPALM
  2. 通過NAPALM登錄設備
  3. 通過NAPALM自定義函數方法

3. 物料清單

  1. EVE-GN 網絡模擬器
  2. PC1臺
  3. Python3.7
  4. NAPALM

4. 實驗拓撲

在EVE_GN中新建一臺IOS交換機,然後橋接到PC網卡(任意能與物理PC通信的網卡),本實驗環境是橋接到vm station的NAT網卡。IP地址如下圖:
在這裏插入圖片描述

5. 實驗記錄

  1. 安裝NAPALM
    本人PC系統爲Win10 64bit enterprise,python版本如下3.7.3:
C:\Users\Nero>python --version
Python 3.7.3

C:\Users\Nero>systeminfo

主機名:           DESKTOP-KDNS6Q5
OS 名稱:          Microsoft Windows 10 企業版
OS 版本:          10.0.17763 暫缺 Build 17763
OS 製造商:        Microsoft Corporation
....
系統類型:         x64-based PC

在cmd輸入pip install napalm將自動安裝NAPALM及其依賴包。

pip install napalm		#安裝napalm
pip install napalm -U  #更新napalm
  1. 通過NAPALM實現TELNET連接設備:圖中交換機IP地址爲192.168.162.111,但是我們現在將使用EVE_NG服務器IP+設備端口號的形式來登錄設備,即: 192.168.162.133 32778 來登錄設備;因爲EVE_NG默認控制檯putty是使用上述IP信息和telnet協議;後面將介紹如何使用SSH連接設備。
from napalm import get_network_driver						#導入相關模塊
driver = get_network_driver('ios')							#指定設備類型爲ios,NAPALM對設備的支持是安裝版本來實現的,在登錄設備之前必須先指定NOS類型
device = driver(
    '192.168.162.133',                                      #設備ip地址
    None,													#設備用戶名,此設備未設置用戶名
    None,													#密碼,此設備未設置密碼
    optional_args={'port':32778,'transport':'telnet','secret':'enable123'} #選項字段,指定端口協議enable密碼, NAPALM默認爲SSH端口號22.
    )
device.open()      											 # 配置好以後,連接設備,沒報錯即連接成功
device.load_merge_candidate(config='hostname test_device')   #下發配置 hostname test_device修改主機名; 這裏需要在交換機上開始SCP,如果沒開啓,NAPALM會報錯提醒。
print(device.compare_config(),"step=1")                      #比較配置信息,詳見後文腳本運行日誌
device.commit_config()										 #確認下發
print(device.compare_config(),"step=2")						 #爲了對比,再比對一次配置,應沒有新增配置,詳見後文腳本運行日誌
device.rollback()											 #配置回滾(回退),結果應該是hostname回覆爲默認 switch
device.close()												 #斷開連接

腳本運行打印日誌:

+hostname test_device step=1       #上面比較配置信息顯示新增該配置(step=1 是爲了方便觀察程序運行添加,不影響功能)
 step=2							   #配置commit後再比較配置,顯示未新增配置,符合預期。

通過在交換機控制檯觀察到現象,注意設備名由Switch—>test_device------Switch的變化,因爲上面的配置將設備名修改爲test_device後,再進行配置回滾,恢復到Switch:
在這裏插入圖片描述
3. 通過SSH連接設備:事先配置好G1/0接口,並給交換機配置好IP地址爲192.168.162.111.配置好用戶名密碼。

from napalm import get_network_driver
driver = get_network_driver('ios')
with driver('192.168.162.111','napalm','napalm',optional_args={'port':22},) as device:    #通過with打開連接設備。默認爲ssh連接,無須額外配置
	print(device.get_facts())

運行上面的腳本輸出如下,返回的是一個字典,內容有運行時間uptime、廠商vendor、版本os_version,可以看書NAPALM已經將設備回顯給標準化了,無論什麼廠商的設備都將顯示爲如下格式:

{'uptime': 2280, 'vendor': 'Cisco', 'os_version': 'vios_l2 Software (vios_l2-ADVENTERPRISEK9-M), Version 15.2(4.0.55)E, TEST ENGINEERING ESTG_WEEKLY BUILD, synced to  END_OF_FLO_ISP', 'serial_number': '9YHU26C8R49', 'model': 'IOSv', 'hostname': 'Switch', 'fqdn': 'Switch.ivi', 'interface_list': ['GigabitEthernet0/0', 'GigabitEthernet0/1', 'GigabitEthernet0/2', 'GigabitEthernet0/3', 'GigabitEthernet1/0', 'GigabitEthernet1/1', 'GigabitEthernet1/2', 'GigabitEthernet1/3', 'GigabitEthernet2/0', 'GigabitEthernet2/1', 'GigabitEthernet2/2', 'GigabitEthernet2/3', 'GigabitEthernet3/0', 'GigabitEthernet3/1', 'GigabitEthernet3/2', 'GigabitEthernet3/3', 'GigabitEthernet3/3', 'GigabitEthernet3/3', 'GigabitEthernet3/3', 'GigabitEthernet3/3', 'Vlan1', 'Vlan300']}
  1. 自定義方法:雖然NAPALM有很多內建方法可以得到常用網絡信息如使用get_mac_table()得到mac地址表、get_interfaces_ip()得到ip信息等,但是實際網絡環境複雜多樣,總會遇到有需要使用額外命令的時候,下面我們假設需要設備輸出show ip interface brief信息。對應的解決方案是擴展NAPALM庫,新增一個自定義方法,完成輸出格式化,下次再使用的時候直接調用即可。新增該方法有兩個途徑

    途徑一是通過在在NAPALM對應的路徑下的模塊中直接增加自定義的函數,然後關閉,再調用。下面我們來操作一把:
    我的路徑是C:\Program Files\Python37\Lib\site-packages\napalm,找到其中的ios文件夾:
    在這裏插入圖片描述
    下圖中ios.py 即是ios設備的庫文件,所有方法均在該文件中,因此我們可以打開它直接在其後面新增自己編寫的方法。
    在這裏插入圖片描述
    我們編寫一個腳本用於輸出show ip interface brief的顯示

    def cuget_interfaces_brief(self):
        command = 'show ip interface brief'
        output = self._send_command(command)

        return_vars = []
        for line in output.splitlines():
            return_vars.append(tuple(line.split()))

        return return_vars                      #將返回一個列表,列表內嵌套元組,命令show ip interface brief 在屏幕上的回顯一行放進一個元組

我們將上述腳本直接粘貼到ios.py文件的末尾,然後保存:
在這裏插入圖片描述
現在我們連接設備後就可以使用該方法了:

from napalm import get_network_driver
driver = get_network_driver('ios')
with driver('192.168.162.111','napalm','napalm',optional_args={'port':22},) as device:          #通過with方法連接設備,少了device.open()這一步
    # device.load_merge_candidate(config='hostname Switch')
    print(device.get_facts())
    print(device.cuget_interfaces_brief())                         	#顯示自定義方法輸出結果


#爲了讓輸出結果和show ip interface brief類似,我們下面對自定義方法進行格式化輸出
    t = 0
    for i in device.cuget_interfaces_brief():
        for tup in i:
            print(tup,end='\t'*3) if t == 0 else print(tup,end='\t'*2)

        print()
        t += 1

腳本輸出如下:
print(device.get_facts())的輸出:

{'uptime': 2280, 'vendor': 'Cisco', 'os_version': 'vios_l2 Software (vios_l2-ADVENTERPRISEK9-M), Version 15.2(4.0.55)E, TEST ENGINEERING ESTG_WEEKLY BUILD, synced to  END_OF_FLO_ISP', 'serial_number': '9YHU26C8R49', 'model': 'IOSv', 'hostname': 'Switch', 'fqdn': 'Switch.ivi', 'interface_list': ['GigabitEthernet0/0', 'GigabitEthernet0/1', 'GigabitEthernet0/2', 'GigabitEthernet0/3', 'GigabitEthernet1/0', 'GigabitEthernet1/1', 'GigabitEthernet1/2', 'GigabitEthernet1/3', 'GigabitEthernet2/0', 'GigabitEthernet2/1', 'GigabitEthernet2/2', 'GigabitEthernet2/3', 'GigabitEthernet3/0', 'GigabitEthernet3/1', 'GigabitEthernet3/2', 'GigabitEthernet3/3', 'GigabitEthernet3/3', 'GigabitEthernet3/3', 'GigabitEthernet3/3', 'GigabitEthernet3/3', 'Vlan1', 'Vlan300']}

print(device.cuget_interfaces_brief())的輸出

[('Interface', 'IP-Address', 'OK?', 'Method', 'Status', 'Protocol'), ('GigabitEthernet0/0', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet0/1', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet0/2', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet0/3', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet1/0', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet1/1', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet1/2', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet1/3', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet2/0', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet2/1', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet2/2', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet2/3', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet3/0', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet3/1', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet3/2', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet3/3', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet3/3', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet3/3', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet3/3', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet3/3', 'unassigned', 'YES', 'unset', 'up', 'up'), ('Vlan1', 'unassigned', 'YES', 'unset', 'administratively', 'down', 'down'), ('Vlan300', '192.168.162.111', 'YES', 'NVRAM', 'up', 'up')]

格式化print(device.cuget_interfaces_brief())後的輸出:是不是和CLI 屏幕輸出一樣了? 爽不爽

Interface			IP-Address			OK?			Method			Status			Protocol			
GigabitEthernet0/0		unassigned		YES		unset		up		up		
GigabitEthernet0/1		unassigned		YES		unset		up		up		
GigabitEthernet0/2		unassigned		YES		unset		up		up		
GigabitEthernet0/3		unassigned		YES		unset		up		up		
GigabitEthernet1/0		unassigned		YES		unset		up		up		
GigabitEthernet1/1		unassigned		YES		unset		up		up		
GigabitEthernet1/2		unassigned		YES		unset		up		up		
GigabitEthernet1/3		unassigned		YES		unset		up		up		
GigabitEthernet2/0		unassigned		YES		unset		up		up		
GigabitEthernet2/1		unassigned		YES		unset		up		up		
GigabitEthernet2/2		unassigned		YES		unset		up		up		
GigabitEthernet2/3		unassigned		YES		unset		up		up		
GigabitEthernet3/0		unassigned		YES		unset		up		up		
GigabitEthernet3/1		unassigned		YES		unset		up		up		
GigabitEthernet3/2		unassigned		YES		unset		up		up		
GigabitEthernet3/3		unassigned		YES		unset		up		up		
GigabitEthernet3/3		unassigned		YES		unset		up		up		
GigabitEthernet3/3		unassigned		YES		unset		up		up		
GigabitEthernet3/3		unassigned		YES		unset		up		up		
GigabitEthernet3/3		unassigned		YES		unset		up		up		
Vlan1		unassigned		YES		unset		administratively		down		down		
Vlan300		192.168.162.111		YES		NVRAM		up		up		

***Repl Closed***

途徑二:擴展NAPALM的第二種方法是新增配置文件,新建類方法,然後繼承對應版本的內建class,既可以使用自建類,也可以繼承內建類的所有方法。這種方法不修改和影響內建方法,靈活性更強,推薦使用該方法:

首先在 C:\Program Files\Python37\Lib\site-packages\napalm下新建文件夾,取名custom_ios
在這裏插入圖片描述
文件夾內新建兩個文件__init__.pyios.py,前者的名字必須固定爲__init__.py
在這裏插入圖片描述
分別在兩個文件粘貼如下內容:
__init__.py:內容如下

"""custom.napalm.ios package.by nero"""
from napalm.custom_ios.ios import CustomIOSDriver

__all__ = ["CustomIOSDriver"]

ios.py內容如下:

from napalm.ios.ios import IOSDriver
class CustomIOSDriver(IOSDriver):                       #注意__init__.py中的對應名字一定要和這裏class的名字保持一致,同時這裏繼承了內建IOSDriver的所有方法,便於我們調用自定義方法的時候可以繼續使用內建的方法
    """Custom NAPALM Cisco IOS Handler."""

    def cuget_interfaces_brief(self):
        command = 'show ip interface brief'
        output = self._send_command(command)

        return_vars = []
        for line in output.splitlines():
            return_vars.append(tuple(line.split()))

        return return_vars

完成以上操作後就可以開始測試效果了:將之前的例子調用方法修改一下,其他語句均不變,如下:

from napalm import get_network_driver
driver = get_network_driver('custom_ios')               #括號內是剛纔新建文件夾的名字,僅修改此處
with driver('192.168.162.111','napalm','napalm',optional_args={'port':22},) as device:
    print(device.get_facts())
    print(device.cuget_interfaces_brief())
    t = 0
    for i in device.cuget_interfaces_brief():
        for tup in i:
            print(tup,end='\t'*3) if t == 0 else print(tup,end='\t'*2)

        print()
        t += 1

運行和預期一樣,正常工作。

6. 參考資料

  1. http://www.ruijie.com.cn/fa/xw-hlw/61232/
  2. NAPALM’s documentation
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章