自动化运维初级村

编程入门 行业动态 更新时间:2024-10-27 14:19:03

自动化运维初级村

自动化运维初级村

摘要

人工定期巡检,应该是大多数做运维工作的朋友的一大心病,面对几十上百台机器,执行相同的命令,要仔细得从输出里提取信息,然后把最终的结果整理到Excel里,发一份邮件抄送老板。

我刚开始工作时巡检也是平台规划中优先级较高的一个模块,需要提供灵活的设备筛选,执行用户指定的命令,应用相应的解析规则,最终把结果导出成Excel。

大多数系统的巡检模块也都具备这些功能,那么今天我们就一起来做一个简易的巡检工具,带着大家边实践边趟一趟用Netmiko巡检的坑。

巡检工具

这里要说一点的是,不管做什么需求,无论是大到系统的设计,还是一个小的脚本,在动手做之前都需要提前做好充分的思考,思考究竟要实现什么功能,这些功能都会涉及到哪些东西,是否需要引入中间件,比如数据库,缓存库等。

如果盲目的开始动手,那么后果就是一直修修改改,既想要这个功能,又想新加那个功能,而由于没有做好充分的设计,而导致扩展性很差,最后堆成了“屎山”;这也是在做功能模块之前需要编写方案设计文档和技术评审的主要原因。

模块设计图

上述流程图就是我们要实现的巡检工具的大概功能设计。

模块概述

设备筛选

在新手村中我们曾经讲解过一个简易的CMDB,即支持从json文件中读写设备信息,也支持从MySQL数据库中读写设备信息,那么这个CMDB功能就可以为我们提供设备筛选的能力,而恰好实际工作中,CMDB同样是会承担这个作用。

命令筛选

由于不同厂商的设备执行的巡检命令会不一样,例如同一个查看镜像版本的操作,可能会对应不同的具体指令;那么我们就需要先根据不同厂商录入好相应的命令,然后巡检的时候可以根据具体要检查的指标项来筛选命令。

执行器

整个巡检工具中最为关键的就是执行器,我们需要实现一个封装好的执行器对象,这个对象对外暴露参数以供其他模块传入;并且它是一个较为抽象的对象,并不是单指通过SSH方式来执行操作,也可以是以API的形式调用厂商API,或者以Netconf的方式对设备进行相应的操作。

解析规则筛选

执行器进行相应的操作之后的回显通常都需要提取关键信息,尤其是以SSH的方式执行完命令后,因此需要针对不同的命令录入其解析规则,这个规则可以是正则表达式,也可以是TextFSM模版,这样就可以在执行完命令之后直接根据相应的规则解析出结构化的数据。

保存结果

巡检的结果需要进行持久化存储,我们可以选择将其保存到MySQL数据库中,并且保存的内容除了处理完的结构化数据之外,也需要将原始输出进行存储,方便后续追溯。

导出

当需要将结果展示或者发送的时候,需要具有导出的功能,具体的细节就是在一定的时间范围内,根据设备和指标项来把之前持久化的数据导出成Excel或CSV。

方案细节

设备筛选

暂且忽略,后续补充

执行器

由于最近的几个章节都在讲解关于Netmiko相关的知识,并且执行器中最为重要的远程执行命令行这一动作,但前期为了便于理解,我们不对执行器做过多的设计,单纯只实现SSH类型的执行器,

执行器必须要具备的几个功能如下:

  • 初始化执行器

  • 获取目标对象

  • 获取执行动作

  • 执行

  • 解析执行结果

  • 保存执行结果

基本代码框架如下

Executor_Mapping = {"SSH": SSHExecutor,  # SSH执行器对象
}      class Executor:def __init__(self, executor_type, *args, **kwargs):if not Executor_Mapping.get(executor_type):raise Exception("has no %s executor" % executor_type)self.executor = Executor_Mapping[executor_type](*args, **kwargs)def fetch_object(self):passdef fetch_action(self):passdef execute(self):passdef parse_result(self):passdef save(self):pass

SSH执行器

SSH执行器必须要具备的几个功能如上述执行器对象所需的相同。

初始化执行器(建立连接)

上一章节中的SSH连接代码如下:

from netmiko import ConnectHandlerparams = {"device_type": "cisco_ios", "host": "192.168.31.149", "username": "cisco", "password": "cisco"
}
net_connect = ConnectHandler(**params)

首先我们需要对连接时的可能存在的异常进行处理,一般的做法如下:

try:conn = ConnectHandler(**device)
except NetmikoAuthenticationException:# TODOreturn
except NetmikoTimeoutException:# TODOreturn
except Exception:# TODOreturn
print(conn.find_prompt()) 

大家可以发现上述代码中的异常分别为NetmikoAuthenticationExceptionNetmikoTimeoutExceptionException;其中Exception类是Python中的最底层Exception,所有的自定义Exception都需要继承Exception基类,Netmiko库中也会自定义一些异常类,这里的前两个异常类就分别是认证异常类和超时异常类。

自定义异常类必须写在异常基类的前面才能被捕获。

通常情况下会在捕获到异常之后,打印堆栈及错误信息,或者将其记录到日志文件中。

这一点Netmiko显然也有考虑,所以专门提供了针对设备连接阶段的异常捕获,如下:

import sys
from netmiko import ConnUnifyparams = {"device_type": "cisco_ios", "host": "192.168.31.149", "username": "cisco", "password": "cisco"
}
conn = ConnUnify(**params)
if conn is None:sys.exit("Logging in failed")print(conn.find_prompt())
conn.disconnect() 

ConnUnify函数的作用就是捕获设备连接过程中各种异常;

该函数的核心代码如下所示:

def ConnUnify(**kwargs: Any,
) -> "BaseConnection":try:kwargs["auto_connect"] = Falsenet_connect = ConnectHandler(**kwargs)hostname = net_connect.hostport = net_connect.portdevice_type = net_connect.device_typegeneral_msg = f"Connection failure to {hostname}:{port} ({device_type})\n\n"net_connect._open()return net_connectexcept NetmikoAuthenticationException as e:msg = general_msg + str(e)raise ConnectionException(msg)except NetmikoTimeoutException as e:msg = general_msg + str(e)raise ConnectionException(msg)except Exception as e:msg = f"An unknown exception occurred during connection:\n\n{str(e)}"raise ConnectionException(msg)

try...except中提取了关键字参数中的主机信息,以便打印日志时使用,除此之外,可能有朋友会对高亮处有疑惑:

明明在BaseConnection中创建连接对象的__init__函数中,auto_connect参数默认为True,会默认调用_open()函数;为什么在ConnLogOnly中要专门把auto_connect设为False,然后在下面单独的调用net_connect._open()函数呢?

我个人的理解是因为ConnectionHandler其实是一个工厂函数,它的作用就是保证根据设备类型返回创建好的连接对象,这个连接对象只要继承了BaseConnection就可以,但子类继承父类时,可以重写其__init__方法,所以可能会存在(或者不保证永远不存在)某个连接对象,在初始化连接对象时不进行_open(),而是在后续再额外发起连接;

如果一旦出现上述情况,那么在对ConnectHandler进行异常捕获时就会无法捕获到建立连接时的异常现象,导致程序中断,这也是为什么这里会显示地调用_open()来建立连接的原因,目的就是为了保证这个try...except一定可以捕获到连接异常。

SSHExecutor的代码如下:

class SSHExecutor:def __init__(self,host: str = "",username: str = "",password: str = None,secret: str = "",port: str = None,device_type: str = "",conn_timeout: int = 10,auth_timeout: int = None,banner_timeout: int = 15,log_file: str = "ssh_executor.log",log_level: str = None,log_format: str = None,) -> None:self.host = hostself.port = portself.username = usernameself.password = passwordself.secret = secretself.device_type = device_typeself.conn_timeout = conn_timeoutself.auth_timeout = auth_timeoutself.banner_timeout = banner_timeoutimport loggingif log_level is None:log_level = logging.ERRORif log_format is None:log_format = "%(asctime)s %(levelname)s %(name)s %(message)s"logging.basicConfig(filename=log_file, level=log_level, format=log_format)self.logger = logging.getLogger(__name__)self.conn = self.connect()def connect(self):try:conn = ConnUnify(**self.__dict__)msg = f"Netmiko connection successful to {self.host}:{self.port}"self.logger.info(msg)return connexcept Exception as e:self.logger.error(str(e))

初始化函数冲需要传入一个关键参数log_file,该参数表示连接过程中的日志输出文件,包括Info日志或者Error日志;如果不传的话默认会保存在同级目录下的ssh_executor.log文件中。

如果有需要还可以传入log_level修改日志打印等级,默认为只打印Error等级,或者传入log_format来修改日志格式

总结

这一章节主要给大家讲解了如何设计一个巡检模块,并对其中的基本组成做了初步的介绍,尤其是针对初始化SSH执行器做了较为深入的讲解,下一章节我们继续巡检模块的讲解,带领大家实现设备和命令的获取。

更多推荐

自动化运维初级村

本文发布于:2024-03-08 18:25:13,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1721805.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!