網路工程師的Python之路---進階篇
來自專欄網路行者28 人贊了文章
7月6號更新:
最近知乎對專欄文章加入了字數限制,好像是7000字封頂,導致我的《網路工程師的Python之路---進階篇》剛講完案例1就必須拆開來寫了。以後的更新將會以新文章的方式發布,下面附上《進階篇(續)》的鏈接:
弈心:網路工程師的Python之路---進階篇(續)前言
本文是《網路工程師的Python之路---初級篇》的續作,《初級篇》以GNS3模擬器為平台,配合Python代碼講解和視頻演示,以實驗的形式在虛擬思科三層交換機上由淺入深地講解了Python在計算機網路運維中的部分運用。從《進階篇》開始,我將從一位資深網路工程師的角度,用實際工作中的案例配合Python代碼來講解Python在大型網路運維(設備數量大於1000台)中的實戰應用。當然,《進階篇》會涉及到更多《初級篇》沒有使用到的Python模塊和應用技巧,並且所有的Python腳本會在真機上執行和演示。另外,因為我長期在國外工作和生活,個人的習慣問題,我在代碼里的注釋或者代碼里列印出的內容都是用英文寫的,不過不用擔心,在講解部分我會用中文翻譯和做解釋。
寫《進階篇》之前有考慮過是否要把所有案例代碼里所涉及到的Python基礎知識講透講清楚,後來一想那樣的話會造成篇幅太長,太費時間和精力,且脫離了當初決定寫這兩篇文章的初衷。《進階篇》寫完後我會考慮專門寫一篇文章主講Python的基礎知識,內容將會側重網路運維腳本代碼中常用到的Python基礎知識,不是a-z的全方位Python講解。
附上《初級篇》的鏈接:
弈心:網路工程師的Python之路---初級篇
正如我在《初級篇》中提到的,現在以Python為代表的編程技術以及以SDN/NFV為代表的網路自動化技術越來越火,傳統網路工程師必須儘快「上車",才能在行業里保持競爭力,因為在CLI和GUI裏手動敲命令、改配置、甚至排錯的時代真的已經過去了(當然,我不否認一些複雜的網路故障依然需要網工手動排錯),很遺憾的是,目前「上了車」的網工真心不多,希望我的這兩篇文章能幫助更多同行快速了解Python在網路運維中的應用,切實體會到用Python腳本來實現自動化管理大型網路的強大和便利之處。
另:《初級篇》中運行Python代碼的主機以Linux為操作系統,有朋友留言說能不能講下Python在Windows中的應用,鑒於並不是所有人都會Linux,因此我決定從《進階篇》開始,所有案例和代碼會在Windows主機上講解和運行。 另外《初級篇》中也有朋友問我能不能講下Ansible,說實話剛學會Python半年的我才剛剛入門,很多優秀、實用的Python模塊還在摸索和研究中,Ansible暫時還沒接觸過,將來如有機會學習和使用到,我將會在第一時間另寫一篇文章主講Ansible。
開篇前先介紹下自己的職業經歷以及目前的工作環境:
筆者在海外從業9年(沒有在國內工作過),2013年考取CCIE,思科方向,主攻思科IOS, IOS-XE, NX-OS, IOS-XR的設備和技術,也接觸過Arista的數據中心解決方案,Juniper, Fortinet, Checkpoint,McAfee的防火牆, Aruba(已被HPE收購)的無線網路等產品。
在新加坡工作過7年,曾先後在美國知名運營商AT&T亞太區總部, 大型數據中心公司Equinix亞太區總部,新加坡陸路交通管理局(Land Transport Authority,相當於交通部),新加坡華僑銀行(OCBC),美國蘋果(Apple)公司,蘇格蘭皇家銀行(RBS)等知名外企、政府部門、銀行、科技巨頭公司擔任過網路工程師、高級網路工程師以及網路顧問等職務。2014年曾有幸做為首席網路工程師全程參與了新加坡國家美術館(National Art Gallery of Singapore)和新加坡國家藝術博物館(Singapore Art Museum)的網路項目的設計、執行和交付。2016年來到沙特,目前任職於"世界第一土豪大學"沙特阿卜杜拉國王科技大學(KAUST)超過兩年,擔任高級網路工程師,第一年在網路工程組(Engineering)負責學校的網路架構和設計,第二年因人事調動轉到網路運維組(Operation)負責技術管理。
筆者目前所任職的KAUST有1萬餘人,佔地面積36平方公里,比澳門還要大三分之一,正因如此,KAUST的校園網是完全仿照運營商的SP網路打造的,骨幹網是MPLS, 骨幹網裡的P設備剛從思科CRS1過渡到ASR9K, 覆蓋校園網路的PE設備(思科7600,6880)接近100台,另外還有3000多台的接入層交換機(均為思科Catalyst,2960/3560/3750/3850/9300都有)供教學樓和教職員工住宅區所用(校內有2800多個美式別墅,每戶別墅單獨配置一台2960或者3750交換機)。另外學校的數據中心裡有世界排名前30,中東排名第一的超級計算機Shaheen II,數據中心裏面也有林林總總的思科Nexus 7K和9K的設備幾十台, 設備總數接近4000台,符合大型網路的定義,可見Python在KAUST的網路里大有用武之地。
下面進入正文:
如前言里提到的,因《初級篇》下大家留言的要求,《進階篇》里所有的Python代碼實例演示都將在Windows主機下完成,因此首先簡單講下在Windows里安裝Python和Paramiko的步驟,以及在Windows里執行Python代碼的方式:
注: 筆者使用的是Win 8.1 64bit的操作系統。
- 首先點擊這裡下載Windows版的Python(版本2.7.12),根據自身情況選擇32位和64位版本。
- 安裝過程中有一個很重要的步驟,如下圖:"Add python.exe to Path"這裡默認是打叉關閉的,請務必記住點開它並選擇"Entire feature will be installed on local hard drive.,它會自動幫你設置好環境變數,(也就是說你以後打開CMD運行Python腳本時,你可以在任意盤符和文件夾下直接輸入"python xxx.py"來運行腳本,無需輸入python執行程序所在的完整路徑來運行腳本,例如"C:Python27python xxx.py",不要小看這一安裝選項提供的自動環境變數設置,它會幫你(尤其是Python初學者)節省很多很多很多很多很多時間!
3. 接下來安裝Paramiko(關於Paramiko的介紹請閱讀《初級篇》,這裡不再贅述)。很簡單,直接打開CMD,輸入pip install paramiko.
4. Paramiko安裝好後,打開python,輸入import paramiko,如果沒有報錯則說明安裝完成。
5. Python和Paramiko安裝好後,我們可以再安裝一個Sublime Text 3來作為我們的Python代碼編輯器,Sublime Text 3點這裡下載。關於Sublime Text 3的用法就不多說了,這裡只提一下Sublime是跨平台的代碼編輯器,默認語法是Plain Text,你必須手動選擇View -> Syntax -> Python(如下圖)才能獲得對Python最好的支持,包括代碼高亮,語法提示,代碼自動補完,默認將腳本保存為.py格式等諸多實用功能。
6. 在Windows里運行python腳本的方式主要有三種:
a. 左鍵雙擊腳本即可執行
b. 右鍵單擊腳本,選擇用IDLE編輯腳本,然後點擊Run—>Run Module執行腳本。
c. 在CMD命令行里輸入"python xxx.py"來執行文件
這裡主要講下第一種方法:左鍵雙擊運行腳本後,你會看到一個「閃退」的CMD窗口(「閃退」很快,從窗口彈出到消失只有0.1-0.2秒的時間,肉眼剛剛能看到),根本看不到運行腳本後的結果,這是因為程序執行完後自動退出了,要讓窗口停留,可以在代碼最後放一個raw_input()。
關於Python和Paramiko在Windows里的安裝,以及代碼的執行就講到這裡,下面進入第一個案例:
案例1
案例背景:
某公司有48口的思科3750交換機共1000台,分別分布在5個掩碼為/24的B類網路子網下:
172.16.0.x /24
172.16.1.x /24
172.16.2.x /24
172.16.3.x /24
172.16.4.x /24
案例需求:
在不藉助任何NMS軟體或網路安全工具的幫助的前提下,使用Python腳本依次ping所有交換機的管理IP地址,來確定當前有哪些交換機可達,並且統計當前每個交換機有多少終端物理埠是UP的(級聯埠不算),以及1000台交換機所有UP的終端端物理埠的總數,並統計網路里的埠使用率(也就是埠的up率)。
案例思路:
根據需求我們可以寫兩個腳本,第一個腳本用來ping5個網段下所有交換機的管理IP,因為掩碼是/24,IP地址的最後一位我們可以指定python來ping .1到.254,然後將所有可達的交換機IP寫入並保存在一個名為reachable_ip.txt的文本文件中。
之後,寫第二個腳本來讀取該文本文件中所保存的IP地址,依次登錄所有這些可達的交換機,輸入命令show ip int brief | i up命令查看有哪些埠是up的,再配合re這個模塊(正則表達式),來匹配我們所要的用戶端物理埠號(Gix/x/x),統計它們的總數,即可得到當前一個交換機有多少物理埠是up的。 (註:因為show ip int brief | i up的結果里也會出現10G的級聯埠Tex/x/x以及虛擬埠,比如vlan或者loopback埠,所以這裡強調的是用正則表達式來匹配用戶端物理埠Gix/x/x)
案例代碼:
案例1-腳本1:
import paramikoimport timeimport subprocessimport osclass Ping(object): third_octect = range(5) last_octect = range(1,255) def __init__(self): self.ping() def ping(self): self.remove_last_reachable_ip_file_exist() for ip3 in self.third_octect: for ip4 in self.last_octect: self.ip = 172.16. + str(ip3) + . + str(ip4) self.ping_result = subprocess.call([ping,-n,2,-w,2,self.ip]) self.open_ip_record_file() self.check_ping_result() self.f.close() def open_ip_record_file(self): self.f = open(reachable_ip.txt,a) def check_ping_result(self): if self.ping_result == 0: self.f.write(self.ip + "
") def remove_last_reachable_ip_file_exist(self): if os.path.exists(reachable_ip.txt): os.remove(reachable_ip.txt) if __name__ == __main__: script1_1 = Ping()
案例1-腳本1代碼講解:
註:《初級篇》里已經解釋過的模塊就不再贅述了,另外關於《進階篇》代碼里出現的if _name_ == main_,類,在類里出現的_init__(self), 以及在類里定義對象屬性、調用類里自定義方法的用法這裡也不講了,如《初級篇》里提到的,網上關於Python的入門教程太多了,在《進階篇》我會選擇性地講解案例腳本中出現的某些Python模塊和它們所帶的方法及其用法,但是我不希望把我的這兩篇文章寫成純粹的Python教學文章。
- 這裡import了subprocess這個模塊,我們要靠它來ping交換機的管理IP地址。
- 另外我們也import了os這個模塊,第一次運行腳本時,我們會通過open()函數的追加模式(也就是參數a),把所有可達的交換機管理IP地址依次寫入reachable_ip.txt這個文本文件中(腳本中的open_ip_record_file(self))。如果我們第二次運行腳本,那麼第二次所有可達的IP地址又會被繼續以追加的形式寫入reachable_ip.txt文件中,這樣的話顯得重複多餘,由此我們import了os這個模塊,每次運行腳本1時,我們可以配合它的os.path.exists來判斷reachable_ip.txt這個文件是否存在,如果存在的話就將它刪除,這樣可以保證每次運行腳本1時,reachabe_ip.txt這個文件里只會出現本次運行腳本後所有可達的IP地址。我們在腳本里自定義的remove_last_reachable_ip_file_exsit(self)這個方法就是干這件事的。
- 因為我們要依次ping 172.16.0.x, 172.16.1.x, 172.16.2.x, 172.16.3.x, 172.16.4.x這五個/24網段的IP,它們是有規律可循的,第三欄位是從0-4, 所以這裡我們用range(5)創建一個包含數字0-4的列表,並把它賦值給third_octect這個變數,第四欄位我們要從1 ping到254, 所以又用range(1,255)創建第二個列表並把它賦值給last_octect這個變數。
- 然後我們用兩個for循環做嵌套,依次從172.16.0.1, 172.16.0.2, 172.16.0.3。。。一直遍歷到172.16.4.254為止,配合subprocess.call來ping所有這些IP,subprocess.call([ping,-n,2,-w,2,self.ip])中的"-n"和"-w"是Windows里的ping命令的參數,表示每個IP只ping兩次,每次最多等待兩秒鐘。
- 注意subprocess.call會返回兩個值0和2(有時也會返回1),返回0表示目標可達,返回2表示不可達(1也表示不可達)。所以下面的check_ping_result(self)方法用來做判斷,如果返回的值是0 (if self.ping_result == 0:),則將它寫入reachable_ip.txt文件中reachable_ip.txt(self.f.write(self.ip + "
"))
執行案例1-腳本1看效果:
執行代碼前,我的腳本名叫advance_1_1.py, 保存的位置是在」C:UsersWANGY0LDesktopPython Scriptszhihu進階篇-1」 這個文件夾下面。
這裡我們用CMD來演示(在Windows里用IDLE執行py文件以後會講到),用CMD執行代碼時,請先用cd命令移動到該文件夾地址,然後再執行py腳本文件:
因為只是演示效果,這裡我們只ping前6個IP, 也就是172.16.0.1 到172.16.0.6,然後我們用Ctrl+C停止腳本。
再次打開腳本所在的文件夾,這時你會看到已經多出來了一個名叫reachable_ip的txt文本文件
打開它,你會看到剛才可達的那6個IP已經被寫入進去了。
講案例1-腳本2之前先來看下在一個48口的3750交換機里輸入show ip int b | i up能得到什麼輸出結果:
如上圖,這是我在172.16.0.1這個交換機里得到的輸出結果,可以看到除了GigabitEthernet終端埠外,還有Vlan虛擬埠和兩個萬兆的級聯埠。在我們案例的需求里已經明說了不考慮虛擬埠和級聯埠,只統計總共有多少終端物理埠是up的。
下面來看案例1-腳本2:
import paramikoimport timeimport refrom datetime import datetimeimport reimport socketnow = datetime.now()date = "%s-%s-%s" % (now.month, now.day, now.year)time_now = "%s:%s:%s" % (now.hour, now.minute, now.second)class Port_statistics(object): switch_with_tacacs_issue = [] switch_not_reachable = [] total_number_of_up_port = 0 def __init__(self): self.ssh_login() self.summary() def ssh_login(self): self.iplist = open(reachable_ip.txt) self.number_of_switch = len(self.iplist.readlines()) self.iplist.seek(0) for line in self.iplist.readlines(): try: self.ip = line.strip() self.ssh_client = paramiko.SSHClient() self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) self.ssh_client.connect(hostname=self.ip,username=xxx,password=xxx,look_for_keys=False) print "
You have successfully connect to ", self.ip self.command = self.ssh_client.invoke_shell() self.check_up_port() except paramiko.ssh_exception.AuthenticationException: print "TACACS is not working for " + self.ip + "." switch_with_tacacs_issue.append(self.ip) except socket.error: print self.ip + " is not reachable." switch_not_reachable.append(self.ip) self.iplist.close() def check_up_port(self): self.command.send(term len 0
) self.command.send(show ip int b | i up
) time.sleep(1) output = self.command.recv(65535) #print output self.search_up_port = re.findall(rGigabitEthernet, output) self.number_of_up_port = len(self.search_up_port) print self.ip + " has " + str(self.number_of_up_port) + " ports up." self.total_number_of_up_port += self.number_of_up_port def summary(self): self.total_number_of_ports = self.number_of_switch * 48 print "
" print "There are totally " + str(self.total_number_of_ports) + " ports available in the network." print str(self.total_number_of_up_port) + " ports are currently up." print "Port up rate is %.2f%%" % (self.total_number_of_up_port / float(self.total_number_of_ports) * 100) print
TACACS is not working for below switches: for i in self.switch_with_tacacs_issue: print i print
Below switches are not reachable: for i in self.switch_not_reachable: print i f = open(date + ".txt", "a+") f.write(As of + date + " " + time_now) f.write("
There are totally " + str(self.total_number_of_ports) + " ports available in the network.") f.write("
" + str(self.total_number_of_up_port) + " ports are currently up.") f.write("
Port up rate is %.2f%%" % (self.total_number_of_up_port / float(self.total_number_of_ports) * 100)) f.write("
***************************************************************
") f.close()if __name__ == __main__: script1_2 = Port_statistics()
案例1-腳本2代碼講解:
- 這裡import了datetime這個模塊,用來記錄每次我們運行代碼的時間。
- 記錄當前時間可以調用datetime.now()方法,將它賦值給now這個變數,datetime.now()這個方法下面有含了.year()(年)、.month()(月)、.day()(日)、.hour()(時)、.minute()(分)、.second()(秒)幾個子方法,這裡將「月-日-年」賦值給date這個變數,將「時:分:秒」賦值給time_now這個變數。
- switch_with_tacacs_issue = []和switch_not_reachable = []在《初級篇》里已經講過了,用來統計有哪些交換機是因為TACACS的原因不可達,有哪些交換機是因為本身鏈路有問題而不可達,方便排錯。
- total_number_of_up_port = 0, 先將總up埠數設為0,後面再用累加的方法統計。
- 這時用self.iplist = open(reachableip.txt)將執行腳本1後生成的reachable_ip.txt文件打開,因為每個IP地址對應一個交換機,我們這裡可以用len(self.iplist.readlines()) 來統計有多少個交換機(記住readlines()返回的值是列表,用len()函數可以計算一個列表裡有多少元素,並返回一個整數),並將它賦值給self.number_of_switch這個變數。
- self.iplist.seek(0),很重要的一步,因為我們上一步已經靠readlines()計算了reachable_ip.txt裡面總共有多少個IP(交換機),這時游標已經移動到了文件的最末端,我們必須用seek(0)將游標移迴文件最開始的地方,否則下面的for循環無法從頭開始讀取reachable_ip.txt裡面的IP地址,我們也就無法用paramiko來登錄交換機了。
- 下面用for循環配合Paramiko來依次SSH登錄所有交換機的部分就不講了,這裡只需要注意self.ssh_client.connect(hostname=self.ip,username=xxx,password=yyy,look_for_keys=False), 記住把xxx和yyy分別替換為你所要登錄的交換機的用戶名和密碼。
- def check_up_port(self)函數下面我們在所登錄的交換機里,輸入了term len 0和show ip int brief | i up,並將輸出結果賦值給了output這個變數。 隨後我們用到了正則表達式re.findall(rGigabitEthernet, output),來從output裡面匹配我們想要的關鍵詞,也就是"GigabitEthernet"這個終端物理埠。
- 正則表達式是個很龐大的話題,我會在以後專門寫一篇Python基礎知識來講解它,目前需要讀者自己去學習和了解,這裡提幾點:re.findall()返回的值是列表,r表示raw string(原始字元串),緊接其後的GigabitEthernet就是我們要匹配的關鍵詞,後面的output則是正則表達式所要讀取的樣本。整個re.findall(rGigabitEthernet, output)返回的是一個包含了所有GigabitEthernet的列表,簡單點來說,你可以把它當成Excel裡面的「查找所有」功能。隨後我們並將該正則表達式查找的內容賦值給self.number_of_up_port,(self.search_up_port = re.findall(rGigabitEthernet, output))
- self.number_of_up_port = len(self.search_up_port),很簡單明了,現在self.number_of_up_port 這個變數代表的就是一個交換機有多少個GigabitEthernet埠是up的。 然後我們可以靠print self.ip + " has " + str(self.number_of_up_port) + " ports up." ,在運行腳本的時候將每個交換機有多少埠是up的列印出來,清晰明了。
- 最後通過self.total_number_of_up_port += self.number_of_up_port來累加在整個網路里總共有多少埠是up的。
- 下面的summary()方法里,除了將統計信息各種列印出來外,我們還將另外創建一個文件,將運行腳本時的日子作為該腳本的名字(f = open(date + ".txt", "a")) ,將統計信息寫入進去,方便我們調閱查看, 注意寫入的內容裡面有f.write(As of + date + " " + time_now),這樣還可以清晰直觀的看到我們是哪一天,幾時幾分幾秒運行的腳本。
- 為什麼要用日期名作為文件名?這樣做的好處是一旦運行腳本時的日期不同,腳本就會自動創建一個新的文件,比如今天6月16號運行了一次腳本,Python創建了一個名為6-16-2018.txt的文件,如果明天我再運行一次腳本,Python又會創建一個名為6-17-2018.txt的文件。如果你在同一天里數次運行腳本,則多次運行的結果會以追加的形式寫進同一個.txt文件,不會創建新文件。這麼做可以讓我們配合Windows的Task Scheduler或者Linux的Crontab來定期自動執行腳本,每天自動生成當天的埠使用量的統計情況,方便管理層隨時觀察網路里交換機的埠使用情況。
執行案例1-腳本2看效果:
運行腳本前我們將案例1-腳本2(advance_1_2.py)保存在案例1-腳本1所在的文件夾下面:
這裡我使用IDLE來執行腳本advance_1_2.py:
輸出結果可以看到每個交換機有多少個埠是up的。
最後的總結部分還可以看到:
There are totally 288 ports available in the network.
6個48口的3750交換機總共有288個埠。
176 ports are currently up.
目前有176個埠是up的
Port up rate is 61.11%
埠up率為61.11%
另外TACACS is not working for below switches: 和Below switches are not reachable: 下面內容為空,表示沒有交換機出現TACACS問題和鏈路問題造成不可達的情況。
這時打開腳本所在的文件夾,發現多出了6-16-2018.txt這個文件
打開它,你會看到運行腳本的時間以及之前提到的統計信息。
如果你此時再運行一次案例1-腳本2,新的腳本運行時間和統計信息又會以追加的形式寫在下面:
推薦閱讀: