亲宝软件园·资讯

展开

CMDB_Agent_ssh版本分析

Jeff的技术栈 人气:1
[TOC] # CMDB_Agent+ssh版本+server端 # CMDB_Agent版本 ## CMDB概念 ``` CMDB: Configure Manage DataBase 中文:配置管理数据库。 主要的作用是:收集服务器的基础信息(包括:服务器的主机名,ip,操作系统版本,磁盘,CPU等信息),将来提供给子系统(代码发布,工单系统等)数据 ``` ## CMDB_Agent介绍 ![](https://img2020.cnblogs.com/blog/1736414/202003/1736414-20200325173812371-1506020760.png) ``` 其本质上就是在各个服务器上执行subprocess.getoutput()命令,然后将每台机器上执行的结果,返回给主机API,然后主机API收到这些数据之后,放入到数据库中,最终通过web界面展现给用户 优点:速度快 缺点:需要为每台服务器步数一个Agent的程序 ``` ### agent方案 ``` 将待采集的服务器看成一个agent,然后再服务器上使用python的subprocess模块执行linux相关的命令,然后分析得到的结果,将分析得到的结果通过requests模块发送给API,API获取到数据之后,进行二次比对数据,最后将比对的结果存入到数据库中,最后django起一个webserver从数据库中将数据获取出来,供用户查看 ``` ### ssh类方案 ``` 在中控机服务器上安装一个模块叫paramiko模块,通过这个模块登录到带采集的服务器上,然后执行相关的linux命令,最后返回执行的结果,将分析得到的结果通过requests模块发送给API,API获取到数据之后,进行二次比对数据,最后将比对的结果存入到数据库中,最后django起一个webserver从数据库中将数据获取出来,供用户查看 ``` ### 相比较 ``` agent方案 优点:不需要额外的增加中控机。 缺点:每新增一台服务器,就需要额外部署agent脚本。使用场景是:服务器多的情况 (1000台以上) ssh方案 优点:不需要额外的部署脚本。 缺点:速度比较慢。使用场景是:服务器少 (1000台往下) ``` # client端 ## 架构目录 ![](https://img2020.cnblogs.com/blog/1736414/202003/1736414-20200326214643206-484338717.png) ## bin-start.py 启动文件 ```python from src.srcipt import run if __name__ == '__main__': run() ``` ## conf-config.py 自定义配置文件 模仿Django的setting,常用的配置写在这里面。不常用的写在global_settings.py中。 加载顺寻:先加载全局的。再加载局部的 ```python USER = 'root' MODE = 'agent' DEBUG = True # True:代表是开发测试阶段 False:代表是上现阶段 import os BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PLUGINS_DICT = { 'basic': 'src.plugins.basic.Basic', 'cpu': 'src.plugins.cpu.Cpu', 'disk': 'src.plugins.disk.Disk', # 'memory': 'src.plugins.memory.Memory', } APIURL = 'http://127.0.0.1:8000/api/' ``` ## files 开发测试的文件 DEBUT=True时为测试阶段,用files的测试数据 ## lib-config-global_settings.py 全局配置的文件 ``` pass ``` ## lib-config-conf.py 读取配置的文件 全局配置放在前面先加载,自定义配置的放在后面后加载。自定义配置了就用自定义的(覆盖),没有配置久用全局的 ```python from conf import config from . import global_settings class mySettings(): def __init__(self): # print('aa:', dir(global_settings)) # print('bb:', dir(config)) # 全局配置 for k in dir(global_settings): if k.isupper(): v = getattr(global_settings, k) setattr(self, k, v) # 自定义配置 for k in dir(config): if k.isupper(): v = getattr(config, k) setattr(self, k, v) settings = mySettings() ``` ## src-plugins-__init__.py 核心文件 ```python from lib.config.conf import settings import importlib class PluginsManager(): def __init__(self, hostname=None): self.plugins_dict = settings.PLUGINS_DICT self.debug = settings.DEBUG self.hostname = hostname # 1.采集数据 def execute(self): response = {} for k, v in self.plugins_dict.items(): ''' k: basic v: src.plugins.basic.Basic ''' res = {'status':None, 'data':None} try: # 2.循环导入(字符串路径) moudle_path, class_name = v.rsplit('.', 1) # ['src.plugins.basic','Basic'] # 用importlib.import_module()导入字符串路径 m = importlib.import_module(moudle_path) # 3.导入类 cls = getattr(m, class_name) # 循环执行鸭子类型的process方法,command_func函数的内存地址传过去,把debug传过去 ret = cls().process(self.command_func, self.debug) res['status'] = 10000 res['data'] = ret response[k] = res except Exception as e: import traceback res['status'] = 10001 res['data'] = '错误信息:%s'%(traceback.format_exc()) response[k] = res return response # 真正的连接,执行命令,返回结果的函数。命令变成参数 def command_func(self, cmd): if settings.MODE == 'agent': import subprocess res = subprocess.getoutput(cmd) return res else: import paramiko # 创建SSH对象 ssh = paramiko.SSHClient() # 允许连接不再know_hosts文件中的主机 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 连接服务器 ssh.connect(hostname=self.hostname, port=22, username='root', password='123456') # 执行命令 stdin, stdout, stderr = ssh.exec_command(cmd) # 获取命令结果 result = stdout.read() # 关闭连接 ssh.close() return result ``` ## src-plugins-basic.py 查看硬件信息 ```python from conf import config class Basic(object): def process(self, command_func, debug): if debug: output = { 'os_platform': "linux", 'os_version': "CentOS release 6.6 (Final)\nKernel \r on an \m", 'hostname': 'c1.com' } else: output = { 'os_platform': command_func("uname").strip(), 'os_version': command_func("cat /etc/issue").strip().split('\n')[0], 'hostname': command_func("hostname").strip(), } return output ``` ## src-plugins-cpu.py 查看cpu属性 ```python import os from lib.config.conf import settings class Cpu(): def __init__(self): pass def process(self, command_func, debug): if debug: output = open(os.path.join(settings.BASEDIR, 'files/cpuinfo.out'), 'r', encoding='utf-8').read() else: output = command_func("cat /proc/cpuinfo") return self.parse(output) def parse(self, content): """ 解析shell命令返回结果 :param content: shell 命令结果 :return:解析后的结果 """ response = {'cpu_count': 0, 'cpu_physical_count': 0, 'cpu_model': ''} cpu_physical_set = set() content = content.strip() for item in content.split('\n\n'): for row_line in item.split('\n'): key, value = row_line.split(':') key = key.strip() if key == 'processor': response['cpu_count'] += 1 elif key == 'physical id': cpu_physical_set.add(value) elif key == 'model name': if not response['cpu_model']: response['cpu_model'] = value response['cpu_physical_count'] = len(cpu_physical_set) return response ``` ## src-plugins-disk.py 查看磁盘信息 ```python # 采集磁盘信息 from lib.config.conf import settings import os import re class Disk(object): def __init__(self): pass def process(self, command_func, debug): if debug: output = open(os.path.join(settings.BASEDIR, 'fileshttps://img.qb5200.com/download-x/disk.out'), 'r', encoding='utf-8').read() else: output = command_func('MegaCli -PDList -aALL') # radi 卡 磁盘阵列 return self.parse(output) # 调用过滤的函数 # 过滤函数,对字符串的处理过滤 def parse(self, content): """ 解析shell命令返回结果 :param content: shell 命令结果 :return:解析后的结果 """ response = {} result = [] for row_line in content.split("\n\n\n\n"): result.append(row_line) for item in result: temp_dict = {} for row in item.split('\n'): if not row.strip(): continue if len(row.split(':')) != 2: continue key, value = row.split(':') name = self.mega_patter_match(key) if name: if key == 'Raw Size': raw_size = re.search('(\d+\.\d+)', value.strip()) if raw_size: temp_dict[name] = raw_size.group() else: raw_size = '0' else: temp_dict[name] = value.strip() if temp_dict: response[temp_dict['slot']] = temp_dict return response @staticmethod def mega_patter_match(needle): grep_pattern = {'Slot': 'slot', 'Raw Size': 'capacity', 'Inquiry': 'model', 'PD Type': 'pd_type'} for key, value in grep_pattern.items(): if needle.startswith(key): return value return False ``` # server端 ![](https://img2020.cnblogs.com/blog/1736414/202003/1736414-20200328150503319-212696805.png) ## 架构目录 ``` 服务端目录结构的设计 django的app - api : 负责接收数据, 并且对比入库的 - backend: 前端数据的展示 - repository: 负责数据表的设计 ``` ![](https://img2020.cnblogs.com/blog/1736414/202003/1736414-20200328150220883-694396656.png) ## 配置 ``` 数据库,App注册等省略 ``` ## repository-models.py 表设计 ```python from django.db import models # Create your models here. class UserProfile(models.Model): """ 用户信息 """ name = models.CharField(u'姓名', max_length=32) email = models.EmailField(u'邮箱') phone = models.CharField(u'座机', max_length=32) mobile = models.CharField(u'手机', max_length=32) class Meta: verbose_name_plural = "用户表" def __str__(self): return self.name class UserGroup(models.Model): """ 用户组 """ name = models.CharField(max_length=32, unique=True) users = models.ManyToManyField('UserProfile') class Meta: verbose_name_plural = "用户组表" def __str__(self): return self.name class BusinessUnit(models.Model): """ 业务线 """ name = models.CharField('业务线', max_length=64, unique=True) contact = models.ForeignKey('UserGroup', verbose_name='业务联系人', related_name='c', on_delete=models.CASCADE) class Meta: verbose_name_plural = "业务线表" def __str__(self): return self.name class IDC(models.Model): """ 机房信息 """ name = models.CharField('机房', max_length=32) floor = models.IntegerField('楼层', default=1) class Meta: verbose_name_plural = "机房表" def __str__(self): return self.name class Server(models.Model): """ 服务器信息 """ device_type_choices = ( (1, '服务器'), (2, '交换机'), (3, '防火墙'), ) device_status_choices = ( (1, '上架'), (2, '在线'), (3, '离线'), (4, '下架'), ) device_type_id = models.IntegerField('服务器类型', choices=device_type_choices, default=1) device_status_id = models.IntegerField('服务器状态', choices=device_status_choices, default=1) cabinet_num = models.CharField('机柜号', max_length=30, null=True, blank=True) cabinet_order = models.CharField('机柜中序号', max_length=30, null=True, blank=True) idc = models.ForeignKey('IDC', verbose_name='IDC机房', null=True, blank=True, on_delete=models.CASCADE) business_unit = models.ForeignKey('BusinessUnit', verbose_name='属于的业务线', null=True, blank=True, on_delete=models.CASCADE) hostname = models.CharField('主机名', max_length=128, unique=True) sn = models.CharField('SN号', max_length=64, db_index=True, blank=True) manufacturer = models.CharField(verbose_name='制造商', max_length=64, null=True, blank=True) model = models.CharField('型号', max_length=64, null=True, blank=True) manage_ip = models.GenericIPAddressField('管理IP', null=True, blank=True) os_platform = models.CharField('系统', max_length=16, null=True, blank=True) os_version = models.CharField('系统版本', max_length=16, null=True, blank=True) cpu_count = models.IntegerField('CPU个数', null=True, blank=True) cpu_physical_count = models.IntegerField('CPU物理个数', null=True, blank=True) cpu_model = models.CharField('CPU型号', max_length=128, null=True, blank=True) create_at = models.DateTimeField(auto_now_add=True, blank=True) class Meta: verbose_name_plural = "服务器表" def __str__(self): return self.hostname class Disk(models.Model): """ 硬盘信息 """ slot = models.CharField('插槽位', max_length=8) model = models.CharField('磁盘型号', max_length=32) capacity = models.CharField('磁盘容量GB', max_length=32) pd_type = models.CharField('磁盘类型', max_length=32) server_obj = models.ForeignKey('Server', related_name='disk', on_delete=models.CASCADE) class Meta: verbose_name_plural = "硬盘表" def __str__(self): return self.slot class NIC(models.Model): """ 网卡信息 """ name = models.CharField('网卡名称', max_length=128) hwaddr = models.CharField('网卡mac地址', max_length=64) netmask = models.CharField(max_length=64) ipaddrs = models.CharField('ip地址', max_length=256) up = models.BooleanField(default=False) server_obj = models.ForeignKey('Server', related_name='nic', on_delete=models.CASCADE) class Meta: verbose_name_plural = "网卡表" def __str__(self): return self.name class Memory(models.Model): """ 内存信息 """ slot = models.CharField('插槽位', max_length=32) manufacturer = models.CharField('制造商', max_length=32, null=True, blank=True) model = models.CharField('型号', max_length=64) capacity = models.FloatField('容量', null=True, blank=True) sn = models.CharField('内存SN号', max_length=64, null=True, blank=True) speed = models.CharField('速度', max_length=16, null=True, blank=True) server_obj = models.ForeignKey('Server', related_name='memory', on_delete=models.CASCADE) class Meta: verbose_name_plural = "内存表" def __str__(self): return self.slot class ErrorLog(models.Model): """ 错误日志,如:agent采集数据错误 或 运行错误 """ server_obj = models.ForeignKey('Server', null=True, blank=True, on_delete=models.CASCADE) title = models.CharField(max_length=16) content = models.TextField() create_at = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = "错误日志表" def __str__(self): return self.title ``` ## Api-views.py 数据处理 这里只对磁盘信息做了处理 ``` 数据处理: ''' 老的槽位信息: [1,2,6] 新的槽位信息:[1,2,3,4,5] 处理:增加3,4,5槽位 删除6槽位 检测1,2槽位有无更新磁盘信息 下面的所有事情:分析磁盘的信息与老信息 1.增加了那些槽位, 2.删除了那些槽位 3.更新了那些槽位,记录变更的日志 ''' ``` ```python from django.shortcuts import render,HttpResponse import json from repository import models def asset(request): if request.method == 'POST': info = json.loads(request.body) hostname = info['basic']['data']['hostname'] # c1.com # 每一个服务器的对象,这里固定为c1.com服务器 server_obj = models.Server.objects.filter(hostname=hostname).first() if not server_obj: return HttpResponse('服务器为录入') # 磁盘数据状态码为例 status = info['disk']['status'] # 状态码 if status != 10000: # 添加错误信息 models.ErrorLog.objects.create(title='错误信息', content=info['disk']['data'], server_obj=server_obj) return HttpResponse('采集出错!') ''' 老的槽位信息: [1,2,6] 新的槽位信息:[1,2,3,4,5] 处理:增加3,4,5槽位 删除6槽位 检测1,2槽位有无更新磁盘信息 下面的所有事情:分析磁盘的信息与老信息 1.增加了那些槽位, 2.删除了那些槽位 3.更新了那些槽位,记录变更的日志 ''' new_disk_info = info['disk']['data'] # 新的磁盘信息 print(new_disk_info) old_disk_info = models.Disk.objects.filter(server_obj=server_obj).all() # 老的磁盘信息 # 集合去重 new_slot = set(new_disk_info.keys()) old_slot = [] for obj in old_disk_info: old_slot.append(obj.slot) old_slot = set(old_slot) print(new_slot) print(old_slot) # 增加的槽位数据 add_slot = new_slot.difference(old_slot) if add_slot: for slot in add_slot: ### {'slot': '3', 'pd_type': 'SATA', 'capacity': '476.939', 'model': 'S1AXNSAF912433K Samsung SSD 840 PRO Series DXM06B0Q'}, add_disk_info = new_disk_info[slot] add_disk_info['server_obj'] = server_obj #### 可以将增加的变更 {2,3,4,5} 数据记录到变更日志表中 models.Disk.objects.create(**add_disk_info) # 删除的槽位数据 del_slot = old_slot.difference(new_slot) if del_slot: models.Disk.objects.filter(server_obj=server_obj, slot__in=del_slot).delete() ### 将删除的槽位数据记录到变更日志表中 # 更新的槽位数据 up_slot = new_slot.intersection(old_slot) if up_slot: for slot in up_slot: ## {'slot': '0', 'pd_type': 'SATA', 'capacity': '279.396', 'model': 'SEAGATE ST300MM0006 LS08S0K2B5NV'} new_disk_row = new_disk_info[slot] ## obj(slot:0, pd_type:sas, capacity:234,...) old_disk_row = models.Disk.objects.filter(slot=slot, server_obj=server_obj).first() for k, new_v in new_disk_row.items(): ''' k: slot, pd_type, capacity, model... new_v: 0, SATA, 279 , ... ''' # 利用反射获取 old_v = getattr(old_disk_row, k) if new_v != old_v: # 记录变更日志,利用反射添加 setattr(old_disk_row, k, new_v) old_disk_row.save() print(info) return HttpResponse('ok') else: print('get') print(request.body) return ['c1.com','a2'] ```

加载全部内容

相关教程
猜你喜欢
用户评论