网路行者
首发于网路行者
网络工程师的Python之路---进阶篇

网络工程师的Python之路---进阶篇

版权声明:我已加入“维权骑士”(rightknights.com)的版权保护计划,所有知乎专栏“网路行者”下的文章均为我本人(知乎ID:弈心)原创,未经允许不得转载。



7月6号更新:

最近知乎对专栏文章加入了字数限制,好像是7000字封顶,导致我的《网络工程师的Python之路---进阶篇》刚讲完案例1就必须拆开来写了。以后的更新将会以新文章的方式发布,下面附上《进阶篇(续)》的链接:

弈心:网络工程师的Python之路---进阶篇(续)zhuanlan.zhihu.com图标

前言

本文是《网络工程师的Python之路---初级篇》的续作,《初级篇》以GNS3模拟器为平台,配合Python代码讲解和视频演示,以实验的形式在虚拟思科三层交换机上由浅入深地讲解了Python在计算机网络运维中的部分运用。从《进阶篇》开始,我将从一位资深网络工程师的角度,用实际工作中的案例配合Python代码来讲解Python在大型网络运维(设备数量大于1000台)中的实战应用。当然,《进阶篇》会涉及到更多《初级篇》没有使用到的Python模块和应用技巧,并且所有的Python脚本会在真机上执行和演示。另外,因为我长期在国外工作和生活,个人的习惯问题,我在代码里的注释或者代码里打印出的内容都是用英文写的,不过不用担心,在讲解部分我会用中文翻译和做解释。

写《进阶篇》之前有考虑过是否要把所有案例代码里所涉及到的Python基础知识讲透讲清楚,后来一想那样的话会造成篇幅太长,太费时间和精力,且脱离了当初决定写这两篇文章的初衷。《进阶篇》写完后我会考虑专门写一篇文章主讲Python的基础知识,内容将会侧重网络运维脚本代码中常用到的Python基础知识,不是a-z的全方位Python讲解。

附上《初级篇》的链接:

弈心:网络工程师的Python之路---初级篇zhuanlan.zhihu.com图标

正如我在《初级篇》中提到的,现在以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的操作系统。

  1. 首先点击这里下载Windows版的Python(版本2.7.12),根据自身情况选择32位和64位版本。
  2. 安装过程中有一个很重要的步骤,如下图:"Add python.exe to Path"这里默认是打叉关闭的,请务必记住点开它并选择"Entire feature will be installed on local hard drive.'',它会自动帮你设置好环境变量,(也就是说你以后打开CMD运行Python脚本时,你可以在任意盘符和文件夹下直接输入"python xxx.py"来运行脚本,无需输入python执行程序所在的完整路径来运行脚本,例如"C:\Python27\python 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 paramiko
import time
import subprocess
import os

class 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 + "\n")

	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 + "\n")

执行案例1-脚本1看效果:

执行代码前,我的脚本名叫advance_1_1.py, 保存的位置是在”C:\Users\WANGY0L\Desktop\Python Scripts\zhihu\进阶篇-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 paramiko
import time
import re
from datetime import datetime
import re
import socket

now = 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 "\nYou 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\n')
        self.command.send('show ip int b | i up\n')
        time.sleep(1)
        output = self.command.recv(65535)
        #print output
        self.search_up_port = re.findall(r'GigabitEthernet', 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 "\n"
        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 '\nTACACS is not working for below switches: '
        for i in self.switch_with_tacacs_issue:
            print i
        print '\nBelow 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("\n\nThere are totally " + str(self.total_number_of_ports) + " ports available in the network.")
        f.write("\n" + str(self.total_number_of_up_port) + " ports are currently up.")
        f.write("\nPort up rate is %.2f%%" % (self.total_number_of_up_port / float(self.total_number_of_ports) * 100))
        f.write("\n***************************************************************\n\n")
        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(r'GigabitEthernet', output),来从output里面匹配我们想要的关键词,也就是"GigabitEthernet"这个终端物理端口。
  • 正则表达式是个很庞大的话题,我会在以后专门写一篇Python基础知识来讲解它,目前需要读者自己去学习和了解,这里提几点:re.findall()返回的值是列表,r表示raw string(原始字符串),紧接其后的'GigabitEthernet'就是我们要匹配的关键词,后面的output则是正则表达式所要读取的样本。整个re.findall(r'GigabitEthernet', output)返回的是一个包含了所有GigabitEthernet的列表,简单点来说,你可以把它当成Excel里面的“查找所有”功能。随后我们并将该正则表达式查找的内容赋值给self.number_of_up_port,(self.search_up_port = re.findall(r'GigabitEthernet', 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,新的脚本运行时间和统计信息又会以追加的形式写在下面:


编辑于 2018-11-10

文章被以下专栏收录

    笔者从业计算机网络工作十年(新加坡7年,沙特3年),2013年考取CCIE(R&S),先后在新加坡任职于AT&T, 苹果,Equinix,苏格兰皇家银行等大型企业。 目前在沙特工作,供职于“世界第一土豪大学“阿卜杜拉国王科技大学(KAUST),担任Senior Network Engineer。本专栏只谈计算机网络技术,专栏下所有文章均为笔者原创,我已加入“维权骑士”(rightknights.com)的版权保护计划。