一、资产采集四种方式

 1. Agent方式
API:Django接收数据并入库
  
程序:放置在每台服务器
  
应用场景:针对服务器较多的公司


步骤一:
	#执行本地命令的库
	import subprocess
	sub = subprocess.getoutput("要执行的命令名")
	每台机器通过用户名密码链接数据库,获取要执行的命令
	
步骤二: 采集数据
	import subprocess
	# 采集到本机运行ipconfig命令的输出结果
	result = subprocess.getoutput("ipconfig")
	print(result)
	
步骤三: 筛选整理数据
	# 正则等方式
	result_dic = {
		"网络信息":result,
	}
	
步骤四:发送数据到api
	# 以post方式将数据发送到url
	import requests
	requests.post("url", result_dic)
2. SSH方式
import paramiko
# 通过paramiko库链接各台机器,获取命令
#缺点是慢
  
API:Django接收数据并入库
  
程序:放在中控机
  
应用场景:针对服务器较少的公司
  
SSH方式有三种工具,paramiko、Fabric、Ansible,其中Fabric、Ansible内部原理也是通过paramiko来实现得。
paramiko安装:pip3 install paramiko

andible,fabic等工具对paramiko进行了封装,方便使用 速度更快一些!


import requests,paramiko
# 获取今天未采集的主机名 
# 从url中获取信息
host_list = requests.get("url")

# 通过paramiko连接远程服务器执行命令
# 创建ssh对象
ssh = paramiko.SSHClient()

# 允许连接不在know_hosts文件中的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# 连接服务器
ssh.connect(hostname="hostname", port=22, username="root", password="password")

# 执行命令
stdin, stdout, stderr = ssh.exec_command("hostname")

# 获取结果
result = stdout.read()
print(result)

# 关闭连接
ssh.close()

# 筛选整理数据
result_dic = {
	"网络信息":result,
}

# 发送获取的数据
requests.post("url", result_dic)
3. saltstack【python语言】
方式同ssh,原理不同 
优点:提高速度,开发成本低 
缺点:依赖saltstack工具 
可以远程执行命令
  
http://www.cnblogs.com/wupeiqi/articles/6415436.html
  
API:Django接收数据并入库
  
应用场景:针对服务器较多的公司【推荐】

master:
	v = subprocess.getoutput('salt "*" cmd.run "ls"')
  
saltstack内部原理是通过RPC来实现得消息队列


步骤一: 安装saltstack
	rpm --import https://repo.saltstack.com/yum/redhat/7/x86_64/latest/SALTSTACK-GPG-KEY.pub
	yum install salt-master    服务端
	yum install salt-minion    客户端

步骤二: Master准备
	a.配置文件,修改监听ip
		/etc/salt/master
		interface: 本机ip地址
	b.启动Master
		/etc/init.d/salt-master start

步骤三: Slave准备
	a.修改配置文件,连接哪个master
		/etc/salt/minion
		master: 远程master的ip
	b.启动Slave
		/etc/init.d/salt-slave start

步骤四: 创建关系
	Master:salt-key -L   查看哪些连接
			salt-key -a   接受连接
			salt-key -A   接收全部连接

步骤五: 执行命令
	Master:salt "连接" cmd.run "命令"

# salt 是一个.py文件
#!/usr/bin/python
# Publish commands to the salt system from the command line on the master.

from salt.scripts import salt_main
if __name__ == '__main__':
	salt_main()
	
# 在Python文件中使用salt的方法(需要提前配置好)
import salt.client
local = salt.client.LocalClient()
result = local.cmd('对方ip', 'cmd.run', ['命令']) # result是一个字典
result.keys()   # 命令
result.values() # 执行结果
4. puppet【ruby语言】
内部原理:puppet客户端每30秒钟自动汇报数据给puppet服务端。
  
应用场景:主要是针对已经在使用puppet的公司。

基于ruby 

优点:自动汇报 

缺点:要用ruby写

 

 二、高级配置文件路径

1.目录结构

CMDB资产采集笔记

2.整合配置文件的代码
import os
import importlib
from . import global_settings

class Settings(object):
    def __init__(self):
        # ------------ 找到默认配置文件----------------
        for name in dir(global_settings):
            if name.upper():  # 配置文件里的变量名需要大写
                value = getattr(global_settings,name)  # 拿到值
                setattr(self,name,value)               # 设置值

        # ------------ 找到用户自定义配置文件------------
        settings_module = os.environ.get('USER_SETTINGS')  # 加载设置到环境变量中的配置文件路径名
        if not settings_module: # 如果用户没有自定义配置文件或没有初始化自定义文件
            return              # 直接返回不做其他操作
        user_settings = importlib.import_module(settings_module)
        for name in dir(user_settings):
            if name.upper():  # 配置文件里的变量名需要大写
                value = getattr(user_settings,name)  # 拿到值
                setattr(self,name,value)             # 设置值

settings = Settings()  # 实例化对象

  

 3.加载自定义配置文件的名称到环境变量中
# 以下代码放置需要执行的py文件中
import os
os.environ['USER_SETTINGS'] = "config.settings"
from lib.conf.config import settings # 通过settings可以点出在自定义或默认配置文件中的相关变量名

  

三、可插拔式插件 (资产采集)

1.目录结构

 

CMDB资产采集笔记CMDB资产采集笔记

 

2.流程介绍

主程序start.py 去执行 script.run()

import os
os.environ['USER_SETTINGS'] = "config.settings" # 初始化
import sys
BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASEDIR)  #上级目录加入环境变量中
from src import script  # 导入执行代码
if __name__ == '__main__':
    script.run()

  

 script.py中 根据MODE去执行相应的方法

MODE是资产采集中四种方法中的一种

from lib.conf.config import settings
from .client import Agent
from .client import SSHSALT

def run():
    if settings.MODE == 'AGENT': # 配置文件中MODE的值为AGENT 或 ALT,SSH
        obj = Agent()
    else:
        obj = SSHSALT()
    obj.execute()

  

client.py中主要是拿到查到的主机的硬盘、CPU、内存等信息发给API

如果是Agent:其中Agent 每个客户端自己查询到信息后直接发给API

如果是SSHSALT:中控机服务器先向API发送请求得到要查询的主机列表

然后遍历列表,

PluginManager(host).exec_plugin()  # 依次传入每个主机的host信息,并拿到返回的查询信息

 

import requests
import json
from src.plugins import PluginManager
from lib.conf.config import settings

# 基类 只有向API发送查询到信息的方法
class Base(object):
    def post_asset(self,server_info):
        requests.post(settings.API,json=server_info)
        # body: json.dumps(server_info) 直接写json=server_info可以把里面的东西改成json字符串
        # headers= {'content-type':'application/json'}
        # request.body
        # json.loads(request.body)

# 继承基类,每个客户端自己向API发送查询到的信息
class Agent(Base):
    def execute(self):
        server_info = PluginManager().exec_plugin()
        self.post_asset(server_info)  # 向API发送查询到信息

class SSHSALT(Base):
    def get_host(self):
        # 获取未采集信息的主机列表
        response = requests.get(settings.API)  # 中控机向API发送get请求获取要查询的主机信息
        result = json.loads(response.text)     # "{status:'True',data: ['c1.com','c2.com']}"
        if not result['status']:             #  状态发生异常,则直接结束跳过异常的主机
            return
        return result['data']

    def execute(self):
        host_list = self.get_host()  # 拿到主机列表清单
        for host in host_list:      # 遍历拿到每个主机信息
            server_info = PluginManager(host).exec_plugin()  # 依次传入每个主机的host信息,并拿到返回的查询信息
            self.post_asset(server_info)                    # 向API发送查询到信息

  

在plugins目录下的__init__.py文件中

函数 exec_plugin 获取所有的插件,执行插件里边的方法并获取插件返回值

从配置文件中获取到插件路径和类名

model_path,class_name = v.rsplit('.',1) # 从右向左按点分割一次,并赋值给相应变量

 

# 'basic': "src.plugins.basic.Basic" 数据结构
v 是"src.plugins.basic.Basic"

 

model_path,class_name = v.rsplit('.',1) # 从右向左按点分割一次,并赋值给相应变量
model_file = importlib.import_module(model_path)  # 以字符串的形式导入模块
cls = getattr(model_file,class_name)  # 利用反射机制拿到类对象
import importlib
import traceback
from lib.conf.config import settings

class PluginManager(object):
    def __init__(self,hostname=None):
        self.hostname = hostname
        self.plugin_dict = settings.PLUGINS_DICT  # 插件存放的目录

        self.mode = settings.MODE
        self.debug = settings.DEBUG

        if self.mode == 'SSH':
            # 拿到配置文件中用来SSH登录的相关信息
            self.ssh_user = settings.SSH_USER
            self.ssh_pwd = settings.SSH_PWD
            self.ssh_port = settings.SSH_PORT
            self.ssh_key = settings.SSH_KEY

    def exec_plugin(self):
        """
        获取所有的插件,并执行获取插件返回值
        :return:
        """
        response = {}
        for k,v in self.plugin_dict.items():
            # 'basic': "src.plugins.basic.Basic" 数据结构
            ret = {'status':True,'data':None}
            try:
                model_path,class_name = v.rsplit('.',1) # 从右向左按点分割一次,并赋值给相应变量
                model_file = importlib.import_module(model_path)  # 以字符串的形式导入模块
                cls = getattr(model_file,class_name)  # 拿到类对象
                if hasattr(cls,'initial'):           # 如果类中有initial方法
                    obj = cls.initial()               # 执行类方法initial  并把返回值赋给obj
                else:
                    obj = cls()                       # 否则直接实例化类对象
                # obj.process(self.command,self.debug) 执行插件中的process方法,传入command函数和debug信息
                result = obj.process(self.command,self.debug) # result = "根据v获取类,并执行其方法采集资产"
                ret['data'] = result
            except Exception as e:
                ret['status'] = False
                # traceback.format_exc() 可以返回更详细的错误信息字符串
                ret['data'] = "[%s][%s] 采集数据出现错误 : %s" % (
                self.hostname if self.hostname else "AGENT", k, traceback.format_exc())
            response[k] = ret
        return response

    def command(self,cmd):
        if self.mode == "AGENT":
            return self.__agent(cmd)
        elif self.mode == "SSH":
            return self.__ssh(cmd)
        elif self.mode == "SALT":
            return self.__salt(cmd)
        else:
            raise Exception('模式只能是 AGENT/SSH/SALT')

    def __agent(self,cmd):  # 如果配置文件中的方法是AGENT
        import subprocess
        output = subprocess.getoutput(cmd)
        return output

    def __ssh(self,cmd):  # 如果配置文件中的方法是SSH
        import paramiko

        # 秘钥连接
        # private_key = paramiko.RSAKey.from_private_key_file(self.ssh_key)
        # ssh = paramiko.SSHClient()
        # ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        # ssh.connect(hostname=self.hostname, port=self.ssh_port, username=self.ssh_user, pkey=private_key)
        # stdin, stdout, stderr = ssh.exec_command(cmd)
        # result = stdout.read()
        # ssh.close()

        # 用户名密码连接
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(hostname=self.hostname,port=self.ssh_port,username=self.ssh_user,password=self.ssh_pwd)
        stdin,stdout,stderr = ssh.exec_command(cmd)
        result = stdout.read()  # 读到返回的信息
        ssh.close()
        return result

    def __salt(self,cmd):  # 也是通过操作subprocess
        # import salt.client
        # local = salt.client.LocalClient()
        # result = local.cmd(self.hostname, 'cmd.run', [cmd])
        # return result[self.hostname]

        salt_cmd = "salt '%s' cmd.run '%s'"%(self.hostname,cmd,)
        import subprocess
        output = subprocess.getoutput(salt_cmd)
        return output  # 读到返回的信息

  

四、唯一标识和线程池

1.唯一标识:主板SN(不准确)

标准化:
       - 主机名不重复(装机同时,主机名在cmdb中设置)

步骤:
    a. 装系统,初始化软件(CMDB),运行CMDB:
             - 通过命令获取主机名
             - 写入本地指定文件
    b. 将资产信息发送到API

    c. 获取资产信息:
           - 本地文件主机名 != 命令获取的主机名(按照文件中的主机名)
           - 本地文件主机名 == 命令获取的主机名

服务器资产采集(Agent):
    a. 第一次:文件不存在,或内容为空;
采集资产:
    - 主机名写入文件
    - 发送API

    b. 第N次:采集资产,主机名:文件中获取
SSH或Salt:
    中控机:获取未采集主机名列表:【c1.com 】

# 继承基类,每个客户端自己向API发送查询到的信息
class Agent(Base):
    def execute(self):
        # 获取主机名唯一标识
        server_info = PluginManager().exec_plugin()
        hostname = server_info['basic']['data']['hostname']
        certname = open(settings.CERT_PATH, 'r', encoding='utf-8').read().strip()  # 配置文件里拿到初始主机名
        if not certname:  # 第一次的时候,把主机名写入文件
            with open(settings.CERT_PATH, 'w', encoding='utf-8') as f:
                f.write(hostname)
        else:   # 以配置文件的主机名为准
            server_info['basic']['data']['hostname'] = certname
        self.post_asset(server_info)  # 向API发送查询到信息
View Code

相关文章: