銀狐DevNet系列會持續(xù)將網(wǎng)絡(luò)運維工作中python的應(yīng)用進行場景化的分享胯杭,因為每個單獨的模塊網(wǎng)上都有詳細的教學(xué),這里就不深入講解模塊基礎(chǔ)了受啥,內(nèi)容主要以思路和示例為主做个,并將碰到的問題匯總提出注意事項。
主要是因為網(wǎng)絡(luò)工程師和網(wǎng)絡(luò)運維工作者編程基礎(chǔ)不強滚局,加上網(wǎng)上對于這個領(lǐng)域的python資料又少居暖,傳統(tǒng)的分享方式(每個章節(jié)僅單純分享一個知識點)對于很多網(wǎng)工來說各個知識點相對獨立且割裂的,很難進行一個知識的融合核畴,現(xiàn)實工作中也很難直接應(yīng)用膝但,大家學(xué)習(xí)的難度就會很大,也會導(dǎo)致大部分人剛?cè)腴T就放棄谤草。所以我將這些內(nèi)容進行場景化跟束,根據(jù)特定場景由淺入深不斷優(yōu)化莺奸,從而帶出更多知識點,希望對大家有所幫助冀宴。
這些分享都是我本人真實的學(xué)習(xí)路徑灭贷,一方面是幫助自己梳理網(wǎng)絡(luò)自動化相關(guān)知識,另一方面也希望可以通過分享我微不足道的學(xué)習(xí)過程和實戰(zhàn)經(jīng)驗略贮,幫助更多想要了解NetDevOps而苦于沒有資料和環(huán)境被勸退的人甚疟,進而找到同行之人互相交流、互相提升逃延。
1览妖、使用場景:
前面5個小節(jié)由淺入深分享了調(diào)用excel內(nèi)容,通過netmiko批量抓取華為網(wǎng)絡(luò)設(shè)備配置揽祥,并存入本地讽膏。目前來看已可以正常在企業(yè)內(nèi)進行配置備份工作,但現(xiàn)實情況下有個不可避免的問題拄丰,如果要批量備份大量設(shè)備府树,加入1臺設(shè)備需要10秒,那100臺設(shè)備豈不是需要1000秒料按,那如果有上千奄侠、上萬臺設(shè)備怎么辦呢?雖然自動化方式比人為備份強很多载矿,但效率還是太低了垄潮,所以在現(xiàn)實網(wǎng)絡(luò)運維場景中,批量操作一般需要結(jié)合并發(fā)操作恢准,這也是本小節(jié)的核心內(nèi)容魂挂。
2、知識講解
高并發(fā)操作網(wǎng)上有很多內(nèi)容馁筐,但都說的很官方涂召,看完并不知道再說些什么。所以我盡可能用大白話簡單解釋一下核心概念敏沉,什么是進程果正、線程、協(xié)程盟迟,同步秋泳、異步、并發(fā)攒菠、并行迫皱?有什么區(qū)別?
模式一:試想工廠里有一條生產(chǎn)線做手機,第一個人負責(zé)裝電池卓起,第二個人負責(zé)裝芯片和敬,第三個人負責(zé)裝屏幕。每個人只負責(zé)自己的一項任務(wù)戏阅,這樣分工明確的方式肯定比一個人干的效率高昼弟,這樣的模式就是單進程、多線程方式奕筐。
模式二:這時工廠增加了一條生產(chǎn)線舱痘,還是同樣的配置,第一個人負責(zé)裝電池离赫,第二個人負責(zé)裝芯片芭逝,第三個人負責(zé)裝屏幕。兩條線同時工作生產(chǎn)手機笆怠,這就叫多進程铝耻、多線程方式。
模式三:讓我們把視角重新拉回單條生產(chǎn)線蹬刷,這里會發(fā)現(xiàn)一個問題,如果第一個人工作沒完成频丘,第二個人是沒法工作的办成,他必須處于等待狀態(tài),就算第一個人工作速度足夠快搂漠,第二個人也會有等待時間迂卢,只是很短暫而已,這就叫同步桐汤。也就是處理任務(wù)必須一個一個來而克,單個任務(wù)處理的再快,也是有等待時間的怔毛。如果我們不讓工人閑著员萍,第一個人處理任務(wù)的時候,第二個人不閑著拣度,而是處理其他事情(工人只要進入等待狀態(tài)就干其他任務(wù))碎绎,最后會發(fā)現(xiàn)同樣的時間有更多的產(chǎn)出,這種方式就叫協(xié)程抗果,也就是我們說的異步筋帖。
進程就是我們CPU的核數(shù),有多少核理論上就可以有多少進程冤馏,2個進程同時工作就是2條生產(chǎn)線日麸,相互之間沒有依賴關(guān)系,你干你的逮光,我干我的代箭,這種模式就是并行墩划,2個CPU可以實現(xiàn)真正的同時處理任務(wù)。
當(dāng)我們啟動py腳本的時候梢卸,就會啟動一個進程走诞,進程會啟動線程,線程是依賴進程的蛤高,單進程多線程工作是有前后依賴關(guān)系的蚣旱,第一個工作做完工作,第二個工作才能繼續(xù)戴陡,同一個任務(wù)項無論他們操作的速度有多快塞绿,他們永遠無法并行工作的,而是有個前后等待關(guān)系恤批,這就叫并發(fā)异吻。協(xié)程只是線程的優(yōu)化,把等待時間利用上喜庞,但也無法實現(xiàn)真正意義上同時任務(wù)诀浪,所以也是并發(fā)。
總結(jié):
進程是種資源的分配單位延都,實際上做事情的是線程雷猪,線程進行優(yōu)化就變成了協(xié)程。多進程可實現(xiàn)真正意義上的并行晰房,但成本很高求摇,換句話說就是需要資源很多,所以網(wǎng)絡(luò)運維一般使用協(xié)程就好殊者。經(jīng)過測試抓取100臺華為設(shè)備配置并進行本地測試需要的時間12秒与境,幾百臺的時間也不會超過20秒,所以簡單的批量操作使用協(xié)程就好猖吴,如果是比較復(fù)雜的跑批操作可以使用進程+協(xié)程的組合方式摔刁,大部分企業(yè)用不上的。
3距误、實驗環(huán)境:
操作系統(tǒng):Linux CentOS 7.4
python版本:python 3.8
網(wǎng)絡(luò)設(shè)備:華為CE 6865
編輯器:vscode(pycharm簸搞、sublime均可,推薦vscode)
excel格式:初次使用簡單一些准潭,excel中只加入IP地址
4趁俊、思路分析
批量操作直接使用協(xié)程就好,不用考慮進程和線程刑然,線程有很多庫asyncio 寺擂、gevent之類的,個人不推薦什么模塊都研究,很浪費時間怔软,不是開發(fā)人員不用研究太深垦细,哪個方便好用就用哪個,個人推薦gevent挡逼,方便好用而且不用改源代碼括改。有些研發(fā)人員會將gevent的一些缺陷,但其實運維場景中沒什么影響家坎,不用在意嘱能。
gevent也有很多使用方式,可以查看文檔虱疏,這里就推薦我常用的方式惹骂,gevent.map,這種方式可以控制并發(fā)量做瞪,比較靈活对粪,防止CPU動不動干到100%。
gevent批量備份配置的思路装蓬,比如我們要備份100臺設(shè)備著拭,其實是登錄100臺設(shè)備,然后輸出命令抓取配置牍帚,并將回顯內(nèi)容寫入本地文件茫死。當(dāng)我們SSH第一臺設(shè)備時,其實會有一個等待時間履羞,這時不要讓程序等待,而是直接SSH第二臺設(shè)備屡久,以此類推忆首。可能SSH第10臺網(wǎng)絡(luò)設(shè)備時被环,第一臺設(shè)備配置回顯了糙及,這些任務(wù)流雖然有前后順序,但切換的速度非常非常塊筛欢,所以會給我們一種同時連接100臺設(shè)備并寫入文件的錯覺浸锨,所以我們操作1臺設(shè)備的時間和操作100臺設(shè)備的時間,其實差距不會很大版姑。這就是為什么我說100臺設(shè)備12秒柱搜,500臺也不會超過20秒。
5剥险、整體代碼
#!/usr/bin/env python
#coding: utf-8
import os
from time import time
from datetime import datetime
from netmiko import ConnectHandler
from openpyxl import Workbook
from openpyxl import load_workbook
import gevent
from gevent import spawn
from gevent import monkey;monkey.patch_all()
from gevent.pool import Pool
from netmiko.ssh_exception import NetMikoTimeoutException
from netmiko.ssh_exception import AuthenticationException
from paramiko.ssh_exception import SSHException
def read_device_excel( ):
ip_list = []
wb1 = load_workbook('/home/netops/venv/cs_lab.xlsx')
ws1 = wb1.get_sheet_by_name("Sheet1")
for cow_num in range(2,ws1.max_row+1):
ipaddr = ws1["a"+str(cow_num)].value
ip_list.append(ipaddr)
return ip_list
def get_config(ipaddr):
session = ConnectHandler(device_type="huawei",
ip=ipaddr,
username="root",
password="root",
banner_timeout=300)
print("connecting to "+ ipaddr)
print ("---- Getting HUAWEI configuration from {}-----------".format(ipaddr))
config_data = session.send_command("dis cu")
session.disconnect()
return config_data
def write_config_to_file(config_data,ipaddr):
now = datetime.now()
date= "%s-%s-%s"%(now.year,now.month,now.day)
time_now = "%s-%s"%(now.hour,now.minute)
#---- Write out configuration information to file
config_path = '/home/netops/linsy_env/devconfig/' +date
verify_path = os.path.exists(config_path)
if not verify_path:
os.makedirs(config_path)
config_filename = config_path+"/"+'config_' + ipaddr +"_"+date+"_" + time_now # Important - create unique configuration file name
print ('---- Writing configuration: ', config_filename)
with open( config_filename, "w",encoding='utf-8' ) as config_out:
config_out.write( config_data )
return
def write_issue_device(issue_device):
now = datetime.now()
date= "%s-%s-%s"%(now.year,now.month,now.day)
time_now = "%s-%s"%(now.hour,now.minute)
config_path = '/home/netops/venv/' + "issue_" + date
verify_path = os.path.exists(config_path)
if not verify_path:
os.makedirs(config_path)
config_filename = config_path+"/"+'issue_'+date+"_" + time_now
print ('---- Writing issue: ', config_filename)
with open (config_filename, "w", encoding='utf-8') as issue_facts:
issue_facts.write('\n'.join(issue_device))
def run_gevent(ipaddr):
issue_device = []
try:
hwconfig = get_config(ipaddr)
write_config_to_file(hwconfig,ipaddr)
except (AuthenticationException):
issue_message = (ipaddr + ': 認證錯誤 ')
issue_device.append(issue_message)
except NetMikoTimeoutException:
issue_message = (ipaddr + ': 網(wǎng)絡(luò)不可達 ')
issue_device.append(issue_message)
except (SSHException):
issue_message = (ipaddr +': SSH端口異常 ')
issue_device.append(issue_message)
except Exception as unknown_error:
issue_message = (ipaddr +': 發(fā)生未知錯誤: ')
issue_device.append(issue_message+str(unknown_error))
finally:
write_issue_device(issue_device) #異常處理信息寫入文件
def main():
starting_time = time()
ip_list = read_device_excel()
pool = Pool(100)
pool.map(run_gevent,ip_list) #map(func, iterable)
pool.join()
print ('\n---- End get config threading, elapsed time=', time() - starting_time)
#========================================
# Get config of HUAWEI
#========================================
if __name__ == '__main__':
main()
6聪蘸、代碼詳解
大部分內(nèi)容都是迭代上一小節(jié)的內(nèi)容,只講解新增的協(xié)程部分。
from gevent import spawn
from gevent import monkey;monkey.patch_all()
from gevent.pool import Pool
導(dǎo)入模塊復(fù)制粘貼就好健爬,monkey.patch_all()是為了不修改源代碼直接使用協(xié)程功能的,這么理解比較簡單。
def main():
starting_time = time()
ip_list = read_device_excel()
pool = Pool(100)
pool.map(run_gevent,ip_list) #map(func, iterable)
pool.join()
print ('\n---- End get config threading, elapsed time=', time() - starting_time)
首先定義一個pool鹃答,我一般使用50或者100礁蔗,主要是為了控制下并發(fā)數(shù),別給本地機器帶來太大壓力设拟。
pool.map(第一個參數(shù)是執(zhí)行的函數(shù)慨仿,第二個參數(shù)是可迭代的數(shù)據(jù))
run_gevent函數(shù)也是上一個小節(jié)的內(nèi)容,只是我把需要執(zhí)行的函數(shù)從main函數(shù)中剝離蒜绽,重新定義了一個函數(shù)镶骗。可迭代的數(shù)據(jù)就是能被for循環(huán)的躲雅,list和tuple都屬于可迭代鼎姊。
這里我們用pool.map調(diào)用run_gevent函數(shù)(真正的主程序任務(wù)),并把IP地址列表傳遞進去相赁,就會自動迭代每個IP地址到run_gevent函數(shù)的任務(wù)里相寇。
到此為止,抓取設(shè)備配置這個小場景就可以在現(xiàn)實環(huán)境中應(yīng)用了钮科,但還遠遠不止于此唤衫,還有很多內(nèi)容可以優(yōu)化和使用,我也會在后續(xù)持續(xù)分享绵脯。比如如何加入類和方法佳励,什么是封裝和繼承,如何一次性批量抓取Juniper蛆挫、華為赃承、H3C、Cisco不同廠商的配置悴侵,有的廠商不支持netmiko怎么辦瞧剖?跑批1000臺設(shè)備靠協(xié)程,復(fù)雜任務(wù)跑批1萬臺設(shè)備時如何使用進程+協(xié)程可免?